JPA를 사용하는데 가장 중요항 일은 엔티티와 테이블을 정확히 매핑하는 것이다. 따라서 매핑 애노테이션을 숙지하고 사용해야 한다. JPA는 다양한 매핑 애노테이션을 지원하는데 크게 4가지로 분류할 수 있다.
@Entity
, @Table
@Id
@Column
@ManyToMany
, @JoinColumn
필드와 컬럼을 매핑하는 애노테이션은 기능을 하나씩 설명하기에는 내용이 많으므로 필요할 때 찾아볼 수 있도록 정리하였다.
@Entity
@Table(name="MEMBER")
public class Member {
@Id @Column(name="ID)
private String id;
@Column(name="NAME")
private String username;
private Integer age;
...
}
해당 애노테이션이 붙은 클래스는 JPA가 관리한다. 기본값을 설정하지 않으면 클래스 이름을 그대로 사용한다.
엔티티와 매핑할 테이블을 지정한다. 생략하면 매핑한 엔티티 이름을 테이블 이름으로 사용한다.
회원 엔티티에서 이름은 필수로 입력되어야 하고, 10자를 초과하면 안된다는 제약 조건이 추가되었다고 가정하자. 이때, 스키마 자동 생성하기를 통해 만들어지는 DDL에 이 제약 조건을 추가해보자.
nullable = false
maxLength = 10
@Entity
@Table(name="MEMBER")
public class Member {
@Id @Column(name="id")
private String id;
@Column(name="NAME", nullable=false, length=10)
private String username;
...
}
이번에는 유니크 제약 조건을 만들어주는 @Table
의 uniqueConstrains
속성을 살펴보자.
@Entity
@Table(name="MEMBER", uniqueConstraints={
@UniqueConstraint(
name="NAME_AGE_UNIQUE",
columnNames={"NAME", "AGE"})
})
public class Member {
@Id @Column(name="ID")
private String id;
@Column(name="NAME", nullable=false, length=10)
private String username;
...
}
JPA가 제공하는 DB 기본 키 생성 전략은 다음과 같다.
자동 생성 전략이 다양한 이유는 DB벤더마다 지원하는 방식이 다르기 때문이다. 예를 들면 오라클은 시퀀스를 제공하지만 MySQL은 제공하지 않는다. 대신 기본 키값을 자동으로 채워주는 AUTO_INCREMENT
기능을 제공한다. 따라서 각 키 생성 전략들을 사용하는 DB에 의존한다. TABLE 전략은 키 생성용 테이블을 하나 만들어두고 마치 시퀀스처럼 사용하는 방식이다. 이 전략은 모든 DB에서 사용할 수 있다.
기본키를 직접 할당하려면 @Id
만 사용하면 되고 자동 생성 전략을 사용하려면 @GeneratedValue
를 추가하고 원하는 키생성 전략을 사용하면 된다. 선택하지 않으면 DB 종류에 따라 자동으로 생성한다.
@Id
적용이 가능한 자바 타입은 다음과 같다.
기본 키 생성을 DB에 위임하는 전략이다. 주로 MySQL, PostgresQL, SQL Server 등에서 사용된다. IDENTITY 전략은 DB에 값을 저장하고 나서야 기본 키값을 구할 수 있을 때 사용한다.
IDENTITY 전략을 사용하려면 아래와 같이 속성을 지정하면 된다.
@GeneratedValue(strategy=GenerationType.IDENTITY)
Statement.getGeneratedKeys()
를 사용하면 데이터를 저장하면서 동시에 기본 키값도 얻어올 수 있다. → 하이버네이트가 사용em.persist()
호출시 즉시 SQL이 DB에 전달된다. → 쓰기 지연 동작이 발생하지 않는다.DB 시퀀스는 유일한 값을 순서대로 생성하는 특별한 DB 오브젝트다. 이 전략은 이 시퀀스를 사용하여 기본키를 생성한다. 오라클, PostgresQL, H2 등에서 사용할 수 있다.
CREATE TABLE board (
id BIGINT NOT NULL PRIMARY KEY,
data VARCHAR(255)
)
//시퀀스 생성: 시퀀스명 -> BOARD_SEQ
@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;
...
}
시퀀스 사용 코드는 IDENTITY 전략과 같지만 내부 동작 방식은 완전히 다르다. 시퀀스 방식은 em.persist()
를 호출할 때 먼저 DB 시퀀스를 사용해서 식별자를 조회한다. 그리고 조회한 식별자를 엔티티에 할당한 후에 이를 영속성 컨텍스트에 저장한다. 이후 트랜잭션을 커밋해서 플러시가 일어나면 DB에 저장한다.
위에서 본 것처럼 DB는 종류도 많고 기본 키를 만드는 방법도 다양하다. GenerationType.AUTO
는 선택한 DB 방언에 따라 위의 전략 중 하나를 자동으로 선택한다. 이 전략의 장점은 DB를 변경해도 코드를 수정할 필요가 없다는 것이다. 특히 키생성 전략이 아직 확정되지 않은 개발 초기 단계나 프로토타입 개발시 편리하다.
em.persist()
를 호출하기 전에 애플리케이션에서 직접 실별자 값을 할당해야 한다.객체 필드를 테이블 컬럼에 매핑한다. 가장 많이 사용되고 기능도 많다. 속성중에 주로 name
, nullable
이 주로 사용되고 나머지는 잘 사용되지 않는 편이다.
@Column
생략시 대부분 속성의 기본값이 적용되는데, 자바 기본 타입일 때는 nullable 속성에 예외가 있다.
int age // null 불가능
위에서 보듯 자바 기본 타입은 null 값을 입력할 수 없다. Integer와 같이 객체 타입일 때만 null이 허용된다. 따라서 자바 기본타입으로 테이블 생성시 not null
제약 조건을 추가하는 것이 안전하다.
JPA는 이를 고려하여 자동으로 기본타입에는 not null
제약 조건을 추가한다. 반면 Integer와 같은 래퍼타입이면 설정하지 않는다.
그런데 @Column
을 사용하면 기본값이 nullable=true
이므로 기본 자료형에는 nullable=false
로 설정하는 것이 안전하다.
enum RoleType {
ADMIN, USER
}
@Enumerated(EnumType.STRING)
private RoleType roleType;
날짜 타입을 매핑할 때 사용한다. 생략시 timestamp 로 정의된다.
@Temporal(TemporalType.DATE)
private Date date;
@Temporal(TemporalType.TIME)
private Date time;
@Temporal(TemporalType.TIMESTAMP)
private Date timestamp;
DB의 BLOB, CLOB 타입과 매핑한다. 여기에는 지정할 수 있는 속성이 없다. 대신에 매핑하는 필드 타입이 문자면 CLOB, 나머지는 BLOB로 매핑한다.
@Lob
private String lobString;
@Lob
private byte[] lobByte;
해당 애노테이션이 적용되는 필드는 매핑되지 않는다. 따라서 DB에 저장하지 않고 조회하지도 않는다. 객체에 임시로 어떤 값을 보관하고 싶을 때 사용한다.