JPA에서는 매핑 어노테이션을 사용해 엔티티와 테이블을 매핑한다. 다양한 매핑 어노테이션들은 아래와 같이 구분해 볼 수 있다.
- 객체와 테이블 매핑:
@Entity,@Table- 기본 키 매핑:
@Id- 필드와 컬럼 매핑:
@Column- 연관관계 매핑:
@ManyToOne,@JoinColumn
매핑 정보는 어노테이션 외에도 XML을 사용해 구성할 수도 있다고 한다.
@Entity:JPA를 사용해 테이블과 매핑할 클래스에 붙이는 어노테이션으로 필수적이다. 이 어노테이션이 붙은 클래스는 JPA가 관리를 하게 된다.
@Entity
public class Member {}
| 속성 | 기능 |
|---|---|
| name | JPA에서 사용할 엔티티 이름을 정함. 기본값은 클래스 이름. |
@Entity 어노테이션 사용 시 주의사항이 있다.
@Table:엔티티와 매핑할 테이블을 지정해주는 어노테이션이다. 생략될 시에는 매핑한 엔티티 이름을 테이블 이름으로 사용한다.
| 속성 | 기능 |
|---|---|
| name | 매핑할 테이블의 이름 |
| catalog | catalog 기능이 있는 DB에서 catalog를 매핑 |
| schema | schema 기능이 있는 DB에서 schema를 매핑 |
@Entity
@Table(name="MEMEBER")
public class Member {}
요구사항
- 회원은 일반 회원과 관리자로 구분
- 회원 가입일과 수정일 존재
- 회원을 설명할 수 있는 필드 존재 (길이 제한 없음)
@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 enum RoleType {
ADMIN, USER
}
@Enumerated:자바의 enum을 사용해 회원의 타입을 구분한다. 자바의 enum을 사용하려면 @Enumerated 어노테이션으로 매핑해야한다.
@Temporal:자바의 날짜 타입을 사용할 때 매핑하는 어노테이션.
@LOB:CLOB, BLOB 타입의 매핑을 위해 사용하는 어노테이션. 위에서는 길이 제한이 없는 회원 설명 필드의 매핑을 위해 사용됐다.
JPA에서는 클래스 매핑 정보를 통해 어떤 테이블의 어떤 컬럼을 사용하는지 알 수 있다. 이 매핑 정보와 데이터베이스 방언을 사용해 JPA에서는 데이터베이스 스키마를 생성할 수 있다.
스키마 자동 생성 기능을 사용하기 위해서는 persistence.xml에 아래의 속성을 추가해줘야한다.
<property name = "hibernate.hbm2ddl.auto" value="create" />
해당 속성은 애플리케이션 실행 시점에 데이터베이스 테이블을 자동으로 생성하는데, 이때 기존의 테이블을 삭제하고 다시 생성한다. 위의 속성에는 create, create-drop, update, validate, none이 존재한다.
다만 DLL을 수정하는 옵션은 운영서버에서 절대 사용하면 안된다. 운영 중인 데이터베이스의 테이블이나 컬럼을 삭제하는 일이 벌어질 수 있다.
스키마 자동 생성을 통해 자동으로 생성되는 DDL(데이터 정의어)는 지정한 데이터베이스 방언에 따라 달라진다. 다만, 스키마 자동 생성 기능의 DDL은 완벽하지 않으니 운영환경에서의 사용은 지양하는 것이 좋다.
스키마 자동 생성을 통해 만들어지는 DDL에 제약조건을 추가할 수 있다.
@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;
}
nullable: 해당 컬럼에 null이 들어가지 않도록 설정할 수 있음. (not null)length: 해당 컬럼의 데이터의 길이를 제한하는 제약 조건을 설정할 수 있음.uniqueConstraints: 유니크 제약조건을 설정. 위의 코드에서는 NAME과 AGE를 유니크한 컬럼이 되도록 설정.@Id기본키(primary key)의 매핑을 위해 사용하는 어노테이션으로. 기본 키를 애플리케이션에서 직접 할당할 때 사용한다.
JPA 제공 데이터베이스 기본 키 생성 전략
- 직접 할당: 기본 키를 애플리케이션에서 직접 할당
- 자동 생성: 대리 키 사용 방식
IDENTITY: 기본 키 생성을 데이터베이스에 위임SEQUENCE: 데이터베이스 시퀀스를 사용해 기본 키를 할당TABLE: 키 생성 테이블을 이용
자동 생성 전략이 다양한 이유는 데이터베이스 벤더마다 지원하는 방식이 다르기 때문. MySQL은 기본 키 값을 자동으로 채우는 AUTO_INCREMENT 기능을 제공.
@Id: 기본 키 직접 할당을 위해서는 해당 어노테이션을 사용하면 된다. 적용 가능한 자바 타입으로는 기본형, 래퍼(Wrapper)형, 문자열, 날짜 등이 있다.
해당 방식은 em.persist()를 사용해 엔티티를 저장하기 전 애플리케이션에서 기본 키를 직접 할당하는 방식이다.
Board board = new Board();
board.setId("id1");
em.persist(board);
만약 식별자 값 없이 저장하게 되면 예외가 발생한다.
IDENTITY 전략은 기본 키 생성을 데이터베이스에 위임하는 전략이다.
주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용한다고한다. 예를 들면 MySQL의 AUTO_INCREMENT 기능을 통해 데이터베이스에서 식별자 값을 할당하지 않아도 자동으로 생성해준다.
데이터베이스에서 값을 저장할 때 식별자 값을 저장하지 않아도 순서대로 값을 채워주게된다.
해당 전략은 데이터베이스에 값을 저장하고 나서야 기본 키 값을 구할 수 있을 때 사용한다. 직접 할당 전략과는 다르게 @Id 어노테이션과 함께 @GeneratedValue 어노테이션을 사용하고 식별자 생성 전략을 선택해야할 필요성이 있다.
IDENTITY 전략에서는 @GeneratedValue의 strategy 속성 값을 GenerationType.IDENTITY로 지정하면 된다. JPA에서는 그럼 기본 키 값을 얻어오기 위해 데이터베이스를 추가로 조회하게된다.
@Id
@GeneratedValue( strategy = GenerationType.IDENTITY)
private Long id;
이때 식별자 값은 데이터를 데이터베이스에 저장하는 시점에 데이터베이스가 생성한 값을 JPA가 조회하는 식으로 얻어온다.
영속 상태가 되려면 식별자 값이 필요하기에 이 전략에서는 em.persist() 호출 즉시 INSERT SQL이 데이터베이스에 전달되어, 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다.
데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트이다. 해당 전략을 사용하기 위해서는 시퀀스를 생성해야한다.
오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용할 수 있다.
CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1;
위 처럼 시퀀스를 생성하고,
@SequenceGenerator (
name = "BOARD_SEQ_GENERATOR",
sequenceName = "BOARD_SEQ", //매핑할 데이터베이스 시퀀스 이름
initialValue = 1, allocationSize = 1)
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "BOARD_SEQ_GENERATOR")
private Long id;
@SequenceGenerator:시퀀스 생성기를 등록하기 위한 어노테이션으로 사용할 데이터베이스 시퀀스 매핑을 위해 사용한다. sequenceName을 앞에서 생성한 데이터베이스 시퀀스의 이름으로 지정한다.
JPA에서는 이 시퀀스 생성기를 실제 데이터베이스의 BOARD_SEQ 시퀀스와 매핑하게된다.
시퀀스 전략의 경우 em.persist() 호출 시 데이터베이스 시퀀스를 사용해 식별자를 조회한다. 이렇게 조회한 식별자를 엔티티에 할당하고 이 엔티티를 영속성 컨텍스트에 저장하기에, 이후 트랜잭션을 커밋하고 플러시할 때 엔티티를 데이터베이스에 저장할 수 있다.
테이블 전략은 키 생성 전용 테이블을 하나 만들어 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략이다. 이 경우에도 시퀀스 전략처럼 키 생성 용도로 사용할 테이블을 먼저 만들어야한다.
@TableGenerator (
name = "BOARD_SEQ_GENERATOR",
table = "MY_SEQUENCES",
pkColumnValue = "BOARD_SEQ", allocationSize = 1
)
@Id
@GeneratedValue (strategy = GenerationType.TABLE,
generator = "BOARD_SEQ_GENERATOR")
private Long id;
@TableGenerator:테이블 키 생성기를 등록하기 위한 어노테이션. 테이블 키 생성기의 이름을 등록하고, 미리 생성해둔 키 생성용 테이블을 매핑한다.
테이블 전략 사용을 위해서는 @GeneratedValue의 strategy를 위처럼 설정해줘야한다.
처음에 읽었을 때는 감이 잘 안왔는데 결과 테이블을 살펴보니, 특정 테이블 하나만을 위한 키가 아니라 여러 테이블의 키를 한 번에 여기서 관리하는 느낌이다. 예를 들면 게시글 테이블의 다음 값이 5, 댓글은 10, 이런 식으로 모아서 관리하고 여기서 찾아서 기본 키를 할당하는 듯 하다.
GenerationType.AUTO를 사용해 전략을 지정해주면 데이터베이스 방언에 따라 앞서 나온 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택하게된다.
이 전략의 장점은 데이터베이스를 변경해도 코드를 수정할 필요가 없다는 점이다.
다만, SEQUENCE나 TABLE 전략이 선택될 경우에는 시퀀스나 테이블을 미리 만들어두어야한다. 이 부분도 스키마 자동 생성기능을 사용하면 하이버네이트에서 기본값을 사용해 적절한 시퀀스나 테이블을 만들어주긴한다.
정리
- 직접할당:
em.persist()호출 전 애플리케이션에서 식별자 값을 직접 할당. 없을 경우 예외 발생- SEQUENCE: 시퀀스에서 식별자 값을 획득 후 영속성 컨텍스트에 저장
- TABLE: 시퀀스 생성용 테이블에서 식별자 값 획득 후 영속성 컨텍스트에 저장
- IDENTITY: 데이터베이스에 엔티티를 저장해 식별자 값 획득 후 영속성 컨텍스트에 저장
식별자 선택을 할 때에는 자연 키 보다는 대리 키를 사용하는 편이 좋다고 한다. 자연 키는 아무리 잘 고른다 해도 예기치 못하게 변경이 일어날 수 있는 상황이 많고, 널값이 들어가는 경우도 있기 때문이다.
@Column객체 필드를 테이블의 컬럼에 매핑하는 어노테이션으로 가장 많이 사용. 속성 중에서는 name, nullable이 주로 사용된다.
@Enumerated자바의 enum 타입을 매핑할 때 사용된다.
EnumType.ORDINAL: enum에 정의된 순서대로 데이터베이스에 인덱스 같은 값이 저장된다.EnumType.STRING: enum에 정의된 이름이 그대로 데이터베이스에 저장된다. 수정이나 순서 변경 등이 발생해도 안전하다는 장점이 존재하지만 저장되는 크기가 크다.@Temporal날짜 타입(java.util.Date, java.tuil.Calendar)을 매핑할 때 사용된다. 해당 어노테이션 생략 시 자바의 Date와 유사한 timestamp로 정의되게 된다.
@Lob지정할 수 있는 속성이 없다. 다만, 매핑 필드 타입이 문자면 CLOB으로 나머지는 BLOB으로 매핑된다.
@Transient해당 필드는 매핑을 하지 않는다. 데이터베이스에 저장하지 않고 조회하지 도 않는, 객체에서 임시로 어떤 값을 보관하기 위해서 사용하는 어노테이션이다.
@AccessJPA가 엔티티의 데이터에 접근하는 방식을 지정한다.
AccessType.FIELD로 지정하며 필드에 직접 접근한다.AccessType.PROPERTY로 지정하며 접근자(getter)를 사용한다.//필드 접근
@Entity
@Access (AccessType.FIELD)
public class Member{
@Id
private String id;
}
위의 경우 @Id를 필드에 붙여주었기에 @Access (AccessType.FIELD)로 설정한 것과 같기에 어노테이션을 생략해도 된다.
//프로퍼티 접근
@Entity
@Access (AccessType.PROPERTY)
public class Member{
private String id;
private String data1;
@Id
public String getData1() {
return data1;
}
}
위의 경우 @Id를 프로퍼티에 붙여주었기에 @Access (AccessType.PROPERTY)로 설정한 것과 같아 어노테이션을 생략해도 된다.