Spring Framework/[인프런] Java ORM 표준 프로그래밍 - JPA

Chapter 04. 엔티티 매핑 (entity mapping)

계란💕 2022. 9. 4. 16:28

4.1 객체와 테이블 매핑

 

@Entity

  • @Entity가 붙은 클래스는JPA가 관리한다. "엔티티"
  • JPA를 사용해서 테이블과 매핑할 클래스는 @Entity가 필수!
  • 주의 사항
    • 파라미터 없는 기본 생성자가 필요하다. (public or protected)
    • fianl, enum, interface, inner 클래스에는 @Entity를 사용할 수 없다.
    • DB에 저장할 필드에는 final을 사용하지 않는다.
    • 클래스 이름을 그대로 테이블로 사용한다. (기본값)

 

 

4.2 데이터베이스 스키마 자동 생성

 

데이터베이스 스키마 자동 생성

  • DDL을 애플리케이션 실행 시점에 자동으로 생성한다. CREATE
  • 테이블 중심 => 객체 중심
  • 데이터베이스 dialect를 활용해서 데이터베이스에 맞는 적절한 DDL을 생성한다.
    • ex) 가변 문자열의 경우: Oracle(varchar2), MySQL(varchar) 다르게 생성한다.
    • 생성된 DDL은 개발 장비에서만 사용해야하며, 운영 서버에서는 사용하지 않아야한다. 또는 적절히 다듬은 후에 사용

 

 

  Ex) 자동 생성

  • properties에 다음과 같이 속성을 추가한다.
    • value에 "create-drop"를 넣으면 애플리케이션 종료 시점에 테이블을 지운다.
<property name="hibernate.hbm2ddl.auto" value="create" />

 

  • 멤버 클래스에 테이블 이름을 지정해서 엔티티 추가
<hide/>
package hellojpa;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "MBR")
public class Member {

    @Id
    private Long id;
    private String name;

    public Member() {
    }
    public Member(Long id, String name) {
        this.id = id;
        this.name = name;
    }
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

  Note) 실행 결과

  • 기존에 있다면 테이블을 drop을 먼저 실행한다.
  • 애플리케이션 로딩 시점에 entity가 붙은 클래스에 대해 모두 테이블을 만든다.

  • H2에도 확인 가능

 

 

DDL(Data Definition Language) 자동 생성 옵션

  • create: 기존 테이블 삭제 후 다시 생성한다. (drop + create)
  • create-drop: create와 같으나 종료 시점에 테이블을 drop해버린다. 테스트 케이스를 날리는 경우에 주로 사용한다.
  • update: 변경분만 반영한다. (운영 DB에는 사용하면 안된다.)
  • validate: 엔티티와 테이블이 정상 매핑되었는지 확인만 한다.
  • none: 사용하지 않는다.

 

 

  Ex) create-drop

<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<hide/>
package hellojpa;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
           // 영속 상태
           Member member = em.find(Member.class, 150L);
           member.setName("AAAAA");
           System.out.println("_=========");
           tx.commit();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과

  • drop => create => drop

 

 

  Ex) update

  • age를 추가하고 싶은데 drop은 하지 않고 alter만 하고 싶은 경우
  • Member 클래스에도 private int age를 추가한다.
<property name="hibernate.hbm2ddl.auto" value="update" />
<hide/>
package hellojpa;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try{
           // 영속 상태
           Member member = em.find(Member.class, 150L);
           member.setName("AAAAA");
           System.out.println("=========");
           tx.commit();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과 

 

==================== 오류  화면 ========================

  - 오류: alter가 나오지 않는다.

  - 원인: 다시 create를 했다가 Member클래스에 age 컬럼을 추가하고 update, 다시 매인 클래스를 실행하니까 정상 실행됐다. 

 

  • 오류 화면
    • alter table이 안 나옴

  • 강사님 화면 - "alter table Member"... 가 나온다.

  Note) 정상 실행 화면

 

  • 위 상태에서 int age 컬럼을 지우면 ?
    • update 쿼리가 날라가지 않는다.
    • 새로운 컬럼이 생긴 다음에 update 하면 쿼리가 생기는데 컬럼을 지운 다음에 실행하면 update쿼리가 실행되지 않는다.

 

 

  Ex) validate - 엔티티가 테이블에 정상 매핑되었는지 확인한다.

  • properties에 "validate"를 추가한다.
<property name="hibernate.hbm2ddl.auto" value="validate" />
  • 멤버 클래스에도 임시로 변수 추가
private int gogo;

 

  Note) 실행 결과 - 에러 나는 것이 정상이다. SchemaManagementException

  • 테이블에 gogo 라는 필드가 없기 때문에 다음과 오류가 난다.

 

 

※주의 사항

  • 운영 장비에는 절대로 create, create-drop, update를 사용하면 안된다.
    • update는잘못했다가 데이터베이스 lock이 걸려서 서비스 중단되는 경우도 있다.
  • 개발 초기 단계는 create, update
  • 테스트 서버는 update, validate
  • 스테이징과 운영 서버는 validate 또는 none

 

 

DDL 생성 기능

  • 제약 조건 추가: 회원 이름은 필수, 10자 이하로 작성해야한다.
    • @Column(nullable = false, length = 10)
  • Unique 제약 조건 추가
    • Unique 제약 조건이 애플리케이션에 영향을 주지는 않는다. DDL 생성만 도와준다.
  • DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다.

 

 

  Ex) Unique 제약 조건

<hide/>
@Column(unique = true, length = 10)
private String name;

 

 

  Note) 실행 결과

  • 그럼 다음과 같이 이름에 길이 10이라는 제약 조건이 붙는다.
  • unique 제약 조건도 붙는다.

 

 

 

4.3 필드와 컬럼 매핑

 

  Ex) 요구사항이 있는 경우

  • 요구사항  
    • 회원은 일반 회원 / 관리자 구분
    • 회원은 가입일과 수정일이 있어야한다.
    • 회원을 설명할 수 있는 필드가 있어야한다.
  • Enum 만든다.
<hide/>
package hellojpa;
public enum RoleType {
    USER, ADMIN
}
  • 멤버 클래스를 수정
    • DB에는 ENUM 타입이 없다. => @Enumerated
    • DATE => @Temporal()
      • 매개변수로 DATE(날짜), TIME(시간), TIMESTAMP(날짜 시간)
    • @Lob: 데이터베이스에 varchar를 넘어서는 큰 값을 주고 싶은 경우
      • blob, clob(문자 타입, 디폴트) 두 종류가 있다.

 

<hide/>
package hellojpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
public class Member {

    @Id
    private Long id;

    @Column(name= "name")   // DB에 넣고 싶은 값
    private String userName;    // 객체에 주는 값

    private Integer age;

    @Enumerated(EnumType.STRING)
    private RoleType roleType;

    @Temporal(TemporalType.TIMESTAMP)
    private Date createDate;

    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModifiedDate;

    @Lob
    private String description;

    public Member() {
    }
}

 

<hide/>
package hellojpa;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try{
            tx.commit();
        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과

  • description이 String이니까 @clob으로 생성된다.

 

 

 

매핑 어노테이션 정리

  • @Column: 컬럼 매핑
  • @Temporal: 날짜 타입 매핑
  • @Enumerated: enum 타입 매핑
  • @Lob: BLOB, CLOB 매핑
  • @Transient: 매핑하고 싶지 않은 경우에 사용, 특정 필드를 컬럼에 매핑하지 않는다. 매핑을 무시한다.

 

 

@Column 속성 - 기본값

  • name: 필드와 매핑할 테이블의 컬럼 이름 - 객체의 필드 이름
  • insertable, updatable: 등록, 변경 가능 여부 - TRUE
  • nullable(DDL): FALSE로 설정하면 NOT NULL 제약 조건이 따라 붙는다.  - TRUE
  • unique(DDL): 필드 보다는 클래스 앞에 속성을 붙이는 게 좋다.
    • 필드에 붙이면 UUID 같은 값이 생성되서 이름을 반영하기 어렵다.
<hide/>
@Table(uniqueConstraints = ??)

 

  • columnDefinition (DDL):  데이터베이스 컬럼 정보를 직접 줄 수 있다. ex) varchar(100) default 'EMPTY'
  • length(DDL): 문자 길이 제약 조건, String 타입에만 사용한다. 
  • precision, scale(DDL): Big Decimal 타입에서 사용한다. precision은 소수점을 포함한 전체 자릿수를, scale은 소수의 자릿수이다. double, float 타입에는 적용되지 않는다. 아주 큰 숫자나 정밀한 소수를 다룰 때만 사용한다.
  • value - ORDINAL을 사용하지 말아야 한다.
    • 왜? 나중에 enum의 맨 앞에 다른값이 추가되는 경우가 있다. 그럴 때 문제가 생긴다. (기존에 저장되어 있던 rollType 값이 바뀌지 않기 때문)
    • EnumType.ORDINAL: enum 순서를 데이터베이스에 저장 - 기본값이다.
    • EnumType.String: enum 이름을 데이터베이스에 저장

 

 

  Ex) column 속성 값

  • member
<hide/>
package hellojpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
public class Member {

    @Id
    private Long id;

    @Column(name= "name", nullable = false)   // DB에 넣고 싶은 값
    private String userName;    // 객체에 주는 값

    private Integer age;

    @Enumerated(EnumType.ORDINAL)
    private RoleType roleType;

    @Temporal(TemporalType.TIMESTAMP)
    private Date createDate;

    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModifiedDate;

    @Lob
    private String description;

    public Member() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public RoleType getRoleType() {
        return roleType;
    }

    public void setRoleType(RoleType roleType) {
        this.roleType = roleType;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public Date getLastModifiedDate() {
        return lastModifiedDate;
    }

    public void setLastModifiedDate(Date lastModifiedDate) {
        this.lastModifiedDate = lastModifiedDate;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

 

<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try{
            Member member = new Member();
            member.setId(1L);
            member.setUserName("A");
            member.setRoleType(RoleType.USER);
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과

  • ordinal은 RoleType에 디폴트로 숫자가 들어간다. (enum 순서이므로)

 

  • properties를 update로 수정하고 실행
  • Main
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try{
            Member member = new Member();
            member.setId(2L);
            member.setUserName("B");
            member.setRoleType(RoleType.ADMIN);
            em.persist(member);
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과

  • 회원은 RollType: 0 / admin은RollType이 1로 들어간다.

 

 

  Ex) EnumType.ORDINAL을 쓰면 안 되는 이유

  • Enum 추가
<hide/>
package hellojpa;
public enum RoleType {
    GUEST, USER, ADMIN
}
  • enumType을 String으로 바꾼다.
<hide/>
@Enumerated(EnumType.STRING)
private RoleType roleType;

  Note) 실행 결과 - STRING이 명확하다.

 

 

@Temporal

  • 날짜 타입을 매핑할 때 사용한다.
  • LocalDate, LocalDateTime을 사용할 때는 생략 가능하다. (최신 하이버네이트 지원)

 

  Ex) @Temporal

<hide/>
package hellojpa;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
public class Member {

    @Id
    private Long id;

    @Column(name= "name", nullable = false)   // DB에 넣고 싶은 값
    private String userName;    // 객체에 주는 값

    private int age;

    @Enumerated(EnumType.STRING)
    private RoleType roleType;

    @Temporal(TemporalType.TIMESTAMP)
    private Date createDate;

    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModifiedDate;

    private LocalDate testLocalDate;
    private LocalDateTime testLocalDateTime;

    @Lob
    private String description;

    public Member() {
    }


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public RoleType getRoleType() {
        return roleType;
    }

    public void setRoleType(RoleType roleType) {
        this.roleType = roleType;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public Date getLastModifiedDate() {
        return lastModifiedDate;
    }

    public void setLastModifiedDate(Date lastModifiedDate) {
        this.lastModifiedDate = lastModifiedDate;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

  Note) 실행 결과

  • LocalDate => 데이터베이스에 date
  • LocalDateTime => 데이터베이스에 timestamp로 들어간다.
  • description은 String이므로 CLOB로 매핑된다.

 

 

@Lob(Large Object)

  • 데이터베이스 BLOB(Binary Large Object), CLOB 타입과 매핑
  • @Lob에는 지정할 수 있는 속성이 없다.
  • 매핑하는 필드 타입이 문자면 CLOB 매핑, 나머지는 BLOB 매핑
    • CLOB: String, Char[], java.sql.CLOB
    • BLOB: byte[], java.sql.BLOB

 

 

@Transient

  • 필드에 매핑하지 않고 데이터베이스에도 조회, 저장되지 않는다.
  • 주로 메모리 상에서만 임시로 어떤 값을 보관하고 싶을 때 사용한다.

 

 

4.4 기본 키 매핑

 

JPA가 제공하는 기본 키 매핑 전략

  • 직접 할당 방식: @Id만 사용한다.
  • 자동 생성 방식: @GeneratedValue - null 을 넣어주면 순차적으로 값이 올라가도록 세팅해준다.
    • AUTO: DB dialect에 따라 자동 지정, 기본값
    • IDENTITY: 데이터베이스에 기본 키 생성을 위임한다. -  MySQL(auto_increment)
    • SEQUENCE(시퀀스): 데이터베이스 시퀀스 오브젝트 사용 - Oracle
      • SEQUENCE: 유일한 값을 생성해주는 오라클 객체를 말한다. 
      • 시퀀스를 기본키와 같이 순차적으로 증가하는 컬럼을 자동적으로 생성 가능하다. (pk를 생성하기 위해 사용한다.)
      • @SequenceGenerator: 테이블마다 sequence를 다르게 관리할 수 있다. 
    • TABLE: 키 생성용 테이블 사용, 모든 DB에서 사용한다.
      • @TableGenerator 필요하다.

 

 

  Ex) 기본 키 매핑 - IDENTITY

 

  • properties를 MySQL로 세팅한다.
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
<hide/>
package hellojpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import lombok.Data;
@Entity
@Data
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private String id;

    @Column(name= "name", nullable = false)   // DB에 넣고 싶은 값
    private String userName;    // 객체에 주는 값

    public Member() {
    }
}
  • Main
    • userName으로 c를 넣고 그 다음에 b를 넣는다. 둘다 모두 userId()는 세팅하지 않는다.
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try{
            Member member = new Member();
            member.setUserName("b");
            em.persist(member);
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과 

  •  MySql 에 맞추어 auto_increment가 들어간다.

 

  • id값을 세팅해주지 않았지만 다음과 같이 id가 자동으로 1씩 증가하면서 값이 들어간다.

 

 

  Ex) 기본 키 매핑 - SEQUENCE

  • create
<hide/>
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
  • update 
    • 아래 처럼 바꾸고 Main클래스의 setUserName()에는 "B"를 넣는다.
<property name="hibernate.hbm2ddl.auto" value="none" />

 

  Note) 실행 결과

  • call next value에서 SEQUENCE에서 가져와서 값을 세팅

 


  Ex) sequence generator

<property name="hibernate.hbm2ddl.auto" value="create" />
<hide/>
package hellojpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
@Entity
@SequenceGenerator(name = "member_seq_generator", sequenceName ="member_seq")
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "member_seq_generator")
    private Long id;

    @Column(name= "name", nullable = false)   // DB에 넣고 싶은 값
    private String userName;    // 객체에 주는 값

    public Member() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

  Note) 실행 결과

  • sequence를 member_seq라고 만든다.

 

 

 

TABLE 전략

  • 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략이다.
  • 장점: 모든 데이터베이스에 적용 가능하다.
    • 어떤 데이터베이스는 auto_increment, 어떤 데이터베이스는 sequence라고 할 때, 둘 중 하나를 선택해야 하는데 테이블 전략은 모든 DB에 적용 가능하다.
  • 단점: 성능

 

 

  Ex) TABLE 전략 -  매핑

  •  GenerationType.TABLE:  매핑 전략을 TABLE로 잡는다.
<hide/>
package hellojpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.TableGenerator;
@Entity
@TableGenerator(
    name = "MEMBER_SEQ_GENERATOR",
    table = "MY_SEQUENCES",
    pkColumnValue = "MEMBER_SEQ", allocationSize = 1)
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,
                    generator = "MEMBER_SEQ_GENERATOR")
    private Long id;

    @Column(name= "name", nullable = false)   // DB에 넣고 싶은 값
    private String userName;    // 객체에 주는 값

    public Member() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

  Note) 실행 결과

  • 데이터를 넣을 때마다 NEXT_VAL은 증가한다.

 

 

 

@TableGenerator 속성과 속성 별 기본값

  • name: 식별자 생성기 이름 - 필수
  • table: 키 생성 테이블명 - hibernate_sequences
  • pkColumnName: 시퀀스 컬럼명 - sequence_name
  • valueColumnNa: 시퀀스 값 컬럼명 - next val
  • pkColumnValue: 키로 사용할 값 이름 - 엔티티 이름
  • initialValue: 초기 값, 마지막으로 생성된 값이 기준이다. - 0
  • allocationSize: 시퀀스 한 번 호출에 증가하는 수 (성능 최적화에 사용된다.) - 50
  • catalog, scheme: 데이터베이스 catalog, schema 이름 
  • uniqueConstraints(DDL): 유니크 제약 조건을 지정 가능하다.

 

 

권장하는 식별자 전략

  • 기본 키 제약 조건: null 불가능 & 불변이어야한다.
  • 미래까지 이 조건을 만족하는 자연 키는 찾기 어렵다. 대리 키(대체 키)를 사용하도록 한다.
  • ex) 주민등록번호도 기본 키로 적절하지 않다.
  • 권장: Long형 + 대체 키(UUID) + 키 생성 전략 사용
  • ex) auto_increment, sequence object

 

 

IDENTITY 전략, 특징

  • 기본 키 생성을 데이터베이스에 위임한다.
    • 그래서, IDENTITY 전략은 DB에 데이터가 들어가야 ID를 알 수 있다.
    • 그런데 영속성 컨텍스트에서 관리되려면 PK가 있어야한다.
  • 주로 MySQL, PostgreSQL, SQL Server, DB2 에서 사용한다. ex) MySQL - auto_increment
  • JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행한다. (IDENTITY 와  다른 점)
  • auto_increment는 데이터베이스에 INSERT SQL을 실행한 다음에야 ID값을 알 수 있다.
  • IDENTITY 전략은 em.persist() 시점에 즉시  INSERT SQL 실행하고 DB에서 식별자를 조회한다.

 

 

  Ex) IDENTITY 전략

  • 내가 ID에 값을 직접 넣으면 안된다. (데이터베이스에 위임해야하므로)
  • 커밋이 아닌 persist()를 호출한 시점에 insert 쿼리가 날아간다.
<hide/>
package hellojpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.TableGenerator;

@Entity
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name= "name", nullable = false)   // DB에 넣고 싶은 값
    private String userName;    // 객체에 주는 값

    public Member() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try{
            Member member = new Member();
            member.setUserName("C");
            System.out.println("===============");
            em.persist(member);
            System.out.println("member.id = " + member.getId());
            System.out.println("===============");
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과

  • 따라서, id는 null이라고 나온다.
  • SELECT 쿼리는 나오지 않는다.
    • 왜? JDBC 드라이버에 값을 넣자마자 반환하는 부분이 설정되어 있다. 그래서 INSERT 하는 시점에 바로 알 수 있다.
  • persist() 시점에 DB에 데이터를 넣는다. 그 다음 getId()를 하면 DB에서 데이터를 읽어온다.

 

 

SEQUENCE - @SequenceGenerator

  • name: 식별자 생성기 이름 - 필수
  • sequenceName: 데이터베이스에 등록되어 있는 시퀀스이름 - hibernate_sequence
  • initialValue: DDL 생성 시에만 사용된다. 시퀀스 DDL을 생성할 때, 처음 시작하는 수를 지정한다. - 1
  • allocationSize: 시퀀스 한 번 호출에 증가하는 수 (성능 최적화에 사용된다. 데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값을 반드시 1로 설정해야한다.) - 50
    • allocation size가 왜 50일까?
    • ex) 멤버 1, 2, 3이 있다고 하는 경우, em.persist(member1); em.persist(member2); em.persist(member3)
    • em.persist() 를 할 때마다 next call로 가져온다면 성능 문제가 생길 수 있다.
    • 따라서 next call을 할 때 데이터베이스에 미리 50개를 쌓아 놓는다. 
  • catalog, schema: 데이터베이스 catalog, schema 이름

 

 

  Ex) SEQUENCE 

<hide/>
package hellojpa;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
@Entity
@SequenceGenerator(
    name = "MEMBER_SEQ_GENERATOR",
    sequenceName = "MEMBER_SEQ",    // 매핑할 데이터베이스 시퀀스 이름
    initialValue = 1,
    allocationSize = 1
)
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator = "MEMBER_SEQ_GENERATOR")
    private Long id;

    @Column(name= "name", nullable = false)   // DB에 넣고 싶은 값
    private String userName;    // 객체에 주는 값

    public Member() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

 

<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try{
            Member member = new Member();
            member.setUserName("C");
            System.out.println("===============");
            em.persist(member);
            System.out.println("member.id = " + member.getId());
            System.out.println("===============");
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

 

  Note) 실행 결과

  • start with 1 => 1부터 시작해서 1까지 증가시켜라 (size는 1이니까)

  • call next value for MEMBER_SEQ: 데이터베이스에 다음 값을 달라고 요청한다.
  • commit() 하는 시점에  INSERT 쿼리가 호출된다.

 

 

  Ex) SEQUENCE - allocationSize = 50

<hide/>
package hellojpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
@Entity
@SequenceGenerator(
    name = "MEMBER_SEQ_GENERATOR",
    sequenceName = "MEMBER_SEQ",    // 매핑할 데이터베이스 시퀀스 이름
    initialValue = 1,
    allocationSize = 50
)
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator = "MEMBER_SEQ_GENERATOR")
    private Long id;

    @Column(name= "name", nullable = false)   // DB에 넣고 싶은 값
    private String userName;    // 객체에 주는 값

    public Member() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

 

<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try{
            Member member1 = new Member();
            member1.setUserName("A");
            Member member2 = new Member();
            member2.setUserName("B");
            Member member3 = new Member();
            member3.setUserName("C");
            
            System.out.println("===============");
//            em.persist(member);
            System.out.println("member.id = " + member1.getId());
            System.out.println("member.id = " + member2.getId());
            System.out.println("member.id = " + member3.getId());
            System.out.println("===============");
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

 

  Note) 실행 결과

  • 1부터 50까지 늘리겠다는 설명이 나온다.

  • 여러 웹 서버가 있어도 동시성 이슈 없이 해결된다.

  • 현재 값이 -49

 

 

  • Main클래스에 persist() 추가
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try{
            Member member1 = new Member();
            member1.setUserName("A");
            Member member2 = new Member();
            member2.setUserName("B");
            Member member3 = new Member();
            member3.setUserName("C");

            System.out.println("===============");
            em.persist(member1);
//            em.persist(member2);
//            em.persist(member3);

            System.out.println("member.id = " + member1.getId());
            System.out.println("member.id = " + member2.getId());
            System.out.println("member.id = " + member3.getId());
            System.out.println("===============");
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과

  • call next value가 왜 두 번 호출될까?
    • DB SEQ 값은 1이었다가 두 번 호출하면 51이 된다.

 

  • persist() 세 개 추가
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try{
            Member member1 = new Member();
            member1.setUserName("A");
            Member member2 = new Member();
            member2.setUserName("B");
            Member member3 = new Member();
            member3.setUserName("C");

            System.out.println("===============");
            em.persist(member1);
            em.persist(member2);
            em.persist(member3);

            System.out.println("member.id = " + member1.getId());
            System.out.println("member.id = " + member2.getId());
            System.out.println("member.id = " + member3.getId());
            System.out.println("===============");
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과

  • call next value: 1로 맞춤
  • call next value: 51번 까지 미리 확보하기 위해 호출된다.
    • 그 다음 부터는 더 이상 next call 이 호출되지 않는다.
    • 여러 개 추가하고나서 51번을 만나면 미리 100개를 확보해야 하므로 한 번 더 next call이 호출될 것이다.

 

 

 

4.5 요구 사항 분석과 기본 매핑

테이블 설계

 

엔티티 설계와 매핑

 

 

  Ex)

  • 새로운 maven 프로젝트를 만든다.

<hide/>
package jpabook.jpashop;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try{
            tx.commit();
        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

 

=======================================오류 ========================================

  - 오류: 데이터베이스가 없다는 오류가 난다.

  - 원인: 초기 접속 url 입력 오류

 

  • 정상 실행 화면
    • 데이터베이스에 처음 연결할 때는 아래과 같이 :"tcp localhost"를 빼고 URL을 넣어야 데이터베이스가 생성된다.

  Note) 실행 결과 - 데이터베이스와 테이블이 생긴다.

 

  • Order 테이블에 member  필드를 추가하면 다음과 같은 알림이 뜬다.

 

 

 

데이터 중심 설계의 문제점

  • 현재 방식은 객체 설계를 테이블 설계에 맞춘 방식이다.
  • 테이블의 외래  키를 객체에 그대로 가져온다.
  • 객체 그래프 탐색이 불가능하다
  • 참조가 없으므로 UML(Unified Modeling Language, 통합 모델링 언어)도 잘못됨
  • Order클래스에서 MemberId가 아닌 진짜 멤버를 가질 수 있도록 연관 관계 매핑을 배운다!

 

 

출처 - https://www.inflearn.com/course/ORM-JPA-Basic

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com