본문 바로가기
프로그래밍/JPA

04장. Entity Mapping

by 직장인 투자자 2022. 1. 18.

04장. Entity Mapping

개요

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

따라서 매핑 어노테이션을 숙지하고 사용해야 한다. JPA는 다양한 매핑 어노테이션을 지원하는데 크게 4가지로 분류할 수 있다.

 

 

  • 객체와 테이블 매핑: @Entity, @Table
  • 기본 키 매핑 : @Id
  • 필드와 칼럼 매핑 : @Column
  • 연관관계 매핑 : @ManyToOne, @JoinColumn

4장에서는 객체와 테이블 매핑, 기본 키 매핑, 필드와 칼럼 매핑에 대해 알아보고 연관관계 매핑은 5, 6, 7장에 걸쳐서 설명한다.

매핑 정보는 XML이나 어노테이션 중에 선택해서 기술하면 되는데 책에서는 어노테이션만 사용하겠다. 각각 장단점이 있지만 어노테이션을 사용하는 쪽이 점 더 쉽고 직관적이다. XML을 사용해서 매핑 정보를 구성하는 방법은 이 책에서 설명하지 않는다.

@Entity 어노테이션

JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 어노테이션을 필수로 붙여야 한다. @Entity 가 붙은 클래스는 JPA가 관리하는 것으로, 엔티티라 부른다.

@Entity 적용 시 주의사항

  • 기본 생성자는 필수다. (파라미터가 없는 public 또는 protected 생성자)
  • final 클래스, enum interface, inner 클래스에는 사용할 수 없다.
  • 저장할 필드에 final을 사용하면 안 된다.
    : @Column으로 매핑할 Class 이 멤버 변수.

JPA 가 엔티티 객체를 생성할 때 기본 생성자를 사용하므로 이 생성자는 반드시 있어야 한다. 자바는 생성자가 하나도 없으면 다음과 같은 기본 생성자를 자동으로 만든다.

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

문제는 다음과 같이 생성자를 하나 이상 만들면 자바는 기본 생성자를 자동으로 만들지 않는다. 이때는 기본 생성자를 직접 만들어야 한다. 

public Member() {}    // 직접 만든 기본 생성자

// 임의로 만든 생성자
public Member(String name) {
    this.name = name;
}

@Table 어노테이션

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

다양한 매핑 사용

  1. 회원은 일반 회원과 관지라로 구분해야 한다.
  2. 회원 가입일과 수정일이 있어야 한다.
  3. 회원을 설명할 수 있는 필드가 있어야 한다. (길이 제한이 없음)

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

JPA는 데이터베이스 스키마를 자동으로 생성하는 기능을 지원한다.

매핑 정보를 사용해 테이블 및 칼럼 정보를 알 수 있고, 데이터베이스 방언(Dialect)을 함께 사용하여 스키마 자동 생성이 가능하다.

  • hibernate.hbm2 ddl.auto를 'create'으로 추가하면 애플리케이션 수행 시 테이블을 자동으로 추가한다.
  • hibernate.show_sql을 'true'로 설정하는 경우 수행되는 DDL 이 console에 출력된다.

DDL 생성 기능

스키마 자동생성 시 만들어지는 DDL에 제약조건을 추가할 수 있다.

  1. notnull 제약조건을 넣기 위해 nullable = false 조건 추가.
  2. @Column(name = "NAME", nullable = false)
  3. 자동생성 필드의 크기를 지정하기 위해 length 속성 사용.
  4. @Column(name = "NAME", length = 10)
  5. 칼럼에 unique constraint 생성.
  6. @Table(name="MEMBER", uniqueConstraints = { @UniqueConstraint(name = "NAME_AGE_UNIQUE", columnNames = {"NAME", "AGE"}) })

나열된 기능들은 단지 DDL을 자동으로 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다.

기본 키 매핑

JPA에서 기본키(Primary Key)를 매핑하는 방법.

@Entity
public class Member {
    @Id
    @Column(name = "ID")
    private String id;
      ...
}

지금까지 나왔던 예제는 @Id 어노테이션 만을 사용하여 기본키에 대해 직접 설정하였으나, 대부분의 경우 Oracle의 'Sequence' 나 MySql의 'AUTO_INCREMENT' 등을 사용하여 DB의 값을 자동으로 생성할 수 있다.

이와 같이 '직접 할당'/'자동생성'과 같은 방법을 JPA의 기본키 생성 전략이라 하며, 다음과 같은 전략을 사용할 수 있다.

  • 직접 할당 : 기본키를 애플리케이션에서 직접 할당. (@Id 어노테이션만 사용.)
  • 자동 생성 : 대리 키 사용 방식. (@GeneratedValue 어노테이션 함께 사용.)
    • IDENTITY: 기본 키 생성을 데이터베이스에 위임한다. (ex. MySql의 AUTO_INCREMENT)
    • SEQUENCE: 데이터베이스 시퀀스를 사용해서 기본 키를 할당한다. (ex. Oracle의 Sequence)
    • TABLE: 키 생성 테이블을 사용한다. (DB에 제약 없이 별도 Table 활용하여 생성)

기본 키 직접 할당 전략

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

@Id 적용 가능 자바 타입

  • 자바 기본형
  • 자바 래퍼(Wrapper) 형
  • String
  • java.util.Date
  • java.sql.Date
  • java.math.BigDecimal
  • java.math.BigInteger

em.persist()로 엔티티를 저장하기 전에 애플리케이션에서 기본키 직접 할당

기본 키 직접 할당을 사용함에 있어 식별자 값 없이 저장하면 예외가 발생한다.

어떤 예외가 발생하는지 JPA 표준에 정의되어 있지 않으며, 하이버네이트일 때 실제 발생하는 예외는 다음처럼 JPA 최상위 예외인 javax.persistence.PersistenceException이며 내부에 org.hibernate.id.identifierGenerationException 이 담겨 있음.

IDENTITY 전략

키 생성을 데이터베이스에 위임.

지원하는 DB : MySQL, PostgreSQL, SQL Server, DB2

기본적으로 DB에 저장한 이후 id값을 알 수 있기 때문에 id를 조회하기 위해 DB를 추가로 조회한다.

@Entity
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 전략지정.
    private Long id;
      ...
}

SEQUENCE 전략

유일 값을 순서대로 생성하는 Sequence를 사용하여 기본키를 생성한다.

지원하는 DB : Oracle, PostgreSQL, DB2, H2

@Entity
@SequenceGenerator(    // SequenceGenerator 의 위치는 PK 필드 멤버변수에서도 사용가능함.
      name = "BOARD_SEQ_GENERATOR",
      sequenceName = "BOARD_SEQ", // 매핑할 실제 DB Sequence name
      initialValue = 1, allocationSize = 1)
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                  generator = "BOARD_SEQ_GENERATOR") // 상단에 설정한 SequenceGenerator 의 name
    private Long id;
    ...
}

IDENTITY과 다른 점은 em.persist() 시점에 Sequence를 Generator 해서 persist context에 저장하고, 실제 INSERT SQL 은 flush 시점에 발생한다.

@SequenceGenerator 어노테이션

매핑할 DDL을 속성과 매칭 하면 다음과 같다.

create sequence [sequenceName]
start with [initialValue] increment by [allocationSize]

TABLE 전략

키를 생성하는 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 칼럼을 만들어 Sequence 전략을 흉내 내는 방법.

Sequence를 지원하지 않는 여타 DB에서도 사용할 수 있다.

@Entity
@TableGenerator(
      name = "BOARD_SEQ_GENERATOR",
      table = "MY_SEQUENCES", // Key Genration 을 담당하는 Table 명.
      pkColumnName = "sequence_name",    // sequence column명. 기본값(sequence_name)
      valueColumnName = "next_val", // sequence value명. 기본값(next_val)
      pkColumnValue = "BOARD_SEQ", allocationSize = 1)
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,
                 generator = "BOARD_SEQ_GENERATOR") // 상단에 설정한 TableGenerator 의 name
    private Long id;
    ...
}

@TableGenerator 어노테이션

TABLE 전략과 최적화

TABLE 전략은 SEQUENCE 전략에 비해 데이터베이스 통신을 1회 더 증가시킨다.(키 생성 테이블에 대해 SELECT & UPDATE 수행.)

AUTO 전략

Database Dialect에 따라 IDENTITY, SEQUENCE, TABLE 중 하나를 자동으로 선택.

@GenratedValue. strategy의 기본값은 AUTO 임. 따라서 아래의 두 코드는 완전히 동일함.

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

 

 

기본 키 매핑 정리

영속성 콘텍스트는 엔티티를 식별자 값으로 구분하므로 엔티티를 영속 상태로 만들기 위해서는 식별자 값이 반드시 필요.

em.persist() 호출 직후 발생하는 일을 식별자 할당 전략별로 정리하면 아래와 같음.

  • 직접 할당 : em.persist()를 호출하기 전에 애플리케이션에서 직접 식별자 값을 할당해야 한다. 만약 식별자 값이 없으면 예외가 발생함.
  • SEQUENCE : 데이터베이스 시퀀스에서 식별자 값을 획득한 후 영속성 콘텍스트에 저장한다.
  • TABLE : 데이터베이스 시퀀스 생성용 테이블에서 식별자 값을 획득한 후 영속성 콘텍스트에 저장한다.
  • IDENTITY: 데이터베이스에 엔티티를 저장해서 식별자 값을 획득한 후 영속성 콘텍스트에 저장한다.(IDENTITY 전략은 테이블에 데이터를 저장해야 식별자 값을 획득할 수 있다.)

필드와 칼럼 매핑: 레퍼런스

@Column 어노테이션

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

생략할 경우 @Column의 기본값이 적용된다.

다만 필드가 자바 기본형(primitive type) 일 경우 자동생성 일 때 칼럼의 nullable 이 false로 설정되므로 이에 대해 주의해야 한다.

@Enumerated 어노테이션

자바의 enum 타입 매핑.

  • EnumType.ORDINAL : enum class에 정의된 순서대로 0부터 숫자를 통해 저장.
    • 장점 : 저장되는 데이터 크기가 작다.
    • 단점 : 이미 정의된 enum의 순서를 변경할 수 없음.
  • EnumType.STRING : enum class에 정의된 이름을 그대로 문자열로 저장.
    • 장점 : 이미 정의된 enum의 순서가 바뀌거나 enum 이 추가되어도 안전하다.
    • 단점 : 저장되는 데이터 크기가 크다.

@Temporal 어노테이션

날짜 타입(java.util.Date, java.util.Calendar)을 매핑.

자바의 Date 타입에는 연월일 시분 초가 모두 있지만, 데이터베이스에는 date(날짜), time(시간), timestamp(날짜와 시간)라는 세 가지 타입이 별도로 존재한다.

날짜 타입에 Temporal을 생략할 경우 자바의 Date와 가장 유사한 timestamp로 정의된다.

timestamp와 같은 의미를 가지는 datetime 예약어를 사용하는 DB(MySQL)도 존재함.

@Lob 어노테이션

BLOB, CLOB 타입과 매핑.

@LOB 에는 지정 가능한 속성이 별도로 존재하지 않고, 매핑 필드의 타입이 문자일 때 CLOB으로 나머지는 BLOB으로 매핑.

@Transient

이 필드는 매핑하지 않는다. 실제 DB 와의 연관은 없이 임시 데이터를 담는 용도 등에 사용할 필드에 사용.

@Access

JPA가 엔티티 데이터에 접근하는 방식을 지정한다.

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

다음과 같이 사용할 경우 필드/프로퍼티 접근방식을 혼용할 수 있다.

@Entity
@Access(AccessType.FIELD)
public class Member {

    @Id
    private String id;

    @Transient
    private String firstName;

    @Transient
    private String lastName;

    @Access(AccessType.PROPERTY)
    public String getFullName() {
        return firstName + lastName;
    }
}
반응형

'프로그래밍 > JPA' 카테고리의 다른 글

05장. 연관관계 매핑기초  (4) 2022.01.22
03장. 영속성 관리  (2) 2022.01.14
02. JPA 시작  (10) 2022.01.11
01. JPA 소개  (4) 2022.01.08

댓글