자바 ORM 표준 JPA 프로그래밍 - (4) 엔티티 매핑
✓︎ @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 생성 기능 사용 예시
@Column
의nullable
속성으로 not null 제약조건을 추가할 수 있다.-
@Column
의length
속성으로 문자의 크기를 지정할 수 있다.@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()
를 사용하면 데이터를 저장하면서 동시에 생성된 기본 키 값도 얻어 올 수 있다. 하이버네이트는 이 메소드를 사용해서 데이터베이스와 한 번만 통신한다.
- IDENTITY 전략은 데이터를 데이터베이스에
- 엔티티가 영속 상태가 되려면 식별자가 반드시 필요하다.
- 따라서 이 전략은 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다.
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 ZONE
및TIME WITH TIME ZONE
을 지원한다. 또한,OffsetTime
및OffsetDateTime
클래스를 통해 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; } }
-