JPA의 다양한 매핑 어노테이션 4가지 분류
- 객체와 테이블 매핑 : @Entity, @Table
- 기본 키 매핑 : @Id
- 필드와 컬럼 매핑 : @Column
- 연관관계 매핑 : @ManyToOne, @JoinColumn
XML을 사용한 맵핑과 어노테이션을 사용한 맵핑 방식이 있지만, 직관적이고, 쉬운 어노테이션 맵핑을 보겠다.
@Entity
JPA를 사용해서 테이블과 맵핑할 클래스는 @Entity를 필수로 붙여야 한다.
@Entity가 붙은 클래스는 JPA가 관리하는 것으로, 엔티티라고 부른다.
속성
- name : JPA에서 사용할 엔티티 이름을 지정
주의사항
- 기본 생성자는 필수(public or protected)
- final 클래스, enum, interface, inner 클래스에는 사용 불가
- 저장할 필드에 final을 사용하면 안된다.
※JPA가 엔티티 객체를 만들 때 기본생성자를 사용하기 때문에 기본 생성자는 반드시 있어야한다.
@Table
엔티티와 맵핑할 테이블을 지정한다.
속성
- name : 매핑할 테이블 이름
- catalog : catalog 기능이 있는 DB에서 catalog 맵핑
- schema : schema 기능이 있는 DB에서 schema 맵핑
- uniqueConstraints : DDL 생성 시에 유니크 제약조건을 만든다. 2개 이상의 복합 유니크 제약조건도 만들 수 있다. 스키마 자동생성 기능을 사용해서 DDL을 만들 때만 사용된다.
회원 관리 프로그램의 요구사항
1. 회원은 일반 회원과 관리자로 구분한다.
2. 회원가입일과 수정일이 있어야 한다.
3. 회원을 설명할 수 있는 필드가 있어야 한다. 필드의 길이 제한은 없다.
@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
}
roleType은 enum을 사용해서 회원의 타입을 구분했다. enum을 사용하려면 @Enumerated을 사용해야한다.
날짜 타입은 @Temporal을 사용해서 맵핑한다.
길이 제한이 없는 description의 경우는 CLOB 타입으로 저장해야한다. 이를 위해서 @Lob을 사용하면 CLOB, BLOB 타입을 맵핑할 수 있다.
데이터베이스 스키마 자동 생성
JPA는 DB 스키마를 자동으로 생성하는 기능을 지원한다. 클래스 맵핑 정보로 어떤 테이블에 어떤 칼럼을 사용하는지 알 수 있다.
맵핑 정보와 DB dialect을 사용해서 DB 스키마를 생성한다.
스키마 자동 생성 기능을 사용하면 애플리케이션 실행 시점에 DB table이 자동으로 생성되므로 개발자가 테이블을 직접 생성하는 수고를 덜 수 있다.
하지만 스키마 자동 생성 기능이 만든 DDL은 운영환경에서 사용할 만큼 완벽하지는 않다고 한다. 개발환경에서 사용하거나 매핑을 어떻게 해야 하는지 참고하는 정도로만 사용하는 것이 좋다고 한다.
ddl auto 속성
- create : 기존 테이블을 삭제하고 새로 생성한다. DROP + CREATE
- create-drop : create 속성에 추가로 애플리케이션을 종료할 때 생성한 DDL을 제거한다. DROP + CREATE + DROP
- update : 데이터베이스 테이블과 엔티티 맵핑정보를 비교해서 변경 사항만 수정한다.
- validate : 데이터베이스 테이블과 엔티티 맵핑 정보를 비교해서 차이가 있으면 경고를 남기고 애플리케이션을 실행하지 않는다. DDL을 수정하지는 않는다.
- none : 자동 생성 기능을 사용하지 않으려면 ddl auto 속성 자체를 삭제하거나 유효하지 않은 옵션 값을 준다.(none은 유효하지 않은 옵 션 값)
속성 사용 주의
운영서버에서 create, create-drop, update같은 DDL 수정 옵션은 절대 사용하면 안된다.
개발 환경에 따른 추천은 아래와 같다.
개발 초기 단계 : create, update
초기화 상태로 자동화된 테스트를 진행하는 개발자 환경과 CI 서버 : create 또는 create-drop
테스트 서버 : update, validate
스테이징과 운영 서버 : validate 또는 none
JAVA는 관례상 카멜 케이스를 사용하는데 DB는 관례상 언더스코어를 사용한다. 이를 개발자가 직접 맵핑하는 방법도 있지만, hibernate가 제공하는 hibernate.ejb.naming_strategy 속성을 사용해서 카멜케이스를 자동으로 언더스코어로 맵핑 할 수 있다.
DDL 생성 기능
@Entity
@Table
public class Member {
@Id
@Column(name = "ID")
private String id;
@Column(name="NAME", nullable = false, length = 10)
private String username;
}
@Column 매핑정보의 nullable 속성 값을 false로 지정하면 자동생성되는 DDL에 not null 제약조건을 추가할 수 있다.
length 속성을 사용하면 자동 생성 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")
private String username;
private Integer age;
}
위의 유니크 제약조건은 아래의 DDL을 실행한 것이다.
ALTER TABLE MEMBER ADD CONSTRAINT NAME_AGE_UNIQUE UNIQUE(NAME, AGE)
프로젝트에 적용했던 OauthMember 엔티티의 일부분이다.
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = PROTECTED)
@Table(name = "oauth_member",
uniqueConstraints = {
@UniqueConstraint(
name = "oauth_id_unique",
columnNames = {
"oauth_server_id",
"oauth_server"
}
),
}
)
public class OauthMember {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Embedded
private OauthId oauthId;
기본 키 매핑
오라클의 시퀀스 오브젝트나 MySQL의 auto increment 같은 기능을 사용해서 생성된 값을 기본키로 사용하려면 두 가지 전략이 있다.
- 직접 할당 : 기본키를 애플리케이션에서 직접 할당한다.
- 자동 생성 : 대리 키 사용 방식
IDENTITY : 기본키 생성을 데이터베이스에 위임한다.
SEQUENCE : 데이터베이스 시퀀스를 사용해서 기본 키를 할당한다.
TABLE : 키 생성 테이블을 사용한다.
자동생성 전략이 다양한 이유는 데이터베이스 벤더마다 지원하는 방식이 다르기 때문이다.
TABLE 전략은 키 생성용 테이블을 활용하므로 모든 데이터베이스에서 사용할 수 있다.
자동생성 전략을 사용하기 위해서는 @GeneratedValue를 사용한다.
기본키 직접 할당 전략
기본키 직접 할당 전략은 persist()로 엔티티를 저장하기 전에 애플리케이션에 기본 키를 직접 할당하는 방법.
Board board = new Board();
board.setId("id1");
em.persist(board);
IDENTITY 전략
기본 키 생성을 데이터베이스에 위임하는 전략.
주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용.
CREATE TABLE BOARD(
ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
DATA VARCHAR(255)
)
IDENTITY 전략 최적화
IDENTITY 전략은 데이터를 데이터베이스에 INSERT 한 후에 기본 키 값을 조회할 수 있다. 따라서 엔티티에 식별자 값을 할당하려면 JPA는 추가로 데이터베이스를 조회해야 한다.
Statement.getGeneratedKeys()를 사용하면 데이터를 저장하면서 동시에 생성된 기본 키 값도 얻어 올 수 있다. 이를 통해 DB와 한번만 통신한다.
IDENTITY 전략은 엔티티가 영속 상태가 되려면 식별자가 필요한데, 이 경우는 엔티티를 DB에 저장해야 식별자를 구할 수 있음으로 persist()를 호출한 즉시 INSERT SQL이 DB에 전달 되어야한다. 따라서 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다.
SEQUENCE 전략
데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트.
오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용할 수 있다.
CREATE TABLE BOARD(
ID BIGINT NOT NULL PRIMARY KEY,
DATA VARCHAR(255)
)
CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1;
@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를 사용해서 BOARD_SEQ_GENERATOR라는 시퀀스 생성기를 등록
시퀀스 생성기를 실제 데이터베이스의 BOARD_SEQ 시퀀스와 맵핑한다.
사용 코드는 IDENTITY와 같지만 내부 동작 방식은 다르다.
persist()를 호출할 때 먼저 DB에서 시퀀스를 사용해서 식별자를 조회한다. 그리고 조회한 식별자를 엔티티에 할당한 후에 엔티티를 영속성 컨텍스트에 저장해서 영속 상태로 만든다.
이후 트랜잭션을 커밋해서 플러시가 일어나면 엔티티를 DB에 저장한다.
@SequenceGenerator
속성
- name : 식별자 생성기 이름
- sequenceName : 데이터베이스에 등록되어 있는 시퀀스 이름
- initialValue : DDL 생성 시에만 사용. 시퀀스 DDL을 생성할 때 처음 시작하는 수를 지정.
- allocationSize : 시퀀스 한번 호출에 증가하는 수(성능 최적화에 사용) 기본값 : 50
- catalog, schema : 데이터베이스 catalog, schema 이름
alocationSize가 기본값이 50이다. 호출 시마다 50씩 증가한다. 기본 값이 50인 이유는 최적화 때문이라고 한다.
데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값을 반드시 1로 설정해야 한다.
50씩 증가하는 이유는 한번에 시퀀스 값을 증가시키고 나서 그만큼 메모리에 시퀀스 값을 할당한다. 예를 들어 allocationSize 값이 50이면 시퀀스를 한 번에 50 증가시킨 다음에 1~50까지는 메모리에서 식별자를 할당한다. 그리고 51이 되면 시퀀스 값을 100으로 증가시킨 다음 51~100까지 메모리에서 식별자를 할당한다.
이 방식은 시퀀스 값을 선점하여 여러 JVM이 동시에 동작해도 기본키 값이 충돌하지 않는 장점이 있다.
@Entity
public class Board {
@Id
@GenerateValue(...)
@SequenceGenerator(...)
private Long id;
}
TABLE 전략
키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략.
테이블을 사용하므로 모든 데이터베이스에 적용할 수 있다.
Table 전략을 사용하려면 키 생성 용도로 사용할 테이블을 만들어야 한다.
create table MY_SEQUENCES(
sequence_name varchar(255) not null,
next_val bigint,
primary key(sequence_name)
)
sequence_name을 시퀀스 이름으로 사용하고, next_val을 시퀀스 값으로 사용한다.
@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로 등록한다. BOARD_SEQ_GENERATOR를 키 생성기로 등록했다.
MY_SEQUENCES를 키 생성용 테이블로 맵핑한다.
id의 키 생성기를 BOARD_SEQ_GENERATOR로 지정했다.
@TableGenerator
속성
- name : 식별자 생성기 이름
- table : 키생성 테이블명, hibernate_sequences
- pkColumnName : 시퀀스 컬럼명, sequence_name
- valueColumnName : 시퀀스 값 컬럼명, next_val
- pkColumnValue : 키로 사용할 값, 이름 기본값=엔티티 이름
- initialValue : 초기값, 마지막으로 생성된 값이 기준, 0
- allocationSize : 시퀀스 한 번 호출에 증가하는 수, 50
- catalog, schema : 데이터베이스 catalog, schema 이름
- uniqueConstraints(DDL) : 유니크 제약 조건을 지정할 수 있다.
맵핑할 DDL, 테이블명 table
pkColumnName | valueColumnName |
pkColumnValue | initialValue |
AUTO 전략
데이터베이스의 종류도 많고 기본키를 만드는 방법도 다양하다. GenerationType.AUTO는 선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택한다. 예를 들어 오라클을 선택하면 SEQUENCE를 MySQL을 선택하면 IDENTITY를 사용한다.
@Entity
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
}
GeneratedValue의 기본값은 AUTO이다.
따라서 아래와 같은 결과와 같다.
@Id @GeneratedValue
private Long id;
AUTO 전략의 장점은 데이터베이스를 변경해도 코드를 수정할 필요가 없다는 것이다.
특히 키 생성 전략이 아직 확정되지 않은 개발 초기 단계나 프로토타입 개발 시 편리하게 사용할 수 있다.
AUTO를 사용할 때 SEQUENCE나 TABLE 전략이 선택되면 시퀀스나 키 생성용 테이블을 미리 만들어 두어야 한다. 만약 스키마 자동 생성 기능을 사용한다면 하이버네이트가 기본값을 사용해서 적절한 시퀀스나 키 생성용 테이블을 만들어준다.
자연키보다는 대리키를 사용하자. 비즈니스 환경은 언젠가 변하고, 일관성과 계속해서 변하는 비즈니스 요구사항에 대처하기 위해서는 대리키의 사용이 좋다.
'Spring' 카테고리의 다른 글
연관관계 매핑 (0) | 2024.10.03 |
---|---|
필드와 컬럼 맵핑 (1) | 2024.10.03 |
영속성 관리 (1) | 2024.09.22 |
JPA Application 개발 (0) | 2024.09.21 |
데이터베이스 방언(dialect) (0) | 2024.09.21 |