매핑

JPA를 사용하는 데 가장 중요한 일은 엔티티와 테이블을 정확히 매핑하는 것이다.

  • 객체와 테이블 매핑 : @Entity, @Table

  • 기본 키 매핑 : @Id

  • 필드와 컬럼 매핑 : @Column

  • 연관관계 매핑 : @ManyToOne, @JoinColumn

@Entity

JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 어노테이션을 필수로 붙여야 한다.

@Entity 적용 시 주의사항

  • 기본 생성자는 필수다(파라미터가 없는 public 또는 protected 생성자)

  • final 클래스, enum, interface, inner 클래스에는 사용할 수 없다.

  • 저장할 필드에 final을 사용하면 안 된다.

자바는 생성자가 하나도 없으면 파라미터가 없는 기본 생성자를 자동으로 만든다.

public Member() {} // 기본 생성자

@Table

@Table은 엔티티와 매핑할 테이블을 지정한다. 생략하면 매핑한 엔티티 이름을 테이블 이름으로 사용한다.

catalog 기능과 schema 기능이란?

다양한 매핑 사용

package jpabook.start;
import javax.persistence.*;
import java.util.Date;

@Entity
@Table (name="MEMBER") 
public class Member {
     @Id @Column (name = "ID")
     private String id;
     @Column (name - "NAME")
     private String username;
     private Integer age;
     //== 추가 ==
     @Enumerated (EnumType. STRING)
     private RoleType roleType; // 1
     @Temporal (TemporalType. TIMESTAMP)
     private Date createdDate; // 2 
     @Temporal (TemporalType. TIMESTAMP)
      private Date lastModifiedDate; // 2
      @Lob
     private String description; //3
}

//Getter, Setter 
package jpabook.start; 
public enum RoleType {
     ADMIN, USER
}
  1. roleType : 자바의 enum을 사용하려면 @Enumerated 어노테이션으로 매핑해야 한다.

  2. createdDate, lastModifiedDate : 자바의 날짜 타입은 @Temporal을 사용해서 매핑한다.

  3. 회원을 설명하는 필드는 길이 제한이 없다. 따라서 데이터 베이스의 VARCHAR 타입 대신에 CLOB 타입으로 저장해야 한다. @Lob을 사용하면 CLOB, BLOB 타입을 매핑할 수 있다.

@Temporal, @Lob은 실무에선 잘 사용하지 않는데 그 이유는?

데이터베이스 스키마 자동 생성

hibernate.hbm2ddl.auto

테이블의 유니크 제약조건을 만들어 주는 @TableuniqueConstraints 속성을 사용할 수 있다.

@Entity (name="Member")
@Table (name="MEMBER", uniqueConstraints = {
@UniqueConstraint( //추가     
		name = "NAME AGE UNIQUE",
    columnNames = {"NAME", "AGE"} )})
 public class Member {
     @id
     @Column (name = "id")
     private String id;
    
     @column (name = "name")
     private String username;
    
     private Integer age;
    ...
}

기본 키 매핑

데이터베이스마다 기본 키를 생성하는 방식이 서로 다르므로 이 문제를 해결하기는 쉽지 않다.

JPA는 이런 문제들을 어떻게 해결하는지 알아보자

  • 직접 할당 : 기본 키를 애플리케이션에서 직접 할당한다.

  • 자동 생성 : 대리 키 사용 방식

    • IDENTITY : 기본 키 생성을 데이터베이스에 위임한다.

    • SEQUENCE : 데이터베이스 시퀀스를 사용해서 기본 키를 할당한다.

    • TABLE : 키 생성 테이블을 사용한다.

오라클 데이터베이스는 시퀀스를 제공하지만 MYSQL은 시퀀스를 제공하지 않는다. 대신에 MySQL은 기본 키 값을 자동으로 채워주는 AUTO_INCREMENT 기능을 제공한다. 따라서 SEQUENCEIDENTITY 전략은 사용하는 데이터베이스에 의존한다.

기본 키 직접 할당 전략

@Id 적용 가능 자바 타입은 다음과 같다.

  • 자바 기본형

  • 자바 래퍼wrapper형

  • String

  • java.util.Date

  • java.sql.Date

  • java.math.BigDecimal

  • java.math.BigInteger

IDENTITY 전략

IDENTITY는 기본 키 생성을 데이터베이스에 위임하는 전략이다.

IDENTITY 전략은 지금 설명한 AUTO INCREMENT를 사용한 예제처럼 데이터베이스에 값을 저장하고 나서야 기본 키 값을 구할 수 있을 때 사용한다.

private static void logic (EntityManager em) {
     Board board = new Board();
		 em.persist (board);
     System.out.println("board.id = " + board.getId());
}
//출력: board.id = 1

IDENTITY 전략은 데이터를 데이터베이스에 INSERT한 후에 기본 키 값을 조회할 수 있다.

엔티티가 영속 상태가 되려면 식별자가 반드시 필요하다.

그런데 IDENTITY 식별자 생성 전략은 엔티티를 데이터베이스에 저장해야 식별자를 구할 수 있으므로 em.persist()를 호출하는 즉시 INSERT SQL이 데이터베이스에 전달된다. 따라서 이 전략은 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다.

SEQUENCE 전략

@Entity
@SequenceGenerator(
    name = "BOARD_SEQ_GENERATOR",
    sequenceName = ”BOARD_SEQ”, //매핑할 데이터베이스 시퀀스 이름
    initialvalue = 1,
    allocationsize = 1
)
public class Board {
    @IdQGeneratedValue(
    strategy = GenerationType.SEQUENCE,
    generator = "BOARD_SEQ_GENERATOR")
    private Long id;
    ...
}

sequenceName 속성의 이름으로 BOARD_SEQ를 지정 했는데 JPA는 이 시퀀스 생성기를 실제 데이터베이스의 BOARD_SEQ 시퀀스와 매핑한다.

SequenceGenerator.allocationSize의 기본값이 50인 이유는 최적화 때문이다 allocationSize 값이 50이면 시퀀스를 한 번에 50 증가 시킨 다음에 1~50까지는 메모리에서 식별자를 할당한다.

이 최적화 방법은 시퀀스 값을 선점 하므로 여러 JVM이 동시에 동작 해도 기본 키 값이 충돌하지 않는 장점이 있다. 반면에, 데이터베이스에 직접 접근해서 데이터를 등록할 때 시퀀스 값이 한번에 많이 증가한다는 점을 염두해 두어야 한다.

참고로 앞서 설명한 hibernate.id.new_generator_mappings 속성을 true로 설정해야 지금까지 설명한 최적화 방법이 적용된다.

TABLE 전략

TABLE 전략은 키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략이다.

@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 Long id;
    ...
}

TABLE 전략과 최적화 TABLE 전략은 값을 조회하면서 SELECT 쿼리를 사용하고 다음 값으로 증가시키기 위해 UPDATE 쿼리를 사용한다. 이 전략은 SEQUENCE 전략과 비교해서 데이터베이스와 한번 더 통신하는 단점이 있다. TABLE 전략을 최적화하려면 TableGenerator.allocationSize를 사용하면 된다.

AUTO 전략

[GenerationType.AUTO](http://generationtype.auto/)는 선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택한다. AUTO 전략의 장점은 데이터베이스를 변경해도 코드를 수정할 필요가 없다는 것이다. AUTO를 사용할 때 SEQUENCETABLE 전략이 선택되면 시퀀스나 키 생성용 테이블을 미리 만들어 두어야 한다.

기본 키 매핑 정리

em.persist()를 호출한 직후에 발생하는 일을 식별자 할당 전략별로 정리하면 다음과 같다.

  • 직접 할당 : em.persist()를 호출하기 전에 애플리케이션에서 직접 식별자 값을 할당해야 한다. 만약 식별자 값이 없으면 예외가 발생한다.

  • SEQUENCE : 데이터베이스 시퀀스에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다.

  • TABLE : 데이터베이스 시퀀스 생성용 테이블에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다.

  • IDENTITY : 데이터베이스에 엔티티를 저장해서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다. (IDENTITY 전략은 테이블에 데이터를 저장해야 식별자 값을 획득할 수 있다.)

@Column

@Column은 객체 필드를 테이블 컬럼에 매핑한다. insertable, updatable 속성은 데이터베이스에 저장되어 있는 정보를 읽기만 하고 실수로 변경하는 것을 발지하고 싶을 때 사용한다.

@Enumerated

  • EnumType.ORDINAL은 enum에 정의된 순서대로 ADMIN은 0, USER는 1 값이 데이터베이스에 저장된다.

    • 장점 : 데이터베이스에 저장되는 데이터 크기가 작다

    • 단점 : 이미 저장된 enum의 순서를 변경할 수 없다

  • EnumType.STRING은 enum 이름 그대로 ADMIN은 ‘ADMIN’, usersms ’USER’라는 문자로 데이터베이스에 저장된다.

    • 장점 : 저장된 enum의 순서가 바뀌거나 enum이 추가되어도 안전하다

    • 단점 : 데이터베이스에 저장되는 데이터 크기가 ORDINAL에 비해서 크다

@Temporal

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

@Temporal을 생략하면 자바의 Date와 가장 유사한 timestamp로 정의된다.

@Temporal(TemporalType.DATE)
private Date date; //날짜

@Temporal(TemporalType.TIME)
private Date time; //시간

@Temporal(TemporalType.TIMESTAMP)
private Date timestamp;
//== 생성된 DDL==//
date date,
time time,
timestamp timestamp,

@Lob

@Lob에는 지정할 수 있는 속성이 없다. 대신에 매핑하는 필드 타입이 문자면 CLOB으로 매핑하고 나머지는 BLOB으로 매핑한다.

@Transient

객체에 임시로 어떤 값을 보관하고 싶을 때 사용한다.

@Transient
private Integer temp;

@Access

  • 필드 접근 : AccessType.FIELD로 지정한다. 필드에 직접 접근한다. 필드 접근 권한이 private이어도 접근할 수 있다.

  • 프로퍼티 접근 : AccessType.PROPERTY로 지정한다. 접근자(Getter)를 사용한다.

@Access를 설정하지 않으면 @Id의 위치를 기준으로 접근 방식이 설정된다.

Last updated