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은 엔티티와 매핑할 테이블을 지정한다. 생각하면 매핑한 엔티티 이름을 테이블 이름으로 사용한다.
다양한 매핑 사용
- 회원은 일반 회원과 관지라로 구분해야 한다.
- 회원 가입일과 수정일이 있어야 한다.
- 회원을 설명할 수 있는 필드가 있어야 한다. (길이 제한이 없음)
데이터베이스 스키마 자동 생성
JPA는 데이터베이스 스키마를 자동으로 생성하는 기능을 지원한다.
매핑 정보를 사용해 테이블 및 칼럼 정보를 알 수 있고, 데이터베이스 방언(Dialect)을 함께 사용하여 스키마 자동 생성이 가능하다.
- hibernate.hbm2 ddl.auto를 'create'으로 추가하면 애플리케이션 수행 시 테이블을 자동으로 추가한다.
- hibernate.show_sql을 'true'로 설정하는 경우 수행되는 DDL 이 console에 출력된다.
DDL 생성 기능
스키마 자동생성 시 만들어지는 DDL에 제약조건을 추가할 수 있다.
- notnull 제약조건을 넣기 위해 nullable = false 조건 추가.
@Column(name = "NAME", nullable = false)
- 자동생성 필드의 크기를 지정하기 위해 length 속성 사용.
@Column(name = "NAME", length = 10)
- 칼럼에 unique constraint 생성.
@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 |
댓글