엔티티 매핑

Posted by yunki kim on July 26, 2022

JPA가 지원하는 매핑 어노테이션은 다음과 같이 4가지로 분류할 수 있다.
1. 객체와 테이블 매핑: @Entity, @Table
2. 기본 키 매핑: @Id
3. 필드와 컬럼 매핑: @Column
4. 연관관계 매핑: @ManyToOne, @JoinColumn

@Entity

JPA를 사용해 테이블과 클래스를 매핑할 때 사용한다. @Entity 속성은 다음과 같다.

속성 기능 기본값
name JPA에서 사용할 엔티티 이름을 지정한다. 보통은 디폴드값인 클래스 이름을 사용한다. 다른 패키지에 이름이 같은 엔티티 클래스가 있으면 이 속성으로 이름을 지정해 충돌을 피할 수 있다. 클래스 이름

@Entity를 적용하기 위해선 다음과 같은 점을 따라야 한다.
1. 기본 생성자가 존재해야 한다. 접근자는 public 또는 protected여야 한다.
2. final class, enum, interface, inner class 에는 사용할 수 없다.
3. 저장할 필드에 final을 사용하면 안된다.

@Table

엔티티와 매핑할 테이블을 지정한다. @Table의 속성은 다음과 같다.

속성 기능 기본값
name 매핑할 테이블 이름 엔티티 이름을 사용한다
catalog catalog 기능이 있는 DB에서 catalog를 매핑한다.  
schema schema 기능이 있는 DB에서 schema를 매핑한다.  
uniqueConstraints DDL 생성 시에 유니크 제약조건을 만든다. 2개 이상의 복합 유니크 제약조건도 만들 수 있다. 이 기능은 스키마 자동 생성 기능을 사용해서 DDL을 만들 때만 사용된다.  

DB 스키마 자동 생성

JPA는 DB 스키마를 자동으로 생성하는 기능을 제공한다. JPA는 매핑 정보와 DB 방언을 사용해 스키마를 생성한다. 스카마 자동 생성 긴능을 사용하기 위해선 application.yml에 다음과 같은 속성을 추가하면 된다.

1
2
3
4
5
spring:
  jpa:
    hibernate:
      ddl-auto: create-drop # 스키마 자동 생성
    show-sql: true # 테이블 생성 DDL(Data Definition Language) 
cs

스키마 자동 생성 기능을 사용하면 개발자가 테이블을 직접 만드는 수고를 덜 수 있다 하지만, 스키마 자동 생성 기능이 만든 DDL은 운영 환경에서 사용할 만큼 완벽하지 않으므로 개발 환경 또는 참고용으로만 사용해야 한다.
spring.jpa.hibernate.ddl-auto의 속성은 다음과 같다.

옵션 설명
create 기존 테이블을 삭제하고 새로 생성한다. DROP + CREATE
create-drop create 속성에 추가로 애플리케이션 종료 시 생성한 DDL을 제거한다. DROP + CREATE + DROP
update DB 테이블과 엔티티 매핑 정보를 비교해 변경 사항만 수정한다.
validate DB 테이블과 엔티티 매핑 정보를 비교해 차이가 있으면 경고를 남기고 애플리케이션을 실행하지 않는다.
none 자동 생성 기능을 사용하지 않는다.

기본 키 매핑

 DB에서 생성해주는 값을 PK로 사용할 때 DB마다 PK를 생성하는 방식이 서로 다르다는 문제가 존재한다. JPA는 이 문제를 해결하기 위해 다음과 같은 PK 생성 전략을 가진다.
1. 직접 할당: PK를 애플리케이션에서 직접 할당한다.
2. 자동 생성: 대리 키 사용 방식
- IDENTITY: PK 생성을 DB에 위임한다.
- SEQUENCE: DB sequence를 사용해 PK를 할당한다.
- TABLE: 키 생성용 테이블을 만들도 sequence 처럼 사용한다.
자동 생성 전략이 다양한 이유는 DB 벤더마다 지원하는 방식이 다르기 때문이다.
기본키를 직접 할당하기 위해선 @Id만 사용하면 되고, 자동 생성 전략을 사용하기 위해선 @GeneratedValue를 추가로 사용하면 된다.

기본 키 직접 할당 전략

직접 PK를 할당하기 위해선 @Id 어노테이션을 사용하면 된다. 이 때, @Id 를 적용할 수 있는 타입은 다음과 같다.
1. primitive type
2. primitive type wrapper class
3. String
4. java.util.Date
5. java.sql.Date
6. java.math.BigDecimal
7. java.math.BigInteger
기본 키 직접 할당 전략은 em.persist()로 엔티티를 저장하기 전에 애플리케이션에서 기본 키를 직접 할당하는 방법이다.

1
2
3
4
Board board = new Board();
board.setId("id1"); // PK 직접 할당
em.persist(board);
 
cs

IDENTITY 전략

IDENTITY 전략은 mysql의 AUTO_INCREMENT 처럼 DB에 값을 저장해야 PK를 구할 수 있을 때 사용한다. 주로 MySql, PostgreSQL, SQL server, DB2 에서 사용한다. 이 전략을 사용하면 JPA는 PK를 구하기 위해 추가로 DB를 조회한다. 이를 바꿔말하면 DB에 entity를 저장해야 PK를 구할수 있다는 소리가 된다. 따라서 em.persist()를 호출하는 즉시 INSERT SQL이 DB에 전달되기 때문에 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다. IDENTITY 전략을 사용하는 방법은 다음과 같다.

1
@GeneratedValue(strategy = GenerationValue.IDENTITY)
cs

IDENTITY 전략과 최적화

IDENTITY 전략에서 JPA는 PK를 얻기 위해 추가로 DB를 조회한다. 하지만 하이버네이트는 JDBC3에서 추가된 Statement.getGeneratedKeys()를 사용해 데이터를 저장하면서 동시에 생성된 PK를 얻어온다.

AUTO 전략

선택한 DB 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택한다. 이때문에 DB를 변경해도 코드를 수정할 필요가 없다는 장점이 있다. AUTO를 사용해서 SEQUENCE나 TABLE 전략이 선택되면 시퀀스나 키 생성용 테이블을 미리 만들어 두어야 한다. 하지만 스키마 자동 생성 기능을 사용하면 하이버네이트가 기본값을 사용해 적절한 시퀀스나 키 생성용 테이블을 만들어준다.

권장하는 식별자 선택 전략

DB PK는 다음 조건들을 만족해야 한다.
1. null 값을 허용하지 않는다.
2. 유일해야 한다.
3. 변해선 안된다.
테이블의 PK를 선택하는 전략은 다음과 같다.
1. 자연 키(natural key)
- 비즈니스에서 의미가 있는 키(ex - 이메일)
2. 대리 키(surrogate key)
- 비즈니스와 관련 없는 임의로 만들어진 키, 대체 키 (ex - auto_increment)

자연 키보다는 대리 키를 권장한다.

자연키를 사용하면 일부 사용자의 경우 자연키로 사용되는 값이 존재하지 않는 경우가 발생할 수 있다. 예를 들어 전화번호를 자연키로 사용하면, 전화번호가 없는 사용자를 대응할 수 없다. 또 한, 모든 사람이 있는 값을 자연키로 사용한다 해도, 언제 비즈니스 환경이 변할지 모른다.

필드과 컬럼 매핑: 레퍼런스

필드와 컬럼 매핑 분류

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

@Column

객체 필드를 테이블 컬럼에 매핑한다.

속성 기능 기본값
name 필드와 매핑할 테이블의 컬럼 이름 객체의 필드 이름
insertable false로 지정 시 SQL insert 문에 해당 컬럼을 포함시키지 않는다 true
updatable false로 지정하면 SQL update 문에 해당 컬럼을 포함시키지 않는다 true
columnDefinition 해당 컬럼에 대한 정의를 직접 내릴 수 있다 필드의 자바 타입과 방언 저올르 사용해 적절한 컬럼 타입을 생성한다.
table 하나의 두 개 이상의 테이블에 매핑할 때 사용한다. 지정한 필드를 다른 테이블에 매핑할 수 있다. 현재 클래스가 매핑된 테이블
nullable null 허용 여부 true
length 문자 길이 제약 조건, String 타입에만 사용된다. 255
precision, scale BigDecimal 타입에서 사용한다. precision은 소수점을 포함한 전체 자릿수, scale은 소수의 자릿수다. double, float에는 적용되지 않는다. precision = 19, scale = 2

@Column 어노테이션을 생략해도 필드를 컬럼으로 매핑할 수 있다. 다만, 필드의 타입이 primtive type이라면 not null 옵션이 자동으로 추가된다. 따라서 primitive type에 @Column을 사용해야 한다면 nullable=false 속성을 추가하자.

@Enumerated

자바의 enum 타입을 매핑할 때 사용한다.

속성 기능
value EnumType.ORDINAL: enum 순서를 DB에 저장
- 장점: DB에 저장되는 데이터 크기가 작다
- 이미 저장된 enum의 순서를 변경할 수 없다
EnumType.STRING: enum 이름을 DB에 저장
- 장점: 저장된 enum의 순서가 바뀌거나 enum이 추가되어도 안전하다
- 단점: DB에 저장되는 데이터 크기가 ORDINAL에 비해 크다

@Temporal

날짜 타입(java.util.Date, java.util.Calendar)을 매핑할 떄 사용한다. @Temporal이 제공하는 속성은 다음과 같다.

속성 기능 기본값
value TemporalType.DATE: 날짜, DB date 타입과 매핑
TemporalType.TIME: 시간, DB time 타입과 매핑
TemporalType.TIMESTAMP: 날짜와 시간, DB timestamp 타입과 매핑
TemporalType은 필수로 지정해야 한다.

  @Temporal을 생략하면 자바의 Date와 가장 유사항 timestamp로 정의된다. timestamp 대신 datetime을 사용하는 DB의 경우 방언 덕분에 애플리케이션 코드는 변경하지 않아도 된다. MySql의 경우 datetime으로 정의되며 H2, Oracle, PostgreSQL은 timestamp로 정의된다.

@Lob

  DB BLOB, CLOB 타입과 매핑한다. @Lob은 지정할 수 있는 속성이 없다. 하지만 다음과 깥이 필드 타입에 따라 지정되는 lob이 다르다.

    CLOB: String, char[], java.sql.CLOB

    BLOB: byte[], java.sql.BLOB

@Transient

  해당 어노테이션이 달린 필드는 컬럼으로 매핑하지 않는다.

@Access

  JPA가 엔티티 데이터에 접근할 방식을 선택한다.

    - 필드 접근: AccessType.FIELD: 필드에 직접 접근한다. 접근 제어자가 private 이여도 접근할 수 있다.

    - 프로퍼티 접근: AccessType.PROPERTY로 지정한다. getter를 사용한다.

 

출처 - 자바 ORM 표준 JPA 프로그래밍