[자바 ORM 표준 JPA 프로그래밍] 4장. 엔티티 매핑
by Jo
4장. 엔티티 매핑
- JPA에서 지원하는 mapping annotation은 크게 4가지로 분류 가능
- 대표적 annotation은 아래와 같음
- 객체와 table mapping: @Entity, @Table
- primary key mapping: @Id
- field와 column mapping: @Column
- relational mapping: @ManyToOne, @JoinColumn
- mapping은 xml로도 기술 가능
4.1 @Entity
- table과 mapping할 class는 @Entity annotation 필수
- 기본 생성자 필수
- final class, inner class, enum, interface 에는 사용 불가
- 저장할 field에 final 사용 불가
속성 | 기능 | 기본값 |
---|---|---|
name | JPA 에서 사용할 entity 이름을 지정 | class의 이름 |
4.2 @Table
- entity와 mapping할 table을 지정
속성 | 기능 | 기본값 |
---|---|---|
name | mapping할 table 이름 | entity 이름 |
catalog | catalog 기능이 있는 DB에서 catalog mapping | |
schema | schema 기능이 있는 DB에서 schema mapping | |
uniqueConstraints (DDL) | DDL 생성시 unique 제약조건 만듬. 2개 이상 복합 가능. schema 자동 생성 기능 사용하여 DDL 만들 시에만 사용됨 |
4.3 다양한 mapping 사용
코드
@Entity // name = Member (default)
@Table(name="MEMBER") // MEMBER table과 Member entity를 mapping
public class Member{
@Id
@Column(name = "ID")
private String id;
@Column(name = "NAME")
private String username;
private Integer age;
// enum 사용하려면 @Enumerated annotation으로 mapping
@Enumerated(EnumType.STRING)
private RoleType roleType;
// 날짜 타입은 @Temporal annotation으로 mapping
@Temporal(TemporalType.TIMESTAMP)
private Date createdDate;
// 길이제한 없는 CLOB 타입으로 저장하려면 @Lob annotation으로 mapping
@Lob
private String description;
// Getter, Setter
...
}
...
public enum RoleType {
ADMIN, USER
}
이름 mapping strategy 변경
- 카멜 표기법(자바 entity)와 스네이크 표기법(DB column)을 @Column.name 속성 명시 없이 mapping하려면
hibernate.ejb.naming_strategy
속성 사용해서 이름 mapping strategy 변경하면 됨 - hibernate 에서는
org.hibernate.cfg.ImprovedNamingStrategy
class를 제공 - 다음과 같이 적용 가능
<property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy />
</div> </details>
4.4 DB schema 자동 생성
- JPA는 DB schema 자동 생성 기능 지원
- class의 mapping 정보를 보고 DB schema를 자동으로 생성해 줌
- 이 기능을 사용하려면 persistence.xml에 다음과 같은 속성 추가해야 함
<property name="hibernate.hbm2ddl.auto" value="create" />
</div> </details>
- 실제 실행되는 DDL 콘솔로 확인하려면 다음과 같은 속성 추가
<property name="hibernate.show_sql" value="true" />
</div> </details>
hibernate.hbm2ddl.auto
- schema 자동생성 기능 사용 안하려면 이 속성을 아예 지우거나 유효하지 않은 옵션 주면 됨 (ex. none)
- 유효한 옵션은 다음과 같음
옵션 | 설명 |
---|---|
create | 기존 table 삭제 후 생성 (DROP + CREATE) |
create-drop | create 속성에 추가로 애플리케이션 종료시 생성한 DDL 제거 (DROP + CREATE + DROP) |
update | DB table과 entity mapping 정보 비교하여 변경사항만 수정 (UPDATE) |
validate | DB table과 entity mapping 정보를 비교해서 차이 있으면 경고 남기고 어플리케이션 실행 안함 |
4.5 DDL 생성 기능
- DDL 자동 생성 기능 사용시 @Column, @Table annotation의 속성을 활용하여 조건들을 추가할 수 있음
- not null, unique 제약조건, varchar 길이 등등
- !! 이러한 속성들은 자동 생성된 DDL에만 영향을 주며, 자동 생성 기능 미사용 시에는 아무런 역할도 하지 않음 !!
not null, length constraint
- @Column.nullable 속성 값 false로 지정하여 자동 생성 DDL에 not null 제약조건 추가
- @Column.length 속성 값을 지정하여 자동 생성 DDL에 문자의 크기 지정
코드
...
@Column(name = "NAME", nullable = false, length = 10)
private String username;
/** 이렇게 옵션 줄 경우 다음과 같이 DDL 생성됨
*
* CREATE TABLE MEMBER (
* ...
* NAME VARCHAR(10) NOT NULL,
* ...
* )
*
*/
...
unique constraint
- @Table.uniqueConstraints 속성에 @uniqueConstraint annotation으로 유니크 제약조건들 추가
코드
@Entity(name="Member")
@Table(name="MEMBER", uniqueConstraints = {@UniqueConstraint(
name = "NAME_AGE_UNIQUE",
columnNames = {"name", "age"}
)})
public class Member {
...
@Column(name = "name")
private String username;
private Integer age;
/** 이렇게 옵션 줄 경우 다음과 같이 DDL 생성됨
*
* ALTER TABLE MEMBER
* ADD CONSTRAINT NAME_AGE_UNIQUE UNIQUE (name, age)
*
*/
...
}
4.6 primary key mapping
- JPA에서 제공하는 DB primary key 생성 strategy
- IDENTITY: primary key 생성을 DB에 위임
- SEQUENCE: DB sequence를 사용해서 할당
- TABLE: 키 생성 table을 사용
- 오라클은 squence를 제공하지만 mysql은 X, 대신 auto_increment 제공
- ∴ SEQUNCE, IDENTITY strategy은 사용하는 DB에 의존
- TABLE strategy은 키 생성용 table을 만들어두고 sequence 처럼 활용하는 거라 DB 의존 X
- primary key 자동 생성 사용하려면 @Id에 @GeneratedValue annotation 추가하고 원하는 키 생성 strategy 선택하면 됨
- 먼저 아래와 같이 persistence.xml에 키 생성 strategy 속성 추가해야 함
- 먼저 아래와 같이 persistence.xml에 키 생성 strategy 속성 추가해야 함
</div> </details>
4.6.1 primary key 직접 할당 strategy
- 그냥 @Id annotation으로 mapping하면 됨
- @Id 적용 가능 자바 타입
- 자바 기본 타입
- 자바 Wrapper 타입
- String
- java.util.Date
- java.sqlDate
- java.math.BigDecimal
- java.math.BigInteger
코드
@Entity
@Table(name="member")
public class Member {
@Id
private String id;
...
}
...
Member member = new Member();
// entity 저장 전에 직접 키 할당
member.setId("id1");
em.persist(member);
4.6.2 IDENTITY strategy
- primary key 생성을 DB에 위임
- MySQL, PostgreSQL, SQL Server, DB2 에서 주로 사용
- IDENTITY strategy은 mySQL AUTO_INCREMENT 처럼 DB 값 저장 후에야 primary key 구할 수 있을 때 사용
@GeneratedValue(strategy = GenrationType.IDENTITY)
annotation 추가하면 DB 추가로 조회해서 primary key값 얻어옴em.persit()
호출해서 entity 저장하는 시점에 DB가 생성한 값을 JPA 가 추가적으로 조회함 (별도 쿼리)
코드
// code
@Entity
@Table(name="MEMBER")
public class Member {
@Id
@GeneratedValue(strategy = GenrationType.IDENTITY)
private int id;
@Column(length = 64)
private String name;
...
}
sql
# sql
CREATE TABLE MEMBER (
ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
NAME VARCHAR(64)
...
);
...
# ID column 비워두면 DB가 순서대로 값 만들어서 넣어줌
INSERT INTO MEMBER(NAME) VALUES('Alice'); // ID = 1
INSERT INTO MEMBER(NAME) VALUES('Bob'); // ID = 2
IDENTITY strategy과 최적화
JDBC3에 추가된 ```Statement.getGeneratedKeys() 를 사용하면 추가 조회 없이 데이터 저장과 동시에 생성된 primary key값 조회 가능
IDENTITY strategy 사용시 쓰기 지연
IDENTITY 사용시 entity를 DB에 저장해야 identifier 구할 수 있기 때문에em.persist()
호출하면 쓰기 지연 없이 바로 INSERT 쿼리 전송됨
4.6.3 SEQUENCE strategy
- DB sequnce는 유니크한 값을 순차로 생성하는 특별한 DB object
- SEQUENCE strategy은 DB sequence 사용해서 primary key 생성
- 오라클, PostgreSQL, DB2, H2 에서 사용 가능
- SEQUENCE strategy을 사용하려면 먼저 DB에서 sequence 생성해줘야 함 (DDL 자동생성 기능 사용해도 됨)
- SEQUENCE 사용하면
em.persist()
호출시 DB sequence 사용해서 identifier 조회 후 entity에 할당 (flush 전까지 DB 저장 X)- ∴ IDENTITY와 달리 쓰기 지연 가능
sql
# sql
CREATE TABLE MEMBER (
ID INT NOT NULL PRIMARY KEY,
NAME VARCHAR(64)
)
# sequence 생성
CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1;
코드
// code
// entity에 sequence mapping
@Entity
@SequenceGenerator(
name = "BOARD_SEQ_GENERATOR" // SequenceGenerator 이름
sequenceName = "BAORD_SEQ", // mapping할 DB sequence 이름
initalValue = 1, allocationSize = 1
)
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "BOARD_SEQ_GENERATOR") // SequenceGenerator mapping
// @SequenceGenrator(...) // @SequenceGenerator는 여기에다 붙여도 됨
private int id;
}
@SequenceGenerator
속성 | 기능 | 기본값 | 비고 |
---|---|---|---|
name | identifier 생성기 이름 | 필수값 | |
sequenceName | DB에 등록되어 있는 sequence 이름 | hibernate_sequence | sequenceName 기본값은 구현체별로 다름 |
initalValue | DDL 생성시에만 사용됨. sequence DDL 생성할 때 처음 시작하는 수 지정 | 1 | |
allocationSize | sequence 한번 호출에 증가하는 수 | 50 | 성능 최적화에 사용. 반드시 DB sequnce allocationSize에 설정된 값과 똑같이 설정해줘야 함 |
catalog | DB catalog 이름 | ||
schema | DB schema 이름 |
- mapping되는 DDL은 다음과 같음
sql
CREATE SEQUENCE [sequenceName]
START WITH [initialValue] INCREMENT BY [allocationSize]
SEQUENCE strategy과 최적화
SEQUENCE strategy 사용시 DB sequence 에서 할당해줄 identifier을 조회해야 하기 때문에 DB와 2번 통신함
- identifier 구하려고 DB sequence 조회
SELECT BOARD_SEQ.NEXTVAL FROM DUAL
- flush 할 때 조회한 sequence를 primary key 값으로 entity를 DB에 저장
INSERT INTO BOARD...
따라서 sequence 접근 횟수를 줄이기 위해 @SequenceGenerator.allocationSize 속성 사용
hibernate.id.new_generator_mappings=true
일 경우
allocationSize 값 만큼 한번에 DB sequence 증가시키고 그만큼 메모리에 sequence 값 할당해서 사용
다 쓰면 다시 DB sequence 조회하고 메모리 할당 반복
hibernate.id.new_generator_mappings=false
일 경우
DB sequence는 하나씩 증가시키고 대신 애플리케이션에서는 allocationSize 크기만큼 사용
ex) allocationSize=50인 경우에 반환된 sequence 값이 1이면 1~50 사용, 2이면 51~100 사용하는 방식
4.6.4 TABLE strategy
- 키 생성 전용 table을 하나 만들어 DB sequence 를 흉내내는 strategy
- table을 사용하기 때문에 모든 DB에 사용 가능
- 먼저 키 생성 용도 table 만들어야 함 (DDL 자동생성 기능 사용해도 됨)
- table에 값 없으면 JPA가 알아서 초기화 하기 때문에 미리 넣어둘 필요 X
- DB sequence 대신 table 사용한다는 점 빼면 내부 동작 방식은 SEQUENCE strategy과 동일
sql
# sql
CREATE TABLE MY_SEQUENCES (
sequence_name VARCHAR(255) NOT NULL, // sequence 이름
next_Val bigint, // sequence 값
primary key ( sequence_name )
)
코드
// code
@Entity
@TableGenerator(
name = "BOARD_SEQ_GENERATOR"
table = "MY_SEQUENCES",
pkColumnValue = "BOARD_SEQ", allocationSize = 1)
)
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.TABLE,
generator = "BOARD_SEQ_GENERATOR")
private int id;
}
@TableGenerator
속성 | 기능 | 기본값 | 비고 |
---|---|---|---|
name | identifier 생성기 이름 | 필수값 | |
table | 키생성 table명 | hibernate_sequences | 하이버네이트 기준 |
pkColumnName | sequence column명 | sequence_name | 하이버네이트 기준 |
valueColumnName | sequence 값 column명 | next_val | 하이버네이트 기준 |
pkColumnValue | 키로 사용할 값 이름 | entity 이름 | |
initalValue | 초기 값. 마지막 생성된 값이 기준 | 0 | |
allocationSize | sequence 한번 호출에 증가하는 수 | 50 | 성능 최적화에 사용 |
schema | DB schema 이름 | ||
uniqueConstratins | 유니크 제약조건 지정 | DDL |
TABLE strategy과 최적화
최적화 하기 위해서는 SEQUENCE 와 마찬가지로 @TableGenerator.allocationSize 를 사용
다만 TABLE strategy은 SELECT 쿼리로 sequence 값 조회 후 sequence 증가시키기 위해 UPDATE 쿼리를 사용해서 최적화 해도 SEQUENCE strategy보다 한 번 더 DB 와 통신하게 됨
4.6.5 AUTO strategy
- 선택한 DB에 따라 IDENTITY, SEQUENCE, TABLE strategy 중 하나를 자동으로 선택
- ex. 오라클: SEQUENCE, MySQL: IDENTITY
- AUTO에 의해 SEQUENCE, TABLE strategy 선택될 경우 sequence 혹은 키 생성용 table을 미리 만들어 두어야 함
- 아니면 schema 자동 생성 기능 써서 기본값으로 자동 생성되도록 하면 됨
4.6.6 primary key mapping 정리
- persistence context는 identifier으로 entity 구분
- ∴ entity를 persist 상태로 만들려면 identifier 필수
em.persist()
호출 직후 primary key mapping strategy 별로 아래와 같이 진행됨- 직접 할당:
em.persist()
호출 전에 identifier 할당 해놔야 함 - IDENTITY: DB에 entity 먼저 저장해서 identifier 획득 후 persistence context에 저장
- SEQUENCE: DB sequence에서 identifier 획득 후 persistence context에 저장
- TABLE: DB sequence 생성용 table에서 identifier 획득 후 persistence context에 저장
- 직접 할당:
Subscribe via RSS