JPA(Java Persistence API) 03 - 엔티티

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

persistence.xmlhibernate.hbm2ddl.auto 속성을 추가하여 애플리케이션 실행 시점에 데이터베이스 스키마를 자동 생성하는 기능을 활성화할 수 있다.

자동으로 생성되는 DDL은 지정한 데이터베이스 방언에 따라 달라진다.

스키마 자동 생성 기능은 개발자가 스키마를 직접 생성하는 수고를 덜 수 있으나 운영 환경에서 사용하는 것은 추천하지 않고 개발 환경에서 사용하거나 엔티티와 테이블 매핑 참고 정도로 사용하는 것을 권장한다.

hibernate.hbm2ddl.auto 속성에 지정할 수 있는 옵션은 아래와 같다.

  • create : 기존 테이블을 삭제하고 새로 생성한다.
  • create-drop : create 속성 + 애플리케이션 종료 시 생성한 DDL을 제거한다.
  • update : 테이블과 엔티티 매핑 정보를 비교해서 업데이트한다.
  • validate : 테이블과 엔티티 매핑 정보를 비교해서 다른 경우 경고하며 애플리케이션을 실행하지 않는다.
  • none : 데이터베이스 스키마 자동 생성 기능을 사용하지 않는다.

JPA 2.1부터 스키마 자동 생성 기능을 표준으로 지원하는데 javax.persistence.schema-generation.database.action 속성에 none, create, drop-and-create, drop 중 하나의 옵션을 지정하면 된다. hibernate.hbm2ddl.auto 속성의 update, validate 옵션은 지원하지 않는다.

hibernate.ejb.naming_strategy 속성으로 이름 매핑 전략을 바꿀 수 있다. 기본은 엔티티 클래스 이름이나 필드 이름을 그대로 사용하지만 org.hibernate.cfg.ImproveNamingStrategy 클래스를 지정하면 카멜 표기법으로 선언한 이름을 언더스코어 표기법으로 매핑한다.

DDL 생성

@TableuniqueConstraints 속성이나 @Columnnullable, length와 같은 속성은 스키마 자동 생성 시에만 사용된다. 즉 자동으로 생성되는 DDL에 적용되며, 직접 DDL을 만드는 경우 사용할 필요가 없으나 엔티티의 제약 조건 명시라는 측면에서 장점이 있다.

객체와 테이블 매핑

@Entity

데이터베이스 테이블과 매핑할 엔티티 클래스는 반드시 @Entity 어노테이션을 붙여야 한다. name 속성을 통해 엔티티 이름을 지정할 수 있다. 보통 기본 값인 클래스 이름을 사용하나 다른 패키지에 같은 클래스 이름을 가진 엔티티 클래스가 있다면 이름을 지정해서 충돌을 피해야 한다.

객체와 테이블 매핑 시 주의사항은 아래와 같다.

  • 기본 생성자 필수.
  • final 클래스, enum, 인터페이스, 내부 클래스는 사용할 수 없다.
  • 저장할 필드에 final을 사용하면 안 된다.

@Table

엔티티와 매핑할 데이터베이스 테이블을 지정한다. 생략하면 엔티티 이름을 테이블 이름으로 사용한다.

  • name : 매핑할 테이블 이름 지정. 기본값은 엔티티 이름이다.
  • catalog : 카탈로그 기능이 있는 데이터베이스에서 카탈로그 매핑.
  • schema : 스키마 기능이 있는 데이터베이스에서 스키마 매핑.
  • uniqueConstraints(DDL) : DDL 생성 시 유니크 제약조건 생성. 2개 이상의 복합 유니크 제약조건도 생성 가능. 해당 속성은 스키마 자동 생성 기능을 사용하여 DDL 생성 시에만 사용된다.

필드와 컬럼 매핑

@Column

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

  • name : 필드와 매핑할 테이블의 컬럼명을 지정한다. 기본 값은 엔티티의 필드 이름이다.
  • insertable : 엔티티 저장 시 해당 필드 저장 여부를 지정한다. 기본 값은 true이다.
  • updatable : 엔티티 수정 시 해당 필드 수정 여부를 지정한다. 기본 값은 true이다.
  • table : 하나의 엔티티를 2개 이상의 테이블과 매핑 시 사용한다. 지정한 필드와 매핑할 테이블을 지정한다. 기본 값은 현재 엔티티가 매핑된 테이블이다.
  • nullable(DDL) : null 값 허용 여부를 지정한다. 기본 값은 true이다.
  • unique(DDL) : 해당 필드와 매핑되는 컬럼 1개에 대한 유니크 제약조건을 걸 때 사용한다.
  • columnDefinition(DDL) : 데이터베이스 컬럼 정의를 직접 지정한다.
  • length(DDL) : 문자 길이 제약조건을 지정한다. String 타입에만 사용하며 기본 값은 255이다.
  • precision, scale(DDL) : BigDecimal 또는 BigInteger 타입에서 사용하며 precision은 소수점을 포함한 전체 자릿수, scale은 소수 자릿수를 지정한다. 기본 값은 precision은 19, scale은 2이다.

@Enumerated

자바의 enum 타입을 매핑 시 사용한다. value 속성에 EnumType enum을 사용하여 enum 순서 기준으로 필드에 값을 저장할 것인지, enum 이름 기준으로 필드에 값을 저장할 것인지 지정할 수 있다.

  • EnumType.ORDINAL : enum 순서 기준으로 필드 값 지정. 0부터 시작한다. 장점으로는 저장되는 데이터 크기가 비교적 작다는 것이고 단점은 enum의 순서가 바뀔 경우 기존에 저장된 값을 수동으로 변경해야 한다는 것이다.
  • EnumType.STRING : enum 이름 기준으로 필드 값 지정. 장점으로는 enum의 순서가 바뀌어도 기존에 저장된 값에 영향이 없다는 것이고 단점은 저장되는 데이터 크기가 비교적 크다는 것이다.

@Temporal

자바의 날짜 타입(java.util.Date, java.util.Calendar)을 매핑 시 사용한다. value 속성에 TemporalType enum을 사용하여 필드의 날짜 타입과 데이터베이스 타입 중 어떤 타입을 매핑할 것인지 지정할 수 있다.

  • TemporalType.DATE : 날짜, 데이터베이스 date 타입과 매핑된다.
  • TemporalType.TIME : 시간, 데이터베이스 time 타입과 매핑된다.
  • TemporalType.TIMESTAMP : 날짜와 시간, 데이터베이스 timestamp 타입과 매핑된다. MySQL의 경우 datetime 타입과 매핑된다.

@Lob

데이터베이스 BLOB(Binary Large Object), CLOB(Character Large Object) 타입과 매핑 시 사용한다. 매핑하는 필드 타입이 문자면 CLOB, 나머지는 BLOB으로 매핑한다.

@Transient

해당 필드를 데이터베이스와 매핑하지 않을 때 사용한다. 보통 객체에 임시로 어떤 값을 보관하고 싶을 때 사용한다.

@Access

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

2가지 접근 방식이 있으며 AccessType enum을 사용하여 지정한다.

  • AccessType.FIELD : 필드 접근 방식. 필드에 직접 접근하며 필드 접근 제어자가 private이어도 접근할 수 있다.
  • AccessType.PROPERTY : 프로퍼티 접근 방식. Getter를 사용하여 접근한다.

@Access를 설정하지 않을 경우 @Id의 위치에 따라 접근 방식이 지정된다. @Id 어노테이션이 객체의 필드에 적용되어 있는 경우 필드 접근 방식, 객체 필드의 Getter 메서드에 적용되어 있는 경우 프로퍼티 접근 방식으로 지정되며 2가지 접근 방식을 혼합해서 사용할 수 있다.

기본 키

@Id 어노테이션을 사용하여 기본 키를 지정할 수 있다.

데이터베이스에 따라 기본 키 생성 방식이 다를 수 있는데, @GeneratedValue 어노테이션의 strategy 속성에 GenerationType enum 클래스를 사용하여 기본 키 생성 전략을 지정할 수 있다.

JPA에서 제공하는 데이터베이스 기본 키 생성 전략은 아래와 같다.

  • 직접 할당 : 기본 키를 직접 할당한다.
  • 자동 생성 : 대리 키 사용 방식.
    • IDENTITY : 기본 키 생성을 데이터베이스에 위임한다.
    • SEQUENCE : 데이터베이스 시퀀스를 사용하여 기본 키를 할당한다.
    • TABLE : 기본 키 테이블을 사용하여 기본 키를 할당한다.

persistence.xmlhibernate.id.new_generator_mappings 속성을 true 값으로 지정해야 한다. 하이버네이트에서 더 효과적이고 JPA 규격에 맞는 새로운 키 생성 전략을 개발했으나 하위 버전과의 호환성 유지를 위해 기본적으로 비활성화 되도록 false 값이 지정되어 있다.

직접 할당 전략

코드 상에서 기본 키 필드에 직접 값을 할당한다.

IDENTITY 전략

기본 키 생성을 데이터베이스에 위임하는 전략. 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용한다.

데이터를 데이터베이스에 INSERT한 후에 기본 키 값을 조회할 수 있기에 엔티티에 식별자 값을 할당하려면 추가로 데이터베이스를 조회해야 한다. JDBC3에 추가된 Statement.getGeneratedKeys()를 사용하면 데이터 저장과 동시에 생성된 기본 키 값을 얻어올 수 있으며 하이버네이트는 이 메서드를 사용해서 데이터베이스와 한 번만 통신한다.

엔티티가 영속 상태가 되려면 식별자가 반드시 필요하기 때문에 IDENTITY 전략을 사용할 경우 엔티티 매니저의 persist()를 호출하는 즉시 INSERT SQL이 데이터베이스에 전달된다. 즉 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다는 의미이다.

SEQUENCE 전략

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

주로 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용한다.

먼저 데이터베이스에 시퀀스를 생성해야 하며, @SequenceGenerator를 사용해서 시퀀스 생성기를 등록해야 한다. @SequenceGenerator의 속성은 아래와 같다.

  • name : 시퀀스 생성기 이름을 지정한다. 필수.
  • sequenceName : 매핑할 데이터베이스 시퀀스 이름을 지정한다. 기본 값은 JPA 구현체가 정의하며 하이버네이트의 경우 hibernate_sequence이다.
  • initialValue : 초기 값을 지정한다. 기본 값은 1.
  • allocationSize : 시퀀스 한 번 호출에 증가하는 수를 지정한다. 성능 최적화와 관련 있으며 기본 값은 50.
  • catalog, schema : 데이터베이스 카탈로그, 스키마 이름을 지정한다.

SEQUENCE 전략은 엔티티 매니저의 persist() 호출 시 데이터베이스 시퀀스를 사용해서 식별자를 조회한 후, 조회한 식별자를 엔티티에 할당하고 엔티티를 영속성 컨텍스트에 저장한다. 이후 플러시가 발생하면 엔티티를 데이터베이스에 저장한다.

JPA는 시퀀스 접근 횟수를 최적화하기 위해 시퀀스 생성기의 allocationSize 속성을 사용한다. allocationSize 속성에 지정된 값만큼 한 번에 데이터베이스의 시퀀스 값을 증가시키고, 그만큼의 시퀀스 값을 메모리에 저장해두며, 메모리에 저장된 시퀀스 값이 있으면 데이터베이스를 조회하지 않고 메모리의 시퀀스 값으로 엔티티의 식별자를 할당한다. 이 방법은 데이터베이스에 시퀀스 값을 조회하기 위한 접근 횟수를 줄일 수 있고, 시퀀스 값을 선점하므로 여러 JVM에서 동시에 접근해서 시퀀스를 사용해도 값이 충돌하지 않는 장점이 있으나 한 번에 증가하는 시퀀스 값이 많을 수 있다는 점을 염두하여야 한다.

TABLE 전략

TABLE 전략은 키 생성 전용 테이블을 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스처럼 사용하는 전략이다. 이 전략은 테이블을 사용하므로 모든 데이터베이스에 적용할 수 있다.

먼저 키 생성 전용 테이블을 생성해야 한다. 기본적으로 sequence_name 컬럼을 시퀀스 이름용 컬럼, next_val 컬럼을 시퀀스 값용 컬럼으로 사용한다. 필요하면 컬럼명을 변경할 수 있다.

@TableGenerator를 사용해서 테이블 키 생성기를 등록하고 키 생성 전용 테이블과 매핑한다.

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

TABLE 전략은 키 생성 전용 테이블을 조회하고, 값을 증가시키는 동작을 실행하기 때문에 SEQUENCE 전략과 비교하면 1번 더 데이터베이스와 통신하는 단점이 있다. 이 전략 역시 SEQUECE 전략과 마찬가지로 allocationSize 속성을 사용하여 최적화할 수 있으며 방법은 SEQUENCE 전략과 동일하다.

AUTO 전략

선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택하는 전략. @GeneratedValue 어노테이션 strategy 속성의 기본 값이다.

보통 아직 키 생성 전략이 확정되지 않은 개발 단계에서 사용한다.

위 내용은 김영한님의 자바 ORM 표준 JPA 프로그래밍를 읽으며 개인적으로 요약 및 정리하는 내용이다.
자세한 내용이 알고 싶으면 김영한님의 자바 ORM 표준 JPA 프로그래밍을 직접 읽어보길 추천한다.