지난 글에서 이어집니다!
지난 글에서 알아보니까 JPA에서 엔티티랑 테이블 맵핑하는 것은 사실 별게 없다. 그런데 필드와 컬럼은 좀 다양하다.
예를 들어 다음과 같은 Enum을 만들었다고 하자.
public enum RoleType {
USER, ADMIN
}
해당 Enum을 JPA에서 사용하고 싶다. 그런데 DB에는 Enum 타입이 없다. 어떻게 해야할까?
그렇다면 @Enumerated
을 사용하면 된다. 이외에도 날짜 타입도 @Temporary
애노테이션으로 해결 가능하다. 하지만 날짜는 자바 8이상에서 지원하는 LocalDateTime
를 사용하는데 더 좋다.
예시
@Entity
public class Member {
@Id
private Long id;
@Column(name = "name")
private String username;
private Integer age;
@Enumerated(EnumType.STRING) //Enum 타입 매핑
private RoleType roleType;
@Temporal(TemporalType.TIMESTAMP) // 날짜 타입 매핑
private Date createdDate;
@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;
@Lob
private String description;
/** 자바 8 이상에서 지원*/
// private LocalDate testLocalDate; //년 월만
// private LocalDateTime testLocalDateTime;//년 월 일
}
이제 @Column에 대해서 자세히 알아보자.
@Column
사실 컬럼이 가장 많이 사용하고 중요하다. 컬럼에는 여러 속성이 존재하는데 어려운 것이 없기 때문에 아래의 사진을 보면 이해가 쉬울 것이다.
@Enumerated
를 사용할 때 주의 사항이 있다.
이넘 타입에는 ORDINAL, STRING 두 가지 속성 중 선택할 수 있다.
아래 코드를 보자.
@Enumerated
package javax.persistence;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Enumerated {
EnumType value() default EnumType.ORDINAL;
}
@Enumerated를 들어가보면 ORDINAL이 default라는 것을 알 수 있다.
하지만 ORDINAL에는 치명적인 문제점이 있다. 코드를 예시로 들면서 설명하겠다.
이넘 타입 설정
@Enumerated(EnumType.ORDINAL) //DB에는 enum 타입이 없음. 사용하기 위한 어노태이션
private RoleType roleType;
이넘 타입을 ORDINAL이라고 설정하자.
Member member = new Member();
member.setId(1L);
member.setUsername("A");
member.setRoleType(RoleType.USER);
em.persist(member);
Member member2 = new Member();
member2.setId(2L);
member2.setUsername("B");
member2.setRoleType(RoleType.ADMIN);
em.persist(member2);
tx.commit();
그리고 위 코드처럼 member 객체를 생성하고 Id, Username, Enum을 넣어주고 persist했다.
DB에서 결과를 확인해보자.
DB의 결과를 확인해보니 Enum이 0, 1 순서로 저장이 됐다.
근데 이게 왜 문제일까?
public enum RoleType {
GUEST, USER, ADMIN
}
만약 Enum에 GUEST를 추가하고
member.setId(3L);
member.setUsername("C");
member.setRoleType(RoleType.GUEST);
em.persist(member)
tx.commit()
member 객체를 생성해서 저장하면 어떻게 될까?
DB를 확인했더니 GUESt는 Enum에서 첫번째 순서이기 때문에 0으로 저장이 된다. 그런데 변경 전 USER 또한 첫번째 순서였기 때문에 0으로 저장이 되어 있다.
순서가 뒤죽박죽이 되고 뭐가 뭔지 알 수가 없다.
그렇기 때문에 STRUNG을 써야한다.
@Enumerated(EnumType.STRING)
private RoleType roleType;
STRING로 변경을 하고 이전에 한 작업을 다시 해보겠다.
이넘 타입이 문자 그대로 들어간다. 이렇게하면 추후에 데이터 타입이 추가되도 순서에 문제가 없다.
결론: ORDINAL 말고 STRING를 사용하자.
날짜 타입을 매핑할 때 사용한다.
하지만 자바 8 이상에서 지원하는 LocalDate, LocalDateTime를 사용할 때는 생략이 가능하다.
LocalDate, LocalDateTime 사용을 권장한다.
Member(Entity) 중
private LocalDate testLocalDate; //년 월만 작성
private LocalDateTime testLocalDateTime;//년 월 일 작성
Member Entity에서 @Temporal로 되어 있던 코드를 뻬고 위의 코드를 추가한뒤 쿼리문을 살펴보자.
쿼리문을 보면 testLocalDate는 DB의 date 타입으로, testLocalDateTime는 Db의 타임 스탬프 타입으로 생성이 된다.
데이터베이스 BLOB, CLOB 타입과 매핑
@Transient
private Integer temp;
기본 키 맵핑 Annotation
직접 할당: @Id
자동 생성: @GeneratedValue
IDENTITY: 데이터베이스에 위임, MYSQL
SEQUENCE: 데이터베이스 시퀀스 오브젝트 사용, ORACLE
TABLE: 키 생성용 테이블 사용, 모든 DB에서 사용
AUTO: 방언에 따라 자동 지정, 기본값
예시
@Id @GeneratedValue(strategy = GenerationType.Auto)
private Long id;
Identity
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Entity
@SequenceGenerator(
name = “MEMBER_SEQ_GENERATOR",
sequenceName = “MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
initialValue = 1, allocationSize = 1)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
이름을 주지 않으면 hibernate_sequence가 만드는 기본 시퀀스를 사용한다. 이때, 테이블마다 시퀀스를 따로 관리하고 싶다면 @SequenceGenerator
를 사용해서 맵핑하면 된다.
@SequenceGenerator
create table MY_SEQUENCES (
sequence_name varchar(255) not null,
next_val bigint,
primary key ( sequence_name )
)
@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;
@TableGenerator
먼저 DB를 먼저 생각해보자. DB의 기본키 제약 조건은 아래의 3가지다.
기본 키 제약 조건
세 가지를 만족하면 DB의 PK로 쓸 수가 있다. 그런데 여기서 '변하면 안된다'라는 조건이 정말 어렵다. 왜냐면 정말 먼 미래까지 변하면 안되기 때문이다.
애플리케이션의 수명이 5년일지 10년일지 알 수 없고, 서비스가 정말 잘되면 20~30년도 살아남을 수 있다. 그때까지 기본 키는 변하면 안된다.
이 조건에 해당하는 자연키(비즈니스 적으로 의미있는 키, 주민등록번호, 전화번호 등)를 찾기가 어렵다. 또한, 주민등록번호 등의 자연키는 많은 부분에서 문제가 될 수 있다.
그래서 랜덤값, 비즈니스와 전혀 상관없는 대체키를 사용하는 걸 권장한다.
출처
자바 ORM 표준 JPA 프로그래밍 강의
게시글 속 자료는 모두 위 강의 속 자료를 사용했습니다.