오봉이와 함께하는 개발 블로그
JPA - 필드와 컬럼 매핑, 기본 키 매핑 본문
필드와 컬럼 매핑
아래와 같은 가정을 해보자
- 회원은 일반 회원과 관리자로 구분해야 한다.
- 회원 가입일과 수정일이 있어야 한다.
- 회원을 설명할 수 있는 필드가 있어야 한다. 이 필드는 길이 제한이 없다.
@Entity
@Getter
@Setter
public class Member {
@Id
private Long id;
@Column(name = "name")
private String username;
private Integer age;
@Enumerated(EnumType.STRING)
private RoleType roleType;
@Temporal(TemporalType.TIMESTAMP)
private Date createdDate;
@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;
@Lob
private String description;
}
/* ======================================= */
public enum RoleType {
USER, ADMIN
}
매핑 어노테이션 정리
- @Id
- PK 매핑
- @Column
- 컬럼 매핑
- name 속성을 통해 DB에 있는 컬럼명을 명시해줄 수 있다.
- @Enumerated
- enum 타입 매핑
- @Temporal
- 날짜 타입 매핑
- DATE(날짜), TIME(시간), TIMESTAMP(날짜 + 시간) 총 세 가지 타입이 있다.
- @lob
- BLOB, CLOB 매핑(varchar 타입보다 큰 공간을 사용하기 위함)
- @Transient
- 특정 필드를 컬럼에 매핑하지 않는다(DB와 매핑하지 않고 메모리에서만 사용)
@Column
- name : 필드와 매핑할 테이블의 컬럼 이름, 기본값은 객체의 필드 이름이다.
- insertable, updatable : 해당 컬럼의 등록, 변경 가능 여부(true, false) 기본값은 true이다.
- nullable(DDL) : null 값의 허용 여부를 설정. false로 설정하면 DDL 생성 시 not null 제약조건이 붙는다.
- unique(DDL) : @Table의 uniqueConstraints와 같지만 한 컬럼에 간단히 유니크 제 약조건을 걸 때 사용한다. 하지만 제약조건의 이름이 랜덤값으로 생기기 때문에 잘 사용하지 않는다.
- columnDefinition(DDL) : DB 컬럼 정보를 직접 줄 수 있다. ex) varchar(100) default ‘EMPTY'
- length(DDL) : 문자 길이 제약조건, String 타입에만 사용한다. 기본값은 255다.
- precision, scale(DDL) : BigDecimal 타입에서 사용한다(BigInteger도 사용할 수 있다). precision은 소수점을 포함한 전체 자릿수를, scale은 소수의 자릿수다. 참고로 double, float 타입에는 적용되지 않는다. 아주 큰 숫자나 정밀한 소수를 다루어야 할 때만 사용한다.
@Enumerated
자바 enum 타입을 매핑할 때 사용한다.
- value(기본값 EnumType.ORDINAL)
- EnumType.ORDINAL : enum 순서를 데이터베이스에 저장(0부터 시작)
- EnumType.STRING : enum 이름을 데이터베이스에 저장
ORDINAL을 사용하면 0부터 숫자로 저장되기 때문에 enum 클래스를 수정했을 때 문제가 발생한다.
예를 들면 기존 USER와 ADMIN enum 타입이 있었는데, 한 가지 타입을 추가해서 GUEST, USER, ADMIN이 됐다고 가정하자.
새로운 정보가 DB에 insert 될 때 기존 0번은 USER 타입인데, GUEST가 추가되어 0번이 GUEST로 바뀐다면 해결할 수 없는 버그가 생기는 것이다.
고로 ORDINAL을 사용하지 않는 것을 강권한다.
@Temporal
날짜 타입(java.util.Date, java.util.Calendar)을 매핑할 때 사용한다
참고로 LocalDate, LocalDateTime을 사용할 때는 생략 가능하다.
- TemporalType.DATE : 날짜, 데이터베이스 date 타입과 매핑(2021-10-11)
- TemporalType.TIME : 시간, 데이터베아스 time 타입과 매핑(11:11:11)
- TemporalType.TIMESTAMP : 날짜와 시간, 데이터베이스 timestamp 타입과 매핑(2021-10-11 11:11:11)
@Lob
데이터베이스 BLOB, CLOB 타입과 매핑한다.
@Lob에는 지정할 수 있는 속성이 없다.
매핑하는 필드 타입이 문자면 CLOB 매핑, 나머지는 BLOB 매핑
- CLOB: String, char[], java.sql.CLOB
- BLOB: byte[], java.sql. BLOB
@Transient
필드 매핑X, 데이터베이스에 저장X, 조회X
주로 메모리상에서만 임시로 어떤 값을 보관하고 싶을 때 사용한다.
기본 키 매핑
기본 키 매핑 어노테이션으로는 @Id, @GeneratedValue가 있다.
- 직접 할당
- @Id
- 자동 생성
- @GeneratedValue(strategy = GenerationType.XXX)
- IDENTITY : DB에 위임, MYSQL
- SEQUENCE : DB 시퀀스 오브젝트 사용, ORACLE
- @SequenceGenerator 필요
- TABLE : 키 생성용 테이블 사용, 모든 DB에서 사용
- @TableGenerator 필요
- AUTO : 방언에 따라 자동 지정, 기본값
- @GeneratedValue(strategy = GenerationType.XXX)
IDENTITY 전략
- 기본 키 생성을 DB에 위임한다.
- 주로 MySQL, PostgreSQL, SQL server, DB2에서 사용한다.(예 : MySQL AUTO_INCREMENT)
- JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행
- AUTO_INCREMENT는 DB에 INSERT SQL을 실행한 후 ID값을 알 수 있다.
- IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL 실행 하고 DB에서 식별자를 조회
IDENTITY 전략은 내가 값을 세팅하지 않고 insert를 해야 한다.
null로 insert 쿼리가 날아오면 DB에서 값을 세팅한다.
그래서 DB에 값이 들어가야 ID값을 알 수 있다.
영속성 컨텍스트는 PK가 있어야 하는데 IDENTITY는 DB에 값이 들어가야 ID값을 알 수 있기 때문에 JPA가 영속성 컨텍스트에 등록할 방법이 없어진다.
그래서 JPA는 IDENTITY 전략 한정으로 em.persist() 하는 시점에 insert 쿼리를 날려준다.(원래는 commit 시점에 날아가야 함)
그후 JPA는 영속성 컨텍스트에 등록을 한다.
그래서 IDENTITY는 모아서 insert하지 못 한다.
SEQUENCE 전략
- DB 시퀀스는 유일한 값을 순서대로 생성하는 특별한 DB 오브젝트(예: 오라클 시퀀스)
- 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용
@Entity
@SequenceGenerator(
name = “MEMBER_SEQ_GENERATOR",
sequenceName = “MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
initialValue = 1, allocationSize = 1)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
private Long id;
SEQUENCE 전략 - @SequenceGenerator
- name : 식별자 생성기 이름으로, 필수값
- sequenceName : DB에 등록되어 있는 시퀀스 이름
- initialValue : DDL 생성 시에만 사용, 시퀀스 DDL을 생성할 때 처음 1 시작하는 수를 지정한다.(기본값 1)
- allocationSize : 시퀀스 한 번 호출에 증가하는 수(기본값 50)
- 성능 최적화에 사용됨 데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값 을 반드시 1로 설정해야 한다
SEQUENCE 전략도 마찬가지도 영속성 컨텍스트에 등록하기 위해 PK가 필요한데 해당 정보는 DB가 가지고 있다.
그래서 SEQUENCE을 사용하면 em.persist()전에 해당 시퀀스를 얻어 영속성 컨텍스트에 등록한다.
이 상태까지는 아직 DB에 insert된 상태가 아니고, 실제 insert는 트랜잭션 시점에 발생한다.
그럼 시퀀스를 계속 확인해야 하기 때문에 반복적으로 통신을 해서 성능상 더 안 좋지 않느냐? 라고 생각할 수 있지만 allocationSize 옵션을 사용하면 해당 걱정이 사라진다.
allocationSize 옵션은 시퀀스를 미리 정해둔 수 만큼 확보하고, 메모리에 저장해서 확인할 수 있게 도와준다.
예를 들어 시퀀스가 1씩 증가하고, allocationSize가 50일 때 시퀀스를 미리 50개만큼 확보하고 1, 2, 3, 4, .. , 50이 되면 다시 시퀀스를 50개만큼 더 확보하여 앞 상황과 같이 동작한다.
TABLE 전략
키 생성 전용 테이블을 하나 만들어서 DB 시퀀스를 흉내내는 전략이다.
장점으로는 모든 DB에서 사용할 수 있지만, 단점으로는 테이블을 하나 더 만들어서 연관관계 같이 사용하기 때문에 성능이 떨어진다.
@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;
create table MY_SEQUENCES (
sequence_name varchar(255) not null,
next_val bigint,
primary key ( sequence_name )
)
@TableGenerator - 속성
- name : 식별자 생성기 이름 (필수)
- table : 키생성 테이블명 (기본값 hibernate_sequences)
- pkColumnName : 시퀀스 컬럼명 (기본값 sequence_name)
- valueColumnName : 시퀀스 값 컬럼명 (기본값 next_val)
- pkColumnValue : 키로 사용할 값 이름 (기본값 엔티티 이름)
- initialValue : 초기 값, 마지막으로 생성된 값이 기준이다. (기본값 0)
- allocationSize : 시퀀스 한 번 호출에 증가하는 수(성능 최적화에 사용됨, 기본값 50)
권장하는 식별자 전략
기본키 제약 조건은 null아니여야 하고 유일해야 하며 변하면 안된다.
미래에 이 조건을 만족하는 자연키(비즈니스상 의미 있는 키)는 찾기 어렵기 때문에 대리키(비즈니스와 상관 없는 키)를 사용하자.
Long 타입 + 대체키 + 키 생성전략 사용하는 것을 권장한다
출처 : 인프런 김영한 지식공유자님의 스프링 부트와 JPA 실무 완전 정복 로드맵 강의
'BE > JPA' 카테고리의 다른 글
JPA - 단방향 연관관계 (0) | 2022.06.24 |
---|---|
JPA - 실전 예제 1 - 요구사항 분석과 기본 매핑 (0) | 2022.06.24 |
JPA - 객체와 테이블 매핑, DB 스키마 자동 생성 (0) | 2022.06.24 |
JPA - 플러시, 준영속 상태 (0) | 2022.06.23 |
JPA - 영속성 컨텍스트 (0) | 2022.06.23 |