JPA로 엔터티와 테이블을 매핑하는 데 사용되는 매핑 애너테이션은 크게 다음의 4가지로 분류할 수 있다.
@Entity 애너테이션으로 지정한 클래스는 테이블과 매핑되며 JPA가 관리하는 엔터티라고 부른다.
| 속성 | 기능 | 기본 값 |
|---|---|---|
| name | JPA에서 사용할 엔터티 이름을 지정한다. 보통 기본 값인 클래스 이름을 사용하지만 다른 엔터티와 이름이 충돌할 시엔 별도로 명시해야 한다. | 클래스 이름 |
애너테이션 적용 시 주의사항은 다음과 같다.
public 또는 protected 접근 제어자가 적용된 기본 생성자가 반드시 정의되어야 한다.final 클래스, enum, interface, inner 클래스에는 사용할 수 없다.final 키워드를 사용하면 안 된다.JPA는 엔터티 객체 생성 시 기본 생성자를 사용하기 때문에 생성자가 반드시 정의되어 있어야 한다. Java는 클래스에 별도로 생성자를 정의하지 않으면 기본 생성자가 자동으로 정의되지만 반대의 경우엔 사용자가 직접 기본 생성자를 정의해야 한다.
@Table 애너테이션으로 엔터티와 매핑할 테이블을 지정한다. 생략하면 매핑한 엔터티 이름을 테이블 이름으로 사용한다.
| 속성 | 기능 | 기본 값 |
|---|---|---|
| name | 매핑할 테이블 이름 | 엔터티 이름 |
| catalog | catalog 기능이 있는 데이터베이스의 catalog 매핑 | |
| schema | schema 기능이 있는 데이터베이스의 schema 매핑 | |
| uniqueConstraints (DDL) | DDL 생성 시 유니크 제약 조건을 만든다. 2개 이상의 복합 유니크 제약 조건도 만들 수 있다. 스키마 자동 생성 기능을 사용해 DDL을 만들 때만 사용된다. |
기존의 회원 관리 요구사항에 다음과 같은 요구사항이 추가되었다고 가정하자.
예제 4.1. 회원 엔터티
package jpabook.start.domain;
import jakarta.persistence.*;
import jpabook.start.enums.RoleType;
import java.util.Date;
@Entity
@Table(name = "MEMBER")
public class Member {
@Id
@Column(name = "ID")
private String id;
@Column(name = "NAME")
private String username;
private Integer age;
@Enumerated(EnumType.STRING)
private RoleType roleType;
@Temporal(TemporalType.TIMESTAMP)
private Date createdDate;
@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;
@Lob
private String description;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public RoleType getRoleType() {
return roleType;
}
public void setRoleType(RoleType roleType) {
this.roleType = roleType;
}
public Date getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}
public Date getLastModifiedDate() {
return lastModifiedDate;
}
public void setLastModifiedDate(Date lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public String toString() {
return "Member{" +
"id='" + id + '\'' +
", username='" + username + '\'' +
", age=" + age +
", roleType=" + roleType +
", createdDate=" + createdDate +
", lastModifiedDate=" + lastModifiedDate +
", description='" + description + '\'' +
'}';
}
}
RoleType 열거형 클래스 정의
package jpabook.start.enums;
public enum RoleType {
ADMIN, USER
}
roleType: Java 열거형(enum)을 사용해 회원의 종류를 구분했다. enum을 사용하려면 @Enumerated 애너테이션으로 매핑해야 한다.createdDate, lastModifiedDate: Java 날짜(Date) 타입은 @Temporal 애너테이션으로 매핑한다.description: 회원 설명 필드는 길이 제한이 없기 때문에 데이터베이스에서 VARCHAR 타입 대신 CLOB 타입으로 저장해야 한다. @Lob 애너테이션으로 CLOB, BLOB 타입을 매핑한다.JPA는 엔터티 클래스의 매핑 정보를 통해 테이블 구조를 파악할 수 있기 때문에 매핑 정보와 데이터베이스 방언을 사용해 데이터베이스 스키마를 자동 생성할 수 있다.
이번 프로젝트부터는 프로퍼티 파일을 통해 JPA 세부 설정을 명시할 것이다. application.properties에 다음 한 줄을 추가함으로써 스키마 자동 생성 기능을 활성화할 수 있다.
spring.jpa.properties.hibernate.hbm2ddl.auto=create
위 프로퍼티 추가 시 애플리케이션 실행 시점에 데이터베이스 테이블을 자동으로 생성한다. spring.jpa.show-sql=true 프로퍼티를 추가해 실행되는 DDL(Data Definition Language)을 출력할 수 있다.
예제 4.2. DDL 콘솔 출력
Hibernate:
drop table if exists member cascade
Hibernate:
create table member (
age integer,
created_date timestamp(6),
last_modified_date timestamp(6),
id varchar(255) not null,
name varchar(255),
description clob,
role_type enum ('ADMIN','USER'),
primary key (id)
)
enum 타입은 VARCHAR 타입으로, Date 타입은 TIMESTAMP 타입으로, String(@Lob) 타입은 CLOB 타입으로 정의되었다. 이러한 타입 변환은 데이터베이스 방언마다 달라질 수 있다.
예제 4.3. 오라클 데이터베이스 방언 결과
Hibernate:
create table member (
age number(10,0),
created_date timestamp(6),
last_modified_date timestamp(6),
id varchar2(255 char) not null,
name varchar2(255 char),
role_type varchar2(255 char) check (role_type in ('ADMIN','USER')),
description clob,
primary key (id)
)
hibernate.hbm2ddl.auto 속성은 다음과 같다.
| 옵션 | 설명 |
|---|---|
| create | 기본 테이블을 삭제하고 새로 생성한다. DROP + CREATE |
| create-drop | create 속성에 애플리케이션 종료 시 생성한 DDL 제거 기능을 추가한다. DROP + CREATE + DROP |
| update | 데이터베이스 테이블과 엔터티 매핑 정보를 비교해 변경 사항만 수정한다. |
| validate | 데이터베이스 테이블과 엔터티 매핑 정보를 비교해 변경 사항이 있을 시 경고를 남기고 애플리케이션을 실행하지 않는다. |
create, create-drop, update처럼 DDL을 수정하는 옵션은 프로덕션 환경에서는 절대로 사용되지 않는다. 개발 환경에 따른 추천 전략은 다음과 같다.
create, updatecreate, create-dropupdate, validatevalidate, none이름 표기 시 Java는 Camel Case를 주로 사용하지만, 데이터베이스는 관례상 Snake Case를 주로 사용한다. 이러한 차이를 @Column 애너테이션 속성으로 데이터베이스 테이블 이름을 전달하는 식으로 반영할 수도 있지만, 프로퍼티 설정에 네이밍 전략을 추가하여 반영할 수도 있다.
회원 이름은 필수로 입력되어야 하고 10자를 초과하면 안 된다는 요구사항이 추가되었다고 가정하자. 다음과 같이 스키마 자동 생성 시 DDL에 제약 조건을 즉시 추가할 수 있다.
예제 4.4. 추가 코드
...
@Entity
@Table(name = "MEMBER")
public class Member {
@Id
@Column(name = "ID")
private String id;
@Column(name = "NAME", nullable = false, length = 10)
private String username;
...
@Column 애너테이션의 속성 값을 nullable=false로 설정하면 NOT NULL 제약 조건을 추가하고, length = N로 설정하면 가변 문자열의 크기를 제한할 수 있다.
예제 4.5. 생성된 DDL
Hibernate:
drop table if exists member cascade
Hibernate:
create table member (
age integer,
created_date timestamp(6),
last_modified_date timestamp(6),
name varchar(10) not null,
id varchar(255) not null,
description clob,
role_type enum ('ADMIN','USER'),
primary key (id)
)
NAME 컬럼에 NOT NULL 제약 조건과 VARCHAR(10)의 길이 제한이 걸린 것을 확인할 수 있다.
테이블에 유니크 제약 조건을 추가하려면 다음과 같이 @Table 애너테이션의 uniqueConstraints 속성을 이용하면 된다.
예제 4.6. 유니크 제약 조건
...
@Entity
@Table(name = "MEMBER", uniqueConstraints = {@UniqueConstraint(
name = "NAME_AGE_UNIQUE",
columnNames = {"NAME", "AGE"}
)})
public class Member {
...
예제 4.7. 생성된 DDL
Hibernate:
drop table if exists member cascade
Hibernate:
create table member (
age integer,
created_date timestamp(6),
last_modified_date timestamp(6),
name varchar(10) not null,
id varchar(255) not null,
description clob,
role_type enum ('ADMIN','USER'),
primary key (id),
constraint NAME_AGE_UNIQUE unique (name, age)
)
위 모든 기능들은 DDL을 자동 생성할 때만 사용되고 JPA 실행 로직에는 영향을 주지 않는다. 따라서 직접 DDL을 정의하는 경우 사용할 이유가 없다. 그래도 이 기능을 사용하면 애플리케이션 개발자가 엔터티만 보고도 데이터베이스의 물리적 구조를 파악할 수 있다는 장점이 있다.
예제 4.8. 기본 키 매핑 시작
...
@Entity
@Table(name = "MEMBER")
public class Member {
@Id
@Column(name = "ID")
private String id;
...
위와 같이 @Id 애너테이션을 사용하는 것은 기본 키를 애플리케이션에서 직접 할당하겠다는 의미이다. 그래서 기본 키 없이 엔터티를 영속화하면 예외가 발생한다. 하지만 오라클의 시퀀스 오브젝트나 MySQL의 AUTO_INCREMENT와 같이 기본 키 값을 데이터베이스 차원에서 생성해 주는 경우가 있다. 이와 관련하여 JPA가 제공하는 기본 키 생성 전략은 다음과 같다.
데이터베이스 벤더마다 지원하는 자동 생성 전략이 다르기 때문에 JPA의 자동 생성 전략도 그에 맞춰 다양하게 존재한다. 직접 할당 전략은 @Id 애너테이션만으로 사용할 수 있고, 자동 생성 전략은 @GeneratedValue 애너테이션을 추가해야 한다.
기존에는 하이버네이트에서 과거 버전과의 호환을 위해 spring.jpa.hibernate.use-new-id-generator-mappings=true와 같은 프로퍼티를 추가할 것을 요구했는데, 최신 버전에서는 이 프로퍼티를 비활성화하는 것을 막아두어서 더 이상 별도로 설정하지 않아도 된다. 이 속성이 활성화되어 있을 시 키 생성 성능을 최적화하는 allocationSize 속성을 사용하는 방식이 달라진다.
@Id 애너테이션을 적용할 수 있는 Java 타입은 다음과 같다.
Stringjava.util.Datejava.sql.Datejava.math.BigDecimaljava.math.BigInteger기본 키 직접 할당 전략 사용 시 엔터티를 영속화하기 전 애플리케이션에서 기본 키를 직접 할당해야 한다.
IDENTITY 전략은 기본 키 생성을 데이터베이스에 위임한다. 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용한다. MySQL의 AUTO_INCREMENT를 사용할 때 이 전략을 사용할 수 있다.
예제 4.9. IDENTITY 매핑 코드
package jpabook.start.domain;
import jakarta.persistence.*;
@Entity
@Table(name = "BOARD")
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
예제 4.10. IDENTITY 사용 코드
@Transactional
public void identityLogic() {
Board board = new Board();
em.persist(board);
System.out.println("board.getId() = " + board.getId());
}
출력 결과
Hibernate:
/* insert for
jpabook.start.domain.Board */insert
into
board (id)
values
(default)
board.getId() = 1
엔터티가 영속 상태가 되려면 식별자가 반드시 필요하기 때문에 IDENTITY 전략으로 식별자 생성 시 영속화와 동시에 INSERT SQL이 데이터베이스로 전달된다. 때문에 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다.
IDENTITY 전략 사용 시 JPA가 영속화한 엔터티를 데이터베이스에 INSERT한 후 추가로 데이터베이스 조회 쿼리를 전송해 기본 키 값을 조회해 오기 때문에 쿼리가 두 번 발생할 수 있다. 하이버네이트는 JDBC3에 추가된 Statement.getGeneratedKeys()를 사용함으로써 한 번의 통신으로 데이터를 저장함과 동시에 생성된 기본 키 값을 얻어올 수 있다.
데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트이다. SEQUENCE 전략은 이를 사용해 기본 키를 생성한다. 오라클, PostgreSQL, DB2, H2 데이터베이스 등에서 시퀀스를 지원한다.
예제 4.11. 시퀀스 DDL
CREATE TABLE BOARD (
ID BIGINT NOT NULL PRIMARY KEY,
DATA VARCHAR(255)
);
-- 시퀀스 생성
CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1;
예제 4.12. 시퀀스 매핑 코드
...
@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;
...
@SequenceGenerator 애너테이션으로 시퀀스 생성기를 정의하고 데이터베이스의 시퀀스 오브젝트와 매핑한다. 이 애너테이션은 엔터티 클래스나 필드를 지정할 수 있다. 이때 애플리케이션에서 사용될 이름과 데이터베이스에 저장될 시퀀스의 이름이 구분된다. @GeneratedValue 애너테이션에서 SEQUENCE 전략 사용 시 generator 속성으로 앞서 정의한 시퀀스 생성기의 이름을 전달함으로써 데이터베이스의 BOARD_SEQ 시퀀스 생성기가 기본 키를 할당하도록 설정했다.
예제 4.13. 시퀀스 사용 코드
@Transactional
public void sequenceLogic() {
Board board = new Board();
em.persist(board);
System.out.println("board.getId() = " + board.getId());
}
출력 결과
Hibernate:
select
next value for board_seq
board.getId() = 1
애플리케이션 레벨의 코드는 IDENTITY 전략과 같지만 SEQUENCE 전략 사용 시 데이터베이스로부터 다음 시퀀스 값을 조회하고 엔터티의 필드 값을 설정한 다음 영속화를 진행한다는 차이점이 있다. 그러므로 이때는 트랜잭션을 지원하는 쓰기 지연 기능을 활용할 수 있다.
| 속성 | 기능 | 기본 값 |
|---|---|---|
| name | 식별자 생성기 이름 | 필수 |
| sequenceName | 데이터베이스의 시퀀스 이름 | hibernate_sequence |
| initialValue | DDL 자동 생성 시에만 사용 시퀀스 DDL 생성 시 초기 값 | 1 |
| allocationSize | 시퀀스 한 번 호출 시 증가하는 수(성능 최적화에 사용) | 50 |
| catalog, schema | 데이터베이스 catalog, schema 이름 |
위 속성들은 다음 DDL에 매핑된다.
CREATE SEQUENCE [sequenceName] START WITH [initialValue] INCREMENT BY [allocationSize]
JPA 표준 명세에서는 sequenceName 기본 값을 JPA 구현체가 정의하도록 했다. 이를 별도로 명시하지 않으면 단일한 시퀀스 오브젝트를 다수의 테이블에서 공유하여 안정성이 저하되는 상황이 발생할 수 있으므로 일반적으로 사용자가 직접 명시하는 것이 권장된다.
또한 최적화를 고려해 allocationSize의 기본 값이 50으로 설정되어 있으므로 데이터베이스 시퀀스 오브젝트의 값이 1씩 증가하도록 설정되어 있으면 이 값을 반드시 1로 통일해 주어야 한다.
SEQUENCE 전략은 데이터베이스 시퀀스 오브젝트를 통해 식별자를 조회하는 추가 작업이 필요하다. 따라서 데이터베이스와 2번 통신한다.
JPA는 시퀀스에 접근하는 횟수를 줄이고자 allocationSize 속성을 사용해 설정된 값만큼 한 번에 시퀀스 값을 증가시키고 나머지 값들은 메모리에서 관리한다. 메모리에 있는 시퀀스 값을 모두 사용하면 다시 한 번 시퀀스 조회가 발생한다.
이 최적화 방법은 데이터베이스 시퀀스 오브젝트의 값을 선점하기 때문에 다수의 JVM이 동작하더라도 기본 키 값이 충돌하지 않는다는 장점이 있다. 하지만 데이터베이스에 직접 접근해 데이터를 저장할 때 시퀀스 값이 한 번에 많이 증가한다는 점을 염두해야 한다. 만약 INSERT 성능이 중요하지 않다면 allocationSize를 1로 설정해도 된다.
과거의 하이버네이트는 데이터베이스 시퀀스 오브젝트로부터 받아온 시퀀스 값에 allocationSize를 곱하여 기본 키 값을 계산하는 방식이었지만, hibernate.id.new_generator_mappings 속성이 기본적으로 설정되어 있는 최신 하이버네이트는 데이터베이스 시퀀스 오브젝트의 시퀀스 값을 실제로 allocationSize 만큼 증가시키는 방식으로 동작한다.
TABLE 전략은 키 생성 전용 테이블을 하나 만들고, 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스의 시퀀스 오브젝트를 흉내내는 전략이다. 이 전략은 테이블을 활용하므로 시퀀스 오브젝트가 없는 모든 데이터베이스에 적용할 수 있다.
TABLE 전략 사용을 위해 다음과 같이 먼저 키를 생성할 테이블을 정의해야 한다.
예제 4.14. TABLE 전략 키 생성 DDL
CREATE TABLE MY_SEQUENCES (
sequence_name VARCHAR(255) NOT NULL,
next_val bigint,
PRIMARY KEY (sequence_name)
);
sequence_name 컬럼을 시퀀스 이름으로 사용하고, next_val 컬럼을 시퀀스 값으로 사용한다. 컬럼의 이름을 변경할 수 있으나 next_val이 기본 값이다.
예제 4.15. TABLE 전략 매핑 코드
...
@Entity
@TableGenerator(
name = "BOARD_SEQ_GENERATOR",
table = "MY_SEQUENCES",
pkColumnValue = "BOARD_SEQ",
allocationSize = 1
)
public class Board {
@Id
@GeneratedValue(
strategy = GenerationType.TABLE,
generator = "BOARD_SEQ_GENERATOR"
)
private Long id;
...
@TableGenerator 애너테이션으로 테이블 키 생성기를 등록하고, @GeneratedValue 애너테이션의 strategy 속성의 값은 GenerationType.TABLE으로 설정한다. 그리고 키 생성기 이름을 통해 둘을 매핑하면 된다.
예제 4.16. TABLE 전략 매핑 사용 코드
@Test
@Transactional
public void tableLogic() {
Board board = new Board();
em.persist(board);
System.out.println("board.getId() = " + board.getId());
}
출력 결과
Hibernate:
select
tbl.next_val
from
my_sequences tbl
where
tbl.sequence_name=? for update
Hibernate:
update
my_sequences
set
next_val=?
where
next_val=?
and sequence_name=?
board.getId() = 1
TABLE 전략은 시퀀스 오브젝트 대신 테이블을 사용한다는 것만 제외하고 SEQUENCE 전략과 내부 동작 방식이 같다.
| 속성 | 기능 | 기본 값 |
|---|---|---|
| name | 식별자 생성기 이름 | 필수 |
| table | 키 생성 테이블 이름 | hibernate_sequences |
| pkColumnName | 시퀀스 컬럼 이름 | sequence_name |
| valueColumnName | 시퀀스 값 컬럼 이름 | next_val |
| pkColumnValue | 키로 사용할 값 이름 | 엔터티 이름 |
| initialValue | 초기 값, 마지막으로 생성된 값이 기준 | 0 |
| allocationSize | 시퀀스 한 번 호출 시 증가하는 수 | 50 |
| catalog, schema | 데이터베이스 catalog, schema 이름 | |
| uniqueConstraints(DDL) | 유니크 제약 조건 지정 |
각 속성은 테이블 구조에서 다음과 같은 의미를 가진다.
| {pkColumnName} | {valueColumnName} |
|---|---|
| {pkColumnValue} | {initialValue} |
| {pkColumnValue} | {initialValue} |
| {pkColumnValue} | {initialValue} |
SEQUENCE 전략과 같은 방식이다.
@GeneratedValue 애너테이션의 strategy 속성으로 GenerationType.AUTO를 설정하면 선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 중 적절한 전략을 자동으로 선택한다.
예제 4.17. AUTO 전략 매핑 코드
...
@Entity
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
...
데이터베이스 변경 시에도 코드를 수정할 필요가 없고, 키 생성 전략이 아직 확정되지 않은 개발 초기 단계나 프로토타입 개발 시 편리하게 사용할 수 있다. AUTO 사용 시 SEQUENCE, TABLE 전략이 사용될 것을 염두에 둔다면 미리 시퀀스 오브젝트나 키 생성 테이블을 정의해야 한다. 이를 위해 스키마 자동 생성 기능을 활용할 수도 있다.
엔터티를 영속화하려면 식별자 값이 반드시 존재해야 한다. 식별자 할당 전략별로 em.persist() 호출 시 다음과 같은 과정이 발생한다.
SEQUENCE: 데이터베이스 시퀀스에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다.TABLE: 데이터베이스 시퀀스 생성용 테이블에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다.IDENTITY: 데이터베이스에 엔터티를 저장하여 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다. 따라서 트랜잭션을 지원하는 쓰기 지연 기능을 활용할 수 없다.데이터베이스 기본 키는 다음의 3가지 조건을 모두 만족해야 한다.
NOT NULL).UNIQUE).테이블의 기본 키를 선택하는 전략은 크게 2가지가 있다.
AUTO_INCREMENT, 키 생성 테이블JPA는 모든 엔터티에 일관된 방식으로 대리 키를 사용할 것을 권장한다. 또한 JPA는 저장된 엔터티의 기본 키 값이 절대 변경되면 안 된다는 기본 원칙으로 인해 기본 키를 변경할 경우 예외를 발생시키거나 정상 동작하지 않는다.
| 분류 | 매핑 애너테이션 | 설명 |
|---|---|---|
| 필드와 컬럼 매핑 | @Column | 컬럼 매핑 |
| 필드와 컬럼 매핑 | @Enumerated | Java enum 타입 매핑 |
| 필드와 컬럼 매핑 | @Temporal | 날짜 타입 매핑 |
| 필드와 컬럼 매핑 | @Lob | BLOB, CLOB 타입 매핑 |
| 필드와 컬럼 매핑 | @Transient | 특정 필드를 매핑하지 않음 |
| 기타 | @Access | JPA가 엔터티에 접근하는 방식 지정 |
@Column 애너테이션은 객체 필드를 테이블 컬럼에 매핑한다.
| 속성 | 기능 | 기본 값 |
|---|---|---|
| name | 필드와 매핑할 테이블의 컬럼 이름 | 객체의 필드 이름 |
| insertable | 엔터티 저장 시 이 필드도 같이 저장한다. false로 설정 시 이 필드는 데이터베이스에 저장하지 않는다. false 옵션은 읽기 전용일 때 사용한다. | true |
| updatable | 엔터티 수정 시 이 필드도 같이 수정한다. false로 설정 시 이 필드는 데이터베이스에 수정하지 않는다. false 옵션은 읽기 전용일 때 사용한다. | true |
| table | 하나의 엔터티를 두 개 이상의 테이블에 매핑할 때 사용한다. 지정한 필드를 다른 테이블에 매핑할 수 있다. | 현재 클래스가 매핑된 테이블 |
| nullable(DDL) | null 값의 허용 여부 | true |
| unique(DDL) | 단일 컬럼에 대한 유니크 제약 조건 설정 | |
| columnDefinition(DDL) | 데이터베이스 컬럼 정보 직접 설정 | 필드의 Java 타입과 방언 정보를 사용해 적절한 컬럼 타입 생성 |
| length(DDL) | 문자 길이 제약 조건, String 타입에만 사용 | 255 |
| precision, scale(DDL) | BigDecimal, BigInteger 타입의 자릿수 설정 precision은 소수점 포함 전체 자릿수 scale은 소수 자릿수 | precision=19, scale=2 |
@Column 애너테이션 생략 시 기본 값으로 채워지는데, nullable 속성과 관련하여 Java 기본형은 null 값이 저장될 수 없기 때문에 예외적으로 NOT NULL로 필드가 정의된다. 이는 안전성을 위한 JPA의 예외 동작에 해당하며 따라서 자바 기본형에 @Column 애너테이션 사용 시 nullable 속성을 false로 설정하는 것이 안전하다.
@Enumerated 애너테이션은 Java의 enum 타입 필드를 테이블 컬럼에 매핑한다.
| 속성 | 기능 | 기본 값 |
|---|---|---|
| value | - EnumType.ORDINAL: enum 순서를 데이터베이스에 저장- EnumType.STRING: enum 이름을 데이터베이스에 저장 | EnumType.Ordinal |
EnumType.ORDINAL: enum에 정의된 순서대로 데이터베이스에 저장한다.EnumType.STRING일반적으로 EnumType.STRING 사용이 권장된다.
@Temporal 애너테이션은 Java의 날짜 타입(java.util.Date, java.util.Calendar) 필드를 테이블 컬럼에 매핑한다.
| 속성 | 기능 | 기본 값 |
|---|---|---|
| value | - TemporalType.DATE: 날짜, 데이터베이스 date 타입과 매핑- TemporalType.TIME: 시간, 데이터베이스 time 타입과 매핑- TemporalType.TIMESTAMP: 날짜와 시간, 데이터베이스 timestamp 타입과 매핑 | TemporalType 지정 필수 |
Java의 Date 타입에는 연, 월, 일, 시, 분, 초가 저장되지만 데이터베이스에는 date(날짜), time(시간), timestamp 또는 datetime(날짜와 시간)의 세 가지 타입이 별도로 존재한다. @Temporal 애너테이션 생략 시 데이터베이스 방언에 따라 Java의 Date와 가장 유사한 timestamp 또는 datetime 타입으로 정의된다.
datetime: MySQLtimestamp: H2, 오라클, PostgreSQL@Lob 애너테이션은 필드를 BLOB, CLOB 타입의 테이블 컬럼에 매핑한다.
@Lob 애너테이션은 지정 가능한 속성이 없는 대신 매핑하는 필드 타입이 문자면 CLOB, 나머지는 BLOB 타입으로 매핑한다.
String, char[], java.sql.CLOBbyte[], java.sql.BLOB@Transient 애너테이션으로 지정한 필드는 테이블과 매핑하지 않는다.
@Access 애너테이션은 JPA가 엔터티 데이터에 접근하는 방식을 지정한다.
AccessType.FIELD로 지정하며, 접근 제어자와 상관없이 필드에 직접 접근한다.AccessType.PROPERTY로 지정하며, 접근자(Getter)를 사용한다.@Access 애너테이션을 사용하지 않으면 @Id 애너테이션의 위치를 기준으로 접근 방식이 설정된다.
예제 4.18. 필드 접근 코드
...
@Entity
@Access(AccessType.FIELD)
public class Member {
@Id
private String id;
...
위 코드는 @Id 애너테이션으로 필드를 지정했으므로 @Access(AccessType.FIELD)는 생략할 수 있다.
예제 4.19. 프로퍼티 접근 코드
@Entity
@Access(AccessType.PROPERTY)
public class Member {
private String id;
private String data1;
private String data2;
@Id
public String getId() {
return id;
}
public String getData1() {
return data1;
}
public String getData2() {
return data2;
}
}
@Id 애너테이션으로 프로퍼티를 지정했으므로 @Access(AccessType.PROPERTY)는 생략할 수 있다.
예제 4.20. 필드, 프로퍼티 접근 함께 사용
...
@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;
}
...
@Id 애너테이션으로 필드를 지정했으므로 기본적으로 필드 접근 방식을 사용하지만, getFullName() 메서드는 프로터피 접근 방식을 사용한다. 따라서 회원 엔터티 저장 시 회원 테이블의 FULLNAME 컬럼에 firstName + lastName 결과가 저장된다.