STUDY/JPA

엔티티 매핑 #2

Anne of Green Galbes 2020. 1. 28. 16:23

1. 기본 키 매핑

JPA가 제공하는 데이터베이스 기본 키 생성 전략은 다음과 같다.

  • 직접 할당 : 기본 키를 애플리케이션에서 직접 할당한다.
  • 자동 생성
    • IDENTITY : 기본 키 생성을 데이터베이스에 위임한다.
    • SEQUENCE : 데이터베이스 시퀀스를 사용해서 기본 키를 할당한다.
    • TABLE : 키 생성 테이블을 사용한다.

이제 자동 기본 키 생성 전략에 대해 자세히 알아보자.

 

1) 직접 할당

기본키를 직접 할당하기 위해서는 다음 코드와 같이 @Id 어노테이션으로 매핑을 해 주면 된다.

@Id
@Column(name="id")
private String id;

@Id 어노테이션 적용 가능 자바 타입은 다음과 같다.

  • 자바 기본형
  • 자바 래퍼(wrapper)형
  • String
  • java.util.Date
  • java.sql.Date
  • java.math.BigDecimal
  • java.math.BigInteger

기본 키 직접 할당 전략은 em.persist()로 엔티티를 저장하기 전에 애플리케이션에서 기본키를 직접 할당하는 방법이다.

Board board = new Board();

//기본 키 직접 할당
board.setId("id1");
em.persist(board)

 

 

2) IDENTITY 전략

IDENTITY 전략은 기본 키 생성을 데이터베이스에 위임하는 전략이다. 주로 MySql, PostgreSQL, SQL Server, DB2에서 사용한다. IDENEITY 전략을 이해하기 위해 MySql의 auto_increment 기능을 잠깐 살펴보도록 하자.

 

먼저 MySql에서 다음과 같이 테이블을 생성 한 후에 데이터를 넣어보자.

create table board (
	id int not null auto_increment primary key,
	data varchar(255)
);

insert into board(data) values('A');
insert into board(data) values('B');

select * from board;

테이블을 생성할 때 auto_increment를 추가했다. 그러면 데이터베이스에 값을 저장할 때 id 값을 비워두면 데이터베이스 순서대로 값을 채워준다.

id data
1 A
2 B

위 표를 보면 insert문에서 id에 값을 넣어주지 않아도 자동으로 id 값이 입력된 것을 볼 수 있다. IDENTITY 전략은 이 auto_increment를 사용한 예제처럼 데이터베이스에 값을 저장하고 나서야 기본 키 값을 구할 수 있을 때 사용한다. 

식별자가 생성되는 경우에는 @GenerationType.IDENTITY 어노테이션을 사용하고 식별자 생성 전략을 선택해야 한다.

@Entity
public class Board{
    @Id 
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id")
    private Long id;
}
private static void logic(EntityManager em) {
  Board board = new Board();
  em.persist(board);
  
  //출력: board.id = 1 
  System.out.println("board.id = " + board.getId());
}

엔티티가 영속 상태가 되려면 식별자가 반드시 필요하다. 그런데 IDENTITY 식별자 생성 전략은 엔티티를 데이터베이스에 저장해야 식별자를 수할 수 있으므로 em.persist()를 호출하는 즉시 insert SQL이 데이터베이스에 전달된다. 따라서 이 전략은 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다.

 

3) SEQUENCE 전략

데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트이다. SEQUENCE 전략은 이 시퀀스를 사용해서 기본 키를 생성한다. 주로 Oracle, PostgreSQL, DB2, H2 데이터베이스에서 사용된다.

 

먼저 다음과 같이 시퀀스를 생성해야 한다.

create table board (
    id bigint not null primary key,
    data varchar(255)  
);

//시퀀스 생성
create sequence board_seq start with 1 increment by 1;

이제 시퀀스 매핑 코드를 작성 해 보자.

// 시퀀스 매핑 코드
@Entity
@SequenceGenerator(
      name = "BOARD_SEQ_GENERATOR",
      sequenceName = "BOARD_SEQ", //매핑할 데이터베이스 시퀀스 이름
      initialValue = 1, allocationSize = 1)
public class Board{
      @Id
      @GeneratedValue(strategy = GenerationType.SEQUENCE,
                      generator = "BOARD_SEQ_GENERATOR")
      private Long id;
      ...
}

먼저 사용할 데이터베이스 시퀀스를 매핑해야 한다. @SequenceGenerator 어노테이선을 사용해서 BOARD_SEQ_GENERATOR라는 시퀀스 생성기를 등록한다. JPA는 이 시퀀스 생성기를 BOARD_SEQ 시퀀스와 매핑한다. 키 전략은 SEQUENCE로 설정하고 generator에는 위에서 등록한 BOARD_SEQ_GENERATOR 시퀀스 생성기를 선택한다.

@SequenceGenerator 어노테이션의 속성은 다음과 같다.

속성 기능 기본값
name 식별자 생성기 이름 필수
sequenceName 데이터베이스에 등록되어 있는 시퀀스 이름 hibernate_sequence
initialValue

DDL 생성 시에만 사용된다.

시퀀스 DDL을 생성할 때 처음 시작하는 수를 지정한다.

1
allocationSize

시퀀스 한 번 호출에 증가하는 수

성능 최적화에 사용된다.

50
catalog, schema 데이터메이스 catelog, schema 이름  

 

마지막으로 시퀀스 사용 코드를 살펴보자.

// 시퀀스 사용 코드
private static void logic(EntityManager em) {
    Board board = new Board();
    em.persist(board);
    
    // 출력: board.id = 1; 
    System.out.println("board.id = " + board.getId());
}

위의 시퀀스 사용 코드는 IDENTITY와 같지만 내부 적으로 동작하는 방식은 다르다.

먼저 em.persist()를 호출할 때 먼저 데이터베이스 시퀀스를 사용해서 식별자를 조회한다. 그리고 조회한 식별자를 엔티티에 할당한 후에 엔티티를 영속성 컨텍스트에 저장한다. 이후 트랜잭션을 커밋해서 플러시가 일어나면 엔티티를 데이터베이스에 저장한다.

[ IDENTITY 전략과 SEQUENCE 전략 동작 방식 간략 정리 ]
  • IDENTITY
    • 엔티티를 데이터베이스에 저장한다.
    • 식별자를 조회해서 엔티티의 식별자에 할당한다.
  • SEQUENCE
    • 시퀀스를 사용해서 식별자를 조회한다.
    • 조회한 식별자를 엔티티에 할당한다.
    • 할당된 엔티티를 영속성 컨텍스트에 저장한다.
    • 트랜잭션을 커밋하여 플러시가 일어나면 엔티티를 데이터베이스에 저장한다.

 

4) TABLE 전략

키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략이다. 모든 데이터베이스에 적용 가능하지만 성능이 떨어진다.

 

Table 정략을 사용하기 위해 먼저 키 생성 용도로 사용할 테이블을 다음과 같이 만들자.

// TABLE 전략 키 생성 DDL
create table MY_SEQUENCE(
      sequence_name varchar(255) not null,
      next_val bigint,
      primary key ( sequence_name )
)

sequence_name을 컬럼을 시퀀스 이름으로 사용하고 next_val 컬럼을 시퀀스 값으로 사용하는 테이블이다.

이제 매핑 코드를 작성 해 보자.

// TABLE 전략 매핑 코드
@Entity
@TableGenerator(
    name = "BOARD_SEQ_GENERATOR",
    table = "MY_SEQUENCES",
    pkColumnValue = "BOARD_SEQ", allocationSize = 1)
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,
                    generator = "BOARD_SEQ_GENERATOR")
    private Long id;
    ...
  }

@TableGenetator 어노테이션의 속성은 다음과 같다.

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

마지막으로 매핑 사용 코드를 살펴보자.

// TABLE 전략 매핑 사용 코드
private static void logic(EntityManager em) {
    Board board = new Board();
    em.persist(board);
    
    // 출력: board.id = 1
    System.out.println("board.id = " + board.getId());
}

Table 전략은 시퀀스 대신에 테이블을 사용한다는 것만 제외하면 SEQUENCE 넞약과 내부 동작방식이 같다. 이제 키 생성기를 사용할 때마다 next_val 컬럼 값이 증가한다.

 

5) AUTO 전략

선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCEM, TABLE 전략 중 하나를 자동으로 선택 해 준다. 오라클은 SEQUENCE, MySql은 IDENTITY를 사용한다. AUTO 전략을 사용하는 방법은 다음과 같다.

@Entity
Public class Board {
      @Id
      @GeneratedValue(strategy = GenerationType.AUTO)
      private Long id;
}
[ 권장하는 식별자 선택 전략 ]

데이터베이스 기본 키느 다음 3가지 조건을 모두 만족해야한다.
  • null값은 허용하지 않는다.
  • 유일해야 한다.
  • 변해서는 안된다.

테이블의 기본 키를 선택하는 전략은 크게 2가지가 있다.
  • 자연 키(natural key)
    • 비즈니스에 의미가 있는 키
    • 주민등록번호, 이메일, 전화번호 등
  • 대리 키(surrogate key)
    • 비즈니스와 관련 없는 임의로 만들어진 키, 대체 키라고도 불린다.
    • 오라클 시퀀스 auto_increment, 키 생성 테이블 사용


권장 : Long형 + 대체키 + 키 생성전략 사용
켤론적으로, auto_increment나 sequence 오브젝트 등을 사용하자.

 

2. 필드와 컬럼 매핑 : 레퍼런스

다음은 JPA에서 제공하는 매핑 어노테이션이다.

분류 매핑 어노테이션 설명
필드와 컬럼 매핑 @Column 컬럼을 매핑한다.
@Eumerated 자바의 enum 타입을 매핑한다.
@Temporal 날짜 타입을 매핑한다.
@Lob BLOB, CLOB 타입을 매핑한다.
@Transient 특정 필드를 데이터베이스에 매핑하지 않는다.
기타 @Acess JPA가 엔티티에 접금하는 방식을 지정한다.

 

1) @Column

@Column 어노테이션은 객체 필드를 테이블 컬럼에 매핑한다. 가장 많이 사용되고 기능도 많다 다음은 @Column 어노테이션의 속성을 정리한 것이다. 이 중 가장 많이 사용하는 속성은 name과 nullable 속성이며, insertable과 updatable 속성은 데이터베이스에 저장되어 있는 정보를 읽기만 하고 실수로 변경하는 것을 방지하고 싶을 때 사용하는 속성이다.

속성 기능 기본값
name 필드와 매핑할 테이블의 컬럼 이름  

insertable

(거의 사용하지 않음)

엔티티 저장 시 이 필드도 같이 저장한다.

false로 설정하면 이 필드는 데이터베이스에 저장하지 않는다.

false 옵션은 읽기 전용일 때 사용한다.

true

 updatable

(거의 사용하지 않음)

 엔티티 수정 시 이 플드도 같이 수정한다.

false로 설정하면 데이터베이스에 수정하지 않는다.

false 옵션은 읽기 전용일 때 사용한다.

true

 table

(거의 사용하지 않음)

하나의 엔티티를 두 개 이상의 테이블에 매핑할 때 사용한다.

저장한 필드를 다른 테이블에 매핑할 수 있다. 

현재 클래스가 매핑된 테이블
 nullable(DDL)

 null 값의 허용 여부를 설정한다.

false로 설정하면 DDL 생성 시에 not null 제약조건이 붙는다.

true
 unique(DDL)

 @Table의 uniqueConstraints와 같지만 한 컬럼에 간단히 유니크 제약조건을 걸 때 사용한다.

만약 두 컬럼 이상을 사용해서 유니크 제약조선을 사용하려면 클래스 레벨에서 @Table.uniqueConstraints를 사용해야 한다.

 
 columnDefinition(DDL)  데이터베이스 컬럼 정보를 직접 줄 수 있다. 필드의 자바 타입과 방언 정보를 사용해서 적절한 컬럼 타입을 생성한다.
length(DDL) 

 문자 길이 제약 조건이다.

String 타입에만 사용한다.

255
precision, scale(DDL)

BigDecimal 타입에서 사용한다.

precision은 소수점을 포함한 전체 자릿수, scale은 소수의 자릿수다.

double, float 타입에는 적용되지 않는다.

아주 큰 숫자나 정밀한 소수를 다루어야 할 때만 사용한다.

precision=19

scale=2

 

2) @Enumerated

자바의 enum 타입을 매필할 때 사용한다.

속성 기능 기본값
value
  • EnumType.ORDINAL : enum 순서를 데이터베이스에 저장한다.
  • EnumType.STRING : enum 이름을 데이터베이스에 저장한다.
EnumType.ORDINAL

기본값이 ORDINAL은 enum의 순서가 바뀌면 안되므로 STRING을 권장한다.

 

 

3) @Temporal

날짜 타입을 매핑할 때 사용한다.

속성 기능 기본값
value
  • TemporalType.DATE
    • 날짜
    • 데이터베이스 data 타입과 매핑하다.
    • 예 : 2019-01-29
  • TemporalType.TIME
    • 시간
    • 데이터베이스 time 타입과 매핑한다.
    • 예 : 11:03:42
  • TemporalType.TIMESTAMP
    • 날짜와 시간
    • 데이터베이스 TIMESTAMP 타입과 매핑한다.
    • 예 : 2020-01-29 11:03:42
TemporalType은 필수로 지정해야 한다.

 

4) @Lob

데이터베이스 BLOB, CLOB 타입과 매핑한다. @LOB에서는 지정할 수 있는 속성이 없지만, 매핑하는 필드 타입이 문자면 CLOB로 나머지는 BLOB로 매핑한다.

  • CLOB : String, char[], java.sql.CLOB
  • BLOB : byte[], java.sql.BLOB5) @Transient

이 어노테이션이 사용된 필드는 매핑하지 않는다. 따라서 데이터베이스에 저장하지 않고 조회되지도 않는다. 객체에 임시로 어떤 값을 보관하고 싶을 때 사용한다.

 

5) @Transient

이 어노테이션이 사용된 필드는 매핑하지 않는다. 데이터베이스에 저장하지 않고 조회하지도 않기 때문에 객체에 임시로 어떤 값을 보관하고 싶을 때 사용한다.

 

6) @ Access

JPA가 데이터에 접근하는 방식을 지정한다.

  • 필드 접근
    • AccessType.FIELD는 필드에 직접 접근한다.
    • 필드 점근 권한이 private이어도 접근할 수 있다.
  • 프로퍼티 접근
    • AccessType.PROPERTY로 지정한다.
    • 접근자(getter)를 사용한다.