✏️ Entity Mapping (2)

박상민·2023년 9월 29일
0

JPA

목록 보기
8/24
post-thumbnail

지난 글에서 이어집니다!

📌 필드와 컬럼 매핑

지난 글에서 알아보니까 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

@Column
사실 컬럼이 가장 많이 사용하고 중요하다. 컬럼에는 여러 속성이 존재하는데 어려운 것이 없기 때문에 아래의 사진을 보면 이해가 쉬울 것이다.

✔︎ @Enumerated

@Enumerated를 사용할 때 주의 사항이 있다.
이넘 타입에는 ORDINAL, STRING 두 가지 속성 중 선택할 수 있다.

  • ORDINAL: Enum의 순서를 DB에 저장
  • STRING: Enum의 이름을 DB에 저장

아래 코드를 보자.
@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를 사용하자.

✔︎ @Tenporal

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

하지만 자바 8 이상에서 지원하는 LocalDate, LocalDateTime를 사용할 때는 생략이 가능하다.

LocalDate, LocalDateTime 사용을 권장한다.

Member(Entity) 중

private LocalDate testLocalDate; //년 월만 작성
private LocalDateTime testLocalDateTime;//년 월 일 작성

Member Entity에서 @Temporal로 되어 있던 코드를 뻬고 위의 코드를 추가한뒤 쿼리문을 살펴보자.


쿼리문을 보면 testLocalDate는 DB의 date 타입으로, testLocalDateTime는 Db의 타임 스탬프 타입으로 생성이 된다.

✔︎ @Lob

데이터베이스 BLOB, CLOB 타입과 매핑

  • @Lob에는 지정할 수 있는 속성이 없다.
  • 매핑하는 필드 타입이 문자면 CLOB 매핑, 나머지는 BLOB 매핑
    • CLOB: String, char[], java.sql.CLOB
    • BLOB: byte[], java.sql. BLOB

✔︎ @Transient

  • 필드 매핑X
  • 데이터베이스에 저장X, 조회X
  • 주로 메모리상에서만 임시로 어떤 값을 보관하고 싶을 때 사용
@Transient
private Integer temp;

📌 기본 키 맵핑

기본 키 맵핑 Annotation

직접 할당: @Id

자동 생성: @GeneratedValue

  • IDENTITY: 데이터베이스에 위임, MYSQL

  • SEQUENCE: 데이터베이스 시퀀스 오브젝트 사용, ORACLE

    • @SequenceGenerator 필요
  • TABLE: 키 생성용 테이블 사용, 모든 DB에서 사용

    • @TableGenerator 필요
  • AUTO: 방언에 따라 자동 지정, 기본값

예시

@Id @GeneratedValue(strategy = GenerationType.Auto)
private Long id;

✔︎ IDENTITY 전략

Identity

  • 기본 키 생성을 데이터베이스에 위임
  • 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용 (예: MySQL의 AUTO_ INCREMENT)
  • JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행
  • AUTO_ INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에 ID 값을 알 수 있음
  • IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회
@Entity
public class Member {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;

✔︎ SEQUENCE 전략

  • 데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트(예: 오라클 시퀀스)
  • 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용
@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

✔︎ TABLE 전략

  • 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략
  • 장점: 모든 데이터베이스 적용 가능
  • 단점: 성능이 안좋음
    운영에서는 테이블 전략을 쓰기가 부담스럽다. DB에서 보통 관계로 쓰는 것들이 있기 때문에 그냥 그걸 쓰는 걸 권장한다.
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가지다.

기본 키 제약 조건

  • null이면 안된다.
  • 유일해야 한다.
  • 변하면 안된다.

세 가지를 만족하면 DB의 PK로 쓸 수가 있다. 그런데 여기서 '변하면 안된다'라는 조건이 정말 어렵다. 왜냐면 정말 먼 미래까지 변하면 안되기 때문이다.

애플리케이션의 수명이 5년일지 10년일지 알 수 없고, 서비스가 정말 잘되면 20~30년도 살아남을 수 있다. 그때까지 기본 키는 변하면 안된다.

이 조건에 해당하는 자연키(비즈니스 적으로 의미있는 키, 주민등록번호, 전화번호 등)를 찾기가 어렵다. 또한, 주민등록번호 등의 자연키는 많은 부분에서 문제가 될 수 있다.

  • 미래까지 이 조건을 만족하는 자연키는 어렵다. 대리키(대체키)를 사용하자.

그래서 랜덤값, 비즈니스와 전혀 상관없는 대체키를 사용하는 걸 권장한다.

  • 권장: Long형 + 대체키 + 키 생성전략 사용

출처
자바 ORM 표준 JPA 프로그래밍 강의
게시글 속 자료는 모두 위 강의 속 자료를 사용했습니다.

profile
스프링 백엔드를 공부중인 대학생입니다!

0개의 댓글