본문 바로가기

Dev Book Review/자바 ORM 표준 JPA 프로그래밍

Chapter4. 엔티티 매핑

JPA를 사용할 때 가장 중요한 일은 Entity와 Table을 정확히 매핑하는 것이다. 따라서 JPA가 제공하는 다양한 매핑 어노테이션을 잘 숙지하고 사용하는 게 중요하다. 지금부터 객체와 테이블, 기본 키, 필드와 컬럼, 연관관계 매핑에 대해서 설명하려고 한다.

1. @Entity

JPA를 통해 테이블과 매핑할 클래스@Entity 어노테이션을 반드시 붙여야 한다. @Entity 어노테이션이 붙은 클래스는 JPA가 관리하는 것으로서 Entity라고 부른다.

 

@Entity 어노테이션 적용 시 주의사항은 다음과 같다.

  • 기본 생성자를 필수로 가져야 한다. (파라미터가 없는 pulibc/protected 생성자)
  • final 클래스, enum, interface, inner 클래스에는 사용할 수 없다.
  • 저장할 필드에 final을 사용하면 안된다.

참고) JPA가 Entity 객체를 생성할 때 기본 생성자를 사용하기 때문에 반드시 가지고 있어야만 한다.

속성 기능 기본값
name JPA에서 사용할 Entity 이름을 지정한다. 보통 기본값인 클래스 이름을 사용한다. 만약 다른 패키지에 이름이 같은 Entity 클래스가 있다면 이름을 지정해서 충돌을 방지한다. 설정하지 않으면 클래스 이름을 그대로 사용한다. (ex. Member)

2. @Table

@TableEntity와 매핑할 테이블을 지정한다. 생략하면 매핑한 Entity 이름을 테이블 이름으로 사용한다.

속성 기능 기본값
name 매핑할 테이블 이름 Entity 이름을 그대로 사용
catalog catalog 기능이 있는 데이터베이스에서 catalog를 매핑  
schema schema 기능이 있는 데이터베이스에서 schema를 매핑  
uniqueConstraints (DDL) DDL 생성 시에 unique 제약조건을 만든다. 2개 이상의 복합 unique 제약조건을 만들 수 있다. 참고로 이 기능은 스키마 자동 생성 기능을 사용해서 DDL을 만들 때만 사용된다.  

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

JPA는 데이터베이스 스키마를 자동 생성하는 기능을 지원한다. 클래스의 매핑 정보만 보고 어떤 테이블에 어떤 컬럼을 사용하는지 파악한 뒤 데이터베이스 방언을 통해 데이터베이스 스키마를 자동 생성한다. 따라서 스키마 자동 생성 기능을 사용하면 애플리케이션 실행 시점에 데이터베이스 테이블이 자동으로 생성되므로 개발자가 테이블을 직접 생성하는 수고를 덜 수 있다. 하지만 이때 생성되는 DDL이 운영 환경에서 사용할 정도로 완벽하지는 않기 때문에 개발 환경에서 사용하거나, 참고하는 정도로만 사용하는 게 좋다.

 

@Entity
@Table(name="MEMBER")
public class Member {

  @Id
  @Column(name="ID")
  private String id;
    
  @Column(name="NAME", nullable=false, length=10)
  private String username;
}
create table MEMBER (
  ID varchar(255) not null,
  NAME varchar(10) not null

  primary key (ID)
)

또한 위처럼 스키마 자동 생성 기능을 사용했을 때 생성되는 DDL에 여러 제약조건을 추가할 수 있다.

nullable=false를 통해 NAME 컬럼에 not null 제약조건을 추가했고, length=10으로 문자의 크기가 10자리로 제한되었다.

 

@Entity
@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", nullable=false, length=10)
  private String username;
}
ALTER TABLE MEMBER
  ADD CONSTRAINT NAME_AGE_UNIQUE UNIQUE(NAME, AGE)

이번에는 @Table 어노테이션의 uniqueConstraints 속성을 활용하여 unique 제약조건을 추가해줬다. 마찬가지로 제약조건이 추가된 DDL이 자동으로 생성되었다.

 

이런 기능들은 JPA의 실행 로직에는 직접적인 영향을 주지 않는다. 따라서 스키마 자동 생성 기능을 사용하지 않는다면 사용할 이유가 없다. 사용하는 경우에는 애플리케이션 개발자가 Entity만 보고도 쉽게 다양한 제약조건을 파악할 수 있다는 장점이 있다.

4. 기본 키 매핑

JPA가 제공하는 데이터베이스 기본 키 생성 전략은 다음과 같다.

  • 직접 할당 : 기본 키를 애플리케이션에서 직접 할당한다.
  • 자동 생성 : 대리 키 사용 방식 (IDENTITY, SEQUENCE, TABLE)

1) 직접 할당 전략

@Id
@Column(name="id")
private String id;

기본 키를 직접 할당하기 위해서는 다음과 같이 @Id로 매핑하면 된다.

직접 할당 전략에서 식별자 값 없이 저장하려고 하면 예외가 발생하게 된다. 따라서 em.persist()로 Entity를 저장하기 전에 애플리케이션에서 기본 키를 직접 할당해주어야 한다.

 

2) IDENTITY 전략

@Entity
public class Board {

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

IDENTITY는 기본 키 생성을 데이터베이스에 위임하는 전략이다. 예를 들어 MySQL의 AUTO_INCREMENT 기능과 같다.

 

IDENTITY 전략은 데이터를 데이터베이스에 INSERT 하고 나서 기본 키 값을 조회할 수 있다. 이때 em.persist()를 통해 영속성 컨텍스트에 Entity를 넣기 위해서는 반드시 식별자가 필요하기 때문에 em.persist() 호출 즉시 데이터베이스에 INSERT 쿼리가 날아간다. 즉, Entity에 식별자 값을 할당하기 위하여 JPA는 데이터베이스를 추가로 조회하게 되는 것이다. 또한 IDENTITY 전략은 트랜잭션을 지원하는 쓰기 지연도 동작하지 않게 된다.

 

3) SEQUENCE 전략

@Entity
@SequenceGenerator(
  name="BOARD_SEQ_GENERATOR",
  sequenceName="BOARD_SEQ",
  initialValue=1,
  allocationSize=1)
public class Board {

  @Id
  @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="BOARD_SEQ_GENERATOR")
  private Long id
}
CREATE TABLE BOARD(
  ID BIGINT NOT NULL PRIMARY KEY,
  DATA VARCHAR(255)
)

CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1;

데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트이다. SEQUENCE 전략은 이러한 시퀀스를 사용해서 기본 키를 생성한다.

 

테이블 별로 시퀀스를 따로 관리하기 위해서 @SequenceGenerator 어노테이션을 통해 시퀀스 생성기를 등록한다. 이때 시퀀스 생성기는 sequenceName 속성에 지정된 값("BOARD_SEQ")에 해당하는 데이터베이스 시퀀스와 매핑된다.

이후 @GeneratedValue 어노테이션의 generator 속성을 통해 앞서 등록한 시퀀스 생성기를 선택하면 이제 id 값은 BOARD_SEQ 시퀀스에서 할당한다.

 

em.persist()를 호출하면 데이터베이스 시퀀스를 사용하여 식별자 값을 조회한다. 그리고 조회한 식별자 값을 Entity에 할당하고 영속성 컨텍스트에 저장한다. 이후 트랜잭션을 커밋해서 flush가 되면 해당 Entity를 데이터베이스 저장한다.

 

속성 기능 기본값
name 식별자 생성기 이름 필수
sequenceName 데이터베이스에 등록되어 있는 시퀀스 이름 hibernate_sequence
initialValue DDL 생성 시에만 사용. 처음 시작하는 수를 지정. 1
allocationSize 시퀀스 한 번 호출에 증가하는 수 (성능 최적화에 사용) 50
catalog, schema 데이터베이스 catalog, schema 이름  

 이때 allocationSize의 기본값이 50인 것에 주의할 필요가 있다.

 

SEQUENCE 전략은 기본적으로 데이터베이스 시퀀스를 통해 식별자를 조회하는 추가 작업이 필요하다. 만약 allocationSize가 1이라면 기본 키를 할당할 때마다 매번 데이터베이스와 통신해야한다. 따라서 JPA는 데이터베이스 시퀀스에 접근하는 비용을 줄이기 위해 allocationSize 기본값을 50으로 설정했다. 따라서 데이터베이스 시퀀스에서 값을 가져올 때 한번에 50개를 가져온다. 그래서 1~50까지는 데이터베이스 시퀀스가 아닌, 메모리에서 식별자를 할당하게 된다. 이후 51이 되면 다시 데이터베이스 시퀀스에서 50개의 값을 가져온다. 따라서 데이터베이스와 통신하게 되는 비용을 줄여 성능 최적화를 할 수 있다.

 

또한 이러한 방법은 여러 JVM이 동시에 동작해도 기본 키 값이 충돌하지 않는다는 장점이 있다. 만약 JVM-1, JVM-2가 동시에 데이터베이스 시퀀스에 접근하더라도 각각은 1~50, 51~100까지의 식별자 값을 사용하기 때문이다.

하지만 데이터베이스에 직접 접근해서 데이터를 등록할 때 시퀀스 값이 한번에 많이 증가한다는 점이 부담스럽거나 INSERT 성능이 크게 중요하지 않으면 allocationSize를 1로 설정할 수도 있다.

 

4) 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;
}
create table MY_SEQUENCES (
  sequence_name varchar(255) not null,
  next_val bigint,
  primary key (sequence_name)
)

TABLE 전략은 키 생성 전용 테이블을 하나 만들고 여기에 이름으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내낸다. 테이블을 사용하기 때문에 모든 데이터베이스에 적용할 수 있다.

 

위 코드에서는 @TableGenerator 어노테이션을 사용하여 테이블 키 생성기("BOARD_SEQ_GENERATOR")를 등록하면서 MY_SEQUENCE 테이블을 키 생성용 테이블로 매핑한다. 그리고 @GeneratedValue 어노테이션의 generator 속성에 방금 만든 테이블 키 생성기("BOARD_SEQ_GENERATOR")를 지정했다. 이제 id 식별자 값은 BOARD_SEQ_GENERAOTR 테이블 키 생성기가 할당한다.

 

이때 MY_SEQUENCE 테이블에는 BOARD_SEQ가 컬럼명으로 추가되었다. 이제 키 생성기를 사용할 때마다 BOARD_SEQ에 대한 next_val 값이 증가한다. (MY_SEQUENCE 테이블에는 여러 Entity에 대한 시퀀스 값이 시퀀스 컬럼-시퀀스 값 컬럼의 형태로 존재할 수 있다)

 

속성 기능 기본값
name 식별자 생성기 이름 필수
table 키 생성 테이블명 hibernate_sequences
pkColumnName 시퀀스 컬럼명 sequence_name
valueColumnName 시퀀스 값 컬럼명 next_val
pkColumnValue 키로 사용할 값 이름 엔티티 이름
initialValue 초기 값. 마지막으로 생성된 값 기준 0
allocationSize 시퀀스 한 번 호출에 증가하는 수 (성능 최적화에 사용) 50
catalog, schema 데이터베이스 catalog, schema 이름  
uniqueConstraints(DDL) 유니크 제약 조건을 지정할 수 있다.  

 

5) AUTO 전략 (default)

@Entity
public class Board {
  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  private Long id;
}

AUTO 전략은 선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택한다.

5. 필드와 컬럼 매핑 : 레퍼런스

분류 매핑 어노테이션 설명
필드와 컬럼 매핑 @Column 컬럼을 매핑한다.
@Enumerated 자바의 enum 타입을 매핑한다.
@Temporal 날짜 타입을 매핑한다.
@Lob BLOB, CLOB 타입을 매핑한다.
@Transient 특정 필드를 데이터베이스에 매핑하지 않는다.
기타 @Access JPA가 엔티티에 접근하는 방식을 지정한다.

** 필요한 매핑을 사용할 일이 있을 때 각각 자세히 알아보고 사용하는 걸 권장한다.