엔티티 매핑 #2
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 |
기본값이 ORDINAL은 enum의 순서가 바뀌면 안되므로 STRING을 권장한다.
3) @Temporal
날짜 타입을 매핑할 때 사용한다.
속성 | 기능 | 기본값 |
value |
|
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)를 사용한다.