자바 ORM 표준 JPA 프로그래밍 - (4) 엔티티 매핑

 Date: 2022-02-17

✓︎ @Entity
✓︎ @Table
✓︎ 데이터베이스 스키마 자동 생성
✓︎ DDL 생성 기능
✓︎ 기본 키 매핑
✓︎ 필드와 컬럼 매핑: 레퍼런스

1. @Entity

  • JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 어노테이션을 붙여야 한다.
  • 주의사항
    • 기본 생성자는 필수로 있어야 한다.
    • final 클래스, enum, interface, inner 클래스에는 사용할 수 없다.
    • 저장할 필드에 final을 사용하면 안 된다.

2. @Table

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

3. 다양한 매핑 사용

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

  • JPA는 데이터베이스 스키마를 자동으로 생성하는 기능을 지원한다.
  • persistence.xml 에 속성을 추가하여 create, create-drop, update, validate, none 으로 스키마 생성을 설정할 수 있다.

    <property name="hibernate.show_sql.auto" value="create">
    

운영 서버에서는 create, create-drop, update 처럼 DLL 을 수정하는 옵션은 절대 사용하면 안된다. 이 옵션들은 운영중인 데이터베이스의 테이블이나 컬럼을 삭제할 수 있다.

5. DDL 생성 기능

  • DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고, JPA의 실행 로직에는 영향을 주지 않는다.
  • DDL 생성 기능 사용 예시
    • @Columnnullable 속성으로 not null 제약조건을 추가할 수 있다.
    • @Columnlength 속성으로 문자의 크기를 지정할 수 있다.

        @Column(name = "NAME", nullable = false, length = 10)
        private String userName;
      
    • @Table@UniqueConstraint 속성으로 유니크 제약조건을 추가할 수 있다.

        @Table(name = "MEMBER", uniqueConstraints = {@UniqueConstraint(
                name = "NAME_AGE_UNIQUE",
                columnNames = {"NAME", "AGE"})})
      
  • 이런 기능을 사용하면 애플리케이션 개발자가 엔티티만 보고도 손쉽게 다양한 제약 조건을 파악할 수 있는 장점이 있다.

6. 기본 키 매핑

  • 기본키를 애플리케이션에서 직접 할당하는 대신에 데이터베이스가 생성해주는 값을 사용할 수도 있다.
  • JPA가 제공하는 데이터베이스 기본 키 생성 전략은 다음과 같다.
    • 직접 할당 : 기본 키 생성을 데이터베이스에 위임한다.
    • 자동 생성
    • IDENTITY : 기본 키 생성을 데이터베이스에 위임한다.
    • SEQUENCE : 데이터베이스 시퀀스를 사용해서 기본 키를 할당한다.
    • TABLE : 키 생성 테이블을 사용한다.
  • 자동 생성 전략이 다양한 이유는 데이터베이스 벤더마다 지원하는 방식이 다르기 때문이다.
  • 키 생성 전략을 사용하려면 persistence.xml에 설정을 추가해야 한다.

      <property name="hibernate.id.new_generator_mappings" value="true">
    

1) 기본키 직접 할당 전략

  • 기본 키를 직접 할당하려면 @Id 로 매핑하면 된다.
  • em.persist() 로 엔티티를 저장하기 전에 애플리케이션에서 기본 키를 직접 할당하는 방법이다.

2) IDENTITY 전략

  • IDENTITY는 기본 키 생성을 데이터베이스에 위임하는 전략이다.
  • @GeneratedValue 어노테이션의 strategy 속성을 GenerationType.IDENTITY 로 설정한다.

      @Entity
      public class Board {
          @Id
          @GeneratedValue(strategy = GenerationType.IDENTITY)
          private Long id;
      }
    
  • IDENTITY 전략은 데이터베이스에 값을 저장하고 나서야 기본 키 값을 구할 수 있을 때 사용한다.
    • IDENTITY 전략은 데이터를 데이터베이스에 INSERT 한 후에 기본 키 값을 조회할 수 있다.
    • JDBC3에 추가된 Statement.getGeneratedKeys()를 사용하면 데이터를 저장하면서 동시에 생성된 기본 키 값도 얻어 올 수 있다. 하이버네이트는 이 메소드를 사용해서 데이터베이스와 한 번만 통신한다.
  • 엔티티가 영속 상태가 되려면 식별자가 반드시 필요하다.
    • 따라서 이 전략은 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다.

3) SEQUENCE 전략

  • SEQUENCE 전략은 시퀀스를 사용해서 기본 키를 생성한다.
  • @SequenceGenerator를 사용해서 시퀀스 생성기를 등록한다.
  • 시퀀스 생성기를 실제 데이터베이스의 시퀀스와 매핑한다.
  • @GeneratedValue 어노테이션의 strategy 속성을 GenerationType.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;
      }
    
  • SEQUENCE 전략은 em.persist()를 호출할 때 먼저 데이터베이스의 시퀀스를 사용해서 식별자를 조회한다.
  • @SequenceGenerator 속성

    • SequenceGenerator.allocationSize 의 기본 값이 50인 것에 주의해야 한다. 데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값을 반드시 1로 설정해야 한다.

4) TABLE 전략

  • TABLE 전략은 키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략이다.
  • @TableGenerator를 사용해서 테이블 키 생성기를 등록한다.
  • 테이블 생성기를 실제 데이터베이스의 테이블과 매핑한다.
  • @GeneratedValue 어노테이션의 strategy 속성을 GenerationType.TABLE 로 설정한다.

    @Entity
    @TableGenerator(
        name = "BOARD_SEQ_GENERATOR",
        table = "MY_SEQUENCE",
        pkColumnValue = "BOARD_SEQ", allocationSize = 1)
    public class Board {
        @Id
        @GeneratedValue(strategy = GenerationType.TABLE,
                                        generator = "BOARD_SEQ_GENERATOR")
        private Long id;
    }
    
  • TABLE 생성 전략은 시퀀스 대신에 테이블을 사용한다는 것만 제외하면 SEQUENCE 전략과 내부 동작 방식이 같다.
  • @TableGenerator 속성

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

5) AUTO 전략

  • AUTO 전략은 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택한다.
    • 예를 들어, 오라클을 선택하면 SEQUENCE, MySQL을 선택하면 IDENTITY 전략을 자동으로 선택한다.
  • @GeneratedValue 어노테이션의 strategy 속성을 GenerationType.AUTO 로 설정한다.
  • 키 생성 전략이 아직 확정되지 않은 개발 초기 단계나 프로토타입 개발 시 편리하게 사용할 수 있다.
  • AUTO를 사용할 때 SEQUENCE 나 TABLE 전략이 선택되면 키 생성용 테이블을 미리 만들어 두어야 한다. 만약 스키마 자동 생성 기능을 사용한다면 하이버네이트가 기본값을 사용해서 적절한 시퀀스나 키 생성용 테이블을 만들어 줄 것이다.

JPA는 모든 엔티티에 일관된 방식으로 대리 키 사용을 권장한다.

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

@Column

  • @Column은 객체 필드를 테이블 컬럼에 매핑한다.
  • name, nullable 이 주로 사용된다.
  • nullable 의 기본값이 true 이므로, 자바 기본 타입 사용시 @Column을 사용할 경우 nullable = false로 지정해 주어야 한다. 또는, @Column을 생략하면 자바 기본 타입일 경우 자동으로 not null 로 생성된다.

      int data1; //@Column 생략, 자바 기본타입
      /* 생성된 DDL : data1 integer not null */
        
      Integer data2; //@Column 생략, 객체 타입
      /* 생성된 DDL : data2 integer */
        
      @Column
      int data3; //@Column 사용, 자바 기본타입
      /* 생성된 DDL : data3 integer */
    

    @Colum(
        nullable = false, 
        unique = true,
        columnDefinition = "varchar(100) default 'EMPTY'",
        length = 100
    )
    private String registrationNumber;
    

@Enumerated

  • 자바의 enum 타입을 매핑할 대 사용한다.
  • EnumType.ORDINAL은 enum 에 정의된 순서대로 ADMIN 은 0, USER 는 1 값이 데이터베이스에 저장된다.
    • 장점 : 저장되는 데이터 크기가 작아진다.
    • 단점 : 이미 저장이 되었다면 enum 의 순서를 변경하면 안된다.
  • EnumType.STRING 은 enum 이름 그래도 ADMIN은 ‘ADMIN’, USER는 ‘USER’라는 문자로 데이터베이스에 저장된다. (권장)
    • 장점 : 저장된 enum 의 순서가 바뀌거나 enum이 추가되어도 안전하다.
    • 단점 : 저장되는 데이터 크기가 크다.

@Temporal

  • 날짜 타입(java.util.Date, java.util.Calendar)을 매핑할 때 사용한다. 하지만 날짜 및 시간을 다룰 때 이 타입들이 잘 사용되지 않는다.
  • Java 8은 java.time 패키지 를 도입했으며 JDBC 4.2 API는 추가 SQL 유형 TIMESTAMP WITH TIME ZONETIME WITH TIME ZONE을 지원한다. 또한, OffsetTimeOffsetDateTime 클래스를 통해 UTC에 대한 오프셋 로컬 시간대를 지원한다.

      @Column(name = "local_time", columnDefinition = "TIME")
      private LocalTime localTime;
        
      @Column(name = "local_date", columnDefinition = "DATE")
      private LocalDate localDate;
        
      @Column(name = "local_date_time", columnDefinition = "TIMESTAMP")
      private LocalDateTime localDateTime;
        
      @Column(name = "offset_time", columnDefinition = "TIME WITH TIME ZONE")
      private OffsetTime offsetTime;
        
      @Column(name = "offset_date_time", columnDefinition = "TIMESTAMP WITH TIME ZONE")
      private OffsetDateTime offsetDateTime;
    

@Lob

  • 데이터베이스 BLOL, CLOB 타입과 매핑한다.

@Transient

  • 이 필드는 매핑하지 않고, 객체에 임시로 어떤 값을 보관하고 싶을 때 사용한다.

@Access

  • AccessType.FIELD
    • 필드 접근 권한이 private 이어도 필드에 직접 접근할 수 있다.
  • AccessType.PROPERTY
    • 접근자(getter)를 사용한다.
  • @Access를 설정하지 않으면 @Id 의 위치를 기준으로 접근 방식이 설정된다.
  • @Id가 필드에 있으므로 @Access(AccessType.FIELD)로 설정한 것과 같다. 따라서 @Access 는 생략 가능하다.

      @Entity
      @Access(AccessType.FIELD)
      public class Member {
          @Id
          private String id;
      }
    
  • @Id가 프로퍼티에에 있으므로 @Access(AccessType.PROPERTY)로 설정한 것과 같다. 따라서 @Access 는 생략 가능하다.

      @Entity
      @Access(AccessType.PROPERTY)
      public class Member {
        
          private String id;
        
          private String data1;
        
          @Id
          public String getId() {
              return id;
          }
        
          @Column
          public String getData1() {
              return data1;
          }
      }
    
  • 필드 접근 방식과 프로퍼티 접근 방식을 함께 사용할 수도 있다.
    • 회원 엔티티를 저장하면 회원 테이블 FULLNAME 컬럼에 firstName + lastName 결과과 저장된다.

      @Entity
      public class Member {
          @Id
          private String id;
          
          @Transient
          private String firstName;
          
          @Transient
          private String lastName;
          
          @Access(AccessType.PROPERTY)
          public String getFullName() {
              return firstName + lastName;
          }
      }
      


Reference

JJPA 엔티티 매핑 공부의 기록 JPA 공부 - 4