엔티티는 데이터베이스의 테이블과 매핑되는 자바 객체이다. 따라서 자바에서 테이블의 데이터를 핸들링하기 위해서는 엔티티를 잘 매핑해야한다.
엔티티를 테이블과 매핑하기 위한 기본적인 문법을 살펴보자.
JPA를 사용해서 테이블과 매핑할 자바 객체를 엔티티라고 한다. 이 엔티티에는 Entity 어노테이션을 사용해서 테이블과 매핑할 것이라고 알려줘야한다.
name attribute를 사용해서 엔티티의 이름을 지정할 수 있다. 생략할 수 있으며 생략하는 경우에는 클래스의 이름을 엔티티의 이름으로 사용한다.
엔티티와 매핑할 데이터베이스 테이블을 지정하는 어노테이션이다. 생략할 수 있으며 생략하게 되면 엔티티의 이름과 동일한 테이블을 찾아서 자동으로 매핑한다.
name attribute로 매핑할 테이블 이름을 지정해야한다.
@Entity
@Table(name = "MEMBER")
public class Member {
@Id
@Column(name = "id")
private Long id;
@Column(name = "name")
private String name;
}
JPA에서 어떤 테이블을 조회할 때 보통 기본키 식별자를 가지고 조회한다. 따라서 데이터베이스의 기본키를 엔티티에도 매핑을 해주어야만 한다.
Id 어노테이션을 기본 키로 사용할 엔티티의 필드위에 명시한다.
데이터베이스의 기본 키는 bigint 타입의 정수형으로 선언되며 auto_increment 제약 조건을 가지는 경우가 많다. 이러한 경우에 엔티티에서는 어떤 방식으로 기본 키를 생성할까?
엔티티에서는 위와 같은 네 가지 방식으로 기본 키를 생성한다.
직접 할당 방식은 데이터베이스에서 auto_increment 제약 조건을 사용하지 않는 경우이다. 이 경우에는 엔티티에서 따로 기본 키 생성 전략을 명시하지 않고, 데이터를 insert 할 때 기본 키도 같이 할당해주어야 한다.
이 방식은 데이터베이스에 기본 키 생성을 전임하는 방식이다. 엔티티에서는 기본 키는 따로 건드리지 않더라도 데이터베이스에서 기본 키를 알아서 생성해준다.
JPA에서는 모든 엔티티를 기본 키 식별자로 구분한다. 그렇기 때문에 이 방식으로 데이터를 저장하게 되면, 식별자를 자바에서 모르는 상태이기 때문에 저장 직후 데이터베이스와 통신해서 식별자를 받아온다. 즉, 쓰기 지연이 동작하지 않는다.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
이 방식은 Oracle, PostgreSQL, DB2, H2와 같은 데이터베이스에서 사용 할 수 있는 생성 전략이다. 이 데이터베이스들은 시퀀스를 지원하고 있고 시퀀스를 이용해서 기본 키를 생성한다.
우선 아래와 같이 시퀀스가 먼저 생성되어 있어야 한다.
CREATE SEQUENCE TEST_SEQ START WITH 1 INCREMENT BY 1;
그리고 엔티티에 @SequenceGenerator를 명시해서 데이터베이스에 생성된 시퀀스를 먼저 매핑하고 이 Generator를 기본 키에 다시 매핑한다.
@Entity
@SequenceGenerator(name = "TEST_SEQ_GENERATOR", sequenceName = "TEST_SEQ",
initialValue = 1, allocationSize = 1)
public class Member(
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "TEST_SEQ_GENERATOR")
@Column(name = "id")
private Long id;
)
이 방식은 시퀀스를 지원하지 않는 데이터베이스에서 테이블을 시퀀스처럼 사용하는 방식이다. 우선 시퀀스처럼 사용할 테이블을 아래와 같이 만들어져 있어야 한다.
CREATE TABLE TEST_SEQUENCES(
sequence_name varchar(255) not null,
next_val bigint,
primary key( sequence_name )
)
이렇게 만들어진 테이블을 @TableGenerator로 매핑하고 이 Generator를 다시 기본 키에 매핑한다.
@Entity
@TableGenerator(name = "TEST_SEQ_GENERATOR", table = "TEST_SEQUENCES",
pkColumnValue = "TEST_SEQ", allocationSize = 1)
public class Member(
@Id
@GeneratedValue(strategy = GenerationType.TABLE,
generator = "TEST_SEQ_GENERATOR")
@Column(name = "id")
private Long id;
)
TEST_SEQUENCE 테이블에서 TEST_SEQ라는 값이 sequence_name(PK)에 입력되며 next_val에 시퀀스 값이 1씩 올라가는 식으로 Generator를 생성해서 매핑했다.
위와 같은 기본 키 생성 전략들이 있지만 JPA에서는 AUTO 전략을 기본으로 제공한다. 데이터베이스 방언에 따라 전략을 다르게 제공하는 방식인데 오라클은 SEQUENCE, MySQL은 IDENTITY를 선택한다.
@Id @GeneratedValue
private Long id;
보통 실무에서는 위와 같이 AUTO 전략을 많이 사용한다. 데이터베이스가 바뀌어 방언이 바뀌게 되더라도 자동으로 전략을 선택해주기 때문이다.
엔티티와 테이블을 매핑했으면 엔티티 안의 필드들과 테이블 안의 컬럼들을 매핑해야한다. 매핑하기 위한 어노테이션은 아래의 표와 같다.
매핑 어노테이션 | 설명 |
---|---|
@Column | 컬럼을 매핑 |
@Enumerated | 자바의 enum 타입 매핑 |
@Temporal | 날짜 타입 매핑 |
@Lob | BLOB/CLOB 타입 매핑 |
@Transient | 매핑하지 않는다 |
@Access | 접근 방식 지정 |
이 표의 내용을 하나씩 짚어보자.
컬럼을 매핑하는 어노테이션이다. DDL을 사용하지 않는다면 기본적으로 사용할 속성은 name 속성뿐이다. 매핑할 컬럼명을 지정하는 속성이다.
하지만 nullable 속성은 예외적이다. nullable 속성은 기본적으로 true이고 false로 설정할 경우 not null 제약조건이 추가된다.
그런데 필드의 타입에 따라 nullable을 명시해줘야하는 경우가 발생할 수 있다. int 타입의 필드를 사용하는 경우에는 자바 기본타입이 때문에 null 값이 들어갈 수 없다. 그렇기 때문에 @Column(name = "id", nullable = false)와 같이 not null 제약조건을 추가해줘야한다.
그게 아니라면 애초에 int 타입 대신 Integer 타입을 사용하는 방법도 있다.
Enumerated 어노테이션은 자바의 enum 타입을 매핑하는 어노테이션이다. value 속성을 가지며 EnumType.ORDINAL, EnumType.STRING을 골라서 입력할 수 있다.
전자의 경우에는 enum 객체에서 순서를 가지고 데이터베이스에 저장하는 방식이고, 후자는 내용을 가지고 데이터베이스에 저장하는 방식이다.
Temporal 어노테이션은 날짜 타입을 매핑할 때 사용한다. value 속성을 가지며 DATE, TIME, TIMESTAMP 값을 입력할 수 있다.
DATE는 날짜, TIME은 시간, TIMESTAMP는 날짜와 시간과 매핑된다. 필드의 타입은 Date 타입을 사용하면 된다.
데이터베이스의 BLOB, CLOB 타입과 매핑할 때 사용한다.
CLOB은 문자형 데이터를 저장하는 타입이고, 바이너리 데이터를 저장하는 타입은 BLOB이다.
이 필드를 매핑하지 않는 경우에 사용하는 어노테이션이다. 엔티티의 필드를 일종을 저장 공간으로 임시로 사용하는 경우에 사용한다.
JPA가 엔티티에 접근하는 방식을 지정한다.
@Entity
@Access(AccessType.FIELD) / @Access(AccessType.PROPERTY)
위의 두 가지 방식으로 사용한다. 전자는 필드에 직접 접근하는 방식이고, 후자는 getter를 사용해서 접근하는 방식이다.
Id 어노테이션이 필드에 있으면 field 방식으로, getter에 있으면 property방식으로 자동으로 설정되므로 생략가능하다.
또한 필드에 Access 어노테이션을 명시하여 해당 필드에만 예외적으로 접근 방식을 달리할 수도 있다.