JPA를 사용하는데 가장 중요한 일은 엔티티와 테이블을 정확히 매핑하는 것이다.
JPA는 다양한 매핑 어노테이션을 지원하는데 크게 4가지로 분류할 수 있다.
- 객체와 테이블 매핑 :
@Entity
,@Table
- 기본 키 매핑 :
@Id
- 필드와 컬럼 매핑 :
@Column
- 연관관계 매핑 :
@ManyToOne
,@JoinColumn
객체와 테이블 매핑
@Entity
JPA를 사용해서 테이블과 매핑할 클래스는 @Entity
어노테이션을 필수로 붙여야한다.
속성
name
: JPA에서 사용할 엔티티 이름을 지정한다. 보통 기본값인 클래스 이름을 사용한다. 만약 패키지에 이름이 같은 엔티티 클래스가 있다면 이름을 지정해서 충돌하지 않도록 해야 한다.
주의사항
- 기본 생성자는 필수다(파라미터가 없는 public 또는 protected 생성자).
- final 클래스, enum, interface, inner 클래스에는 사용할 수 없다.
- 저장할 필드에 final을 사용하면 안된다.
JPA가 엔티티 객체를 생성할 때 기본 생성자를 사용하므로 이 생성자는 반드시 있어야 한다.
@Table
@Table
은 엔티티와 매핑할 테이블을 지정한다. 생략하면 매핑한 엔티티 이름을 테이블 이름으로 사용한다.
속성
name
: 매핑할 테이블 이름- 생략하면 엔티티 이름을 테이블 이름으로 사용한다.
catalog
: catalog 기능이 있는 데이터베이스에서 catalog를 매핑한다.schema
: schema 기능이 있는 데이터베이스에서 schema를 매핑한다.uniqueConstraints (DDL)
: DDL 생성 시에 유니크 제약조건을 만든다. 스키마 자동 생성 기능을 사용해서 DDL을 만들 때만 사용된다.indexes
: index를 추가 할 수 있다.
스키마 자동 생성
JPA는 데이터베이스 스키마를 자동으로 생성하는 기능을 지원한다.
JPA는 매핑정보와 데이터베이스 방언을 사용해서 데이터베이스 스키마를 생성한다.
persistence.xml
에 다음 속성을 추가하자.
<property name="hibernate.hbm2ddl.auto" value="create">
이 속성을 추가하면 애플리케이션 실행 시점에 데이터베이스 테이블을 자동으로 생성한다.
참고로 hibernate.show_sql
속성을 true로 설정하면 콘솔에 실행되는 테이블 생성 DDL을 출력할 수 있다.
<property name="hibernate.show_sql" value="true">
속성
create
: 기존 테이블을 삭제하고 새로 생성한다. DROP + CREATEcreate-drop
: create 속성에 추가로 애플리케이션을 종료할 때 생성한 DDL을 제거한다. DROP + CREATE + DROPupdate
: 데이터베이스 테이블과 엔티티 매핑정보를 비교해서 변경 사항만 수정한다.validate
: 데이터베이스 테이블과 엔티티 매핑정보를 비교해서 차이가 있으면 경고를 남기고 애플리케이션을 실행하지 않는다. 이 설정은 DDL을 수정하지 않는다.none
: 자송 생성 기능을 사용하지 않으려면hibernate.hbm2ddl.auto
속성 자체를 삭제하거나 유효하지 않은 옵션 값을 주면 된다.(참고로 none은 유효하지 않은 옵션 값이다).
주의 사항
운영 서버에서 create, create-drop, update 처럼 DDL을 수정하는 옵션은 절대 사용하면 안 된다.
오직 개발 서버나 개발 단계에서만 사용해야 한다. 이 옵션들은 운영 중인 데이터베이스의 테이블이나 컬럼을 삭제할 수 있다.
개발 환경에 따른 추천 전략은 다음과 같다.
- 개발 초기 단계는 create 또는 update
- 초기화 상태로 자동화된 테스트를 진행하는 개발자 환경과 CI 서버는 create 또는 create-drop
- 테스트 서버는 update 또는 validate
- 스테이징과 운영 서버는 validate 또는 none
기본 키 매핑
- 직접 할당 : 기본 키를 애플리케이션에서 직접 할당한다.
- 자동 생성 : 대리 키 사용 방식
IDENTITY
: 기본 키 생성을 데이터베이스에 위임한다.SEQUENCE
: 데이터베이스 시퀀스를 사용해서 기본키를 할당한다.TABLE
: 키 생성 테이블을 사용한다.
자동 생성 전략이 이렇게 다양한 이유는 데이터베이스 벤더마다 지원하는 방식이 다르기 때문이다.
예를 들어 오라클 데이터베이스는 시퀀스를 제공하지만 MySQL은 시퀀스를 제공하지 않는다. 대신에 MySQL은 기본 키 값을 자동으로 채워주는 AUTO_INCREMENT 기능을 제공한다.
따라서 SEQUENCE나 IDENTITY 전략은 사용하는 데이터베이스에 의존한다.
기본 키 직접 할당 전략
기본 키를 직접 할당하려면 다음 코드와 같이 @Id
로 매핑하면 된다.
@Id
@Column(name = "id")
private String id;
기본 키 직접 할당 전략은 em.persist()
로 엔티티를 저장하기 전에 애플리케이션에서 기본 키를 직접 할당하는 방법이다.
Board borad = new Board();
board.setId("id1"); //기본 키 직접 할당
em.persist(board);
IDENTITY 전략
IDENTITY는 기본 키 생성을 데이터베이스에 위임하는 전략이다. 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용한다.
IDENTITY 전략은 AUTO_INCREMENT
처럼 데이터베이스에 값을 저장하고 나서야 기본 키 값을 구할 수 있을 때 사용한다.
개발자가 엔티티에 직접 식별자를 할당하면 @Id
어노테이션만 있으면 되지만 지금 처럼 식별자가 생성되는 경우에는 @GeneratedValue
의 strategy
속성 값을 GenerationType.IDENTITY
로 지정하면 된다.
@Entity
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
}
주의사항
엔티티가 영속 상태가 되려면 식별자가 반드시 필요하다. 그런데 IDENTITY 식별자 생성 전략은 엔티티를 데이터베이스에 저장해야 식별자를 구할 수 있으므로 em.persist()를 호출하는 즉시 INSERT SQL이 데이터베이스에 저장된다. 따라서 이 전략은 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다.
SEQUENCE 전략
데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트다. 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
@GeneraedValue(strategy = GenerationType.SEQUENCE,
generator = "BOARD_SEQ_GENERATOR")
private Long id;
}
동작 과정
em.persist()
를 호출할 때 먼저 데이터베이스 시퀀스를 사용해서 식별자를 조회.- 조회한 식별자를 엔티티에 할당한 후에 엔티티를 영속성 컨텍스트에 저장.
- 트랜잭션을 커밋해서 플러시가 일어나면 엔티티를 데이터베이스에 저장.
@SequenceGenerator 속성
name
: 식별자 생성기 이름- 필수
sequenceName
: 데이터베이스에 등록되어 있는 시퀀스 이름- 기본 값 : hibernate_sequence
initialValue
: 시퀀스 한 번 호출에 증가하는 수(성능 최적화에 사용됨)- 기본 값 : 1
allocationSize
: 시퀀스 한 번 호출에 증가하는 수(성능 최적화에 사용됨)- 기본 값 : 50
catalog
,schema
: 데이터베이스 catalog, schema 이름
주의 사항
SequenceGenerator.allocationSize
의 기본 값이 50인 것에 주의해야 한다. 기본값이 50인 이유는 최적화 때문인데 아래에서 설명한다. 데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값을 반드시 1로 설정해야 한다.
SEQUENCE 전략과 최적화
SEQUENCE 전략은 데이터베이스 시퀀스를 통해 식별자를 조회하는 추가 작업이 필요하다. 따라서 데이터베이스와 2번 통신한다.
1. 식별자를 구하려고 데이터베이스 시퀀스를 조회한다.
2, 조회한 시퀀스를 기본 키 값으로 사용해 데이터베이스에 저장한다.
JPA는 시퀀스에 접근하는 횟수를 줄이기 위해 @SequenceGenerator.allocationSize
를 사용한다. 간단히 말하자면 여기에 설정한 값만큼 한 번에 시퀀스 값을 증가시키고 나서 그만큼 메모리에 시퀀스 값을 할당한다.
예를 들어 allocationSize
값이 50이면 시퀀스를 한 번에 50 증가시킨 다음에 1~50까지는 메모리에서 식별자를 할당한다. 그리고 51이 되면 시퀀스 값을 100으로 증가시킨 다음 51~100까지 메모리에서 식별자를 할당한다.
이 최적화 방법은 시퀀스 값을 선점하므로 여러 JVM이 동시에 동작해도 기본 키 값이 충돌하지 않는 장점이 있다. 반면에 데이터베이스에 직접 접근해서 데이터를 등록할 때 시퀀스 값이 한 번에 많이 증가한다는 점을 염두해두어야한다. 이런 상황이 부담스럽고 INSERT 성능이 중요하지 않으면 allocationSize
의 값을 1로 설정하면 된다.
Table 전략
TABLE 전략은 키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략이다. 이 전략은 테이블을 사용하므로 모든 데이터베이스에 적용할 수 있다.
TABLE 전략 키 생성 DDL
create table MY_SEQUENCES (
sequence_name varchar(255) not null,
next_val bigint,
primary key (sequence_name)
)
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;
}
TABLE 전략은 시퀀스 대신에 테이블을 사용한다는 것만 제외하면 SEQUENCE 전략과 내부 동작 방식이 같다.
@TableGenerator 속성
name
: 식별자 생성기 이름- 필수
table
: 키생성 테이블명
- 기본 값 : hibernate_sequences
pkColumnName
: 시퀀스 컬럼명- 기본 값 : sequence_name
valueColomnName
: 시퀀스 값 컬럼명- 기본 값 : next_val
pkColumnValue
: 키로 사용할 값 이름- 엔티티 이름
initialValue
: 시퀀스 한 번 호출에 증가하는 수(성능 최적화에 사용됨)- 기본 값 : 50
allocationSize
: 시퀀스 한 번 호출에 증가하는 수(성능 최적화에 사용됨)- 기본 값 : 50
catalog
,schema
: 데이터베이스 catalog, schema 이름uniqueContraints(DDL)
: 유니크 제약 조건을 지정할 수 있다.
AUTO 전략
GenerationType.AUTO
는 선택한 데이터베이스 방언에 따라 IDENTITY,SEQUENCE, TABLE 전략 중 하나를 자동으로 선택한다. GeneratedValue.strategy
의 기본값은 AUTO다.
AUTO 전략의 장점은 데이터베이스를 변경해도 코드를 수정할 필요가 없다는 것이다. 특히 키 생성 전략이 아직 확정되지 않은 개발 초기 단계나 프로토타입 개발 시 편리하게 사용할 수 있다.
식별자 선택 전략
- 자연 키
- 비즈니스에 의미가 있는 키
- 예) 주민등록번호, 이메일, 전화번호
- 대리 키
- 비즈니스와 관련 없는 임의로 만들어진 키, 대체 키로도 불린다.
- 예) 오라클 시퀀스, auto_increment, 키생성 테이블 사용
기본키의 조건을 미래까지 충족하는 자연 키를 찾기 쉽지 않다.
대리키는 비즈니스와 무관한 임의의 값이므로 요구사항이 변경되어도 기본 키가 변경되는 일은 드물다.
JPA는 모든 엔티티에 일관된 방식으로 대리 키 사용을 권장한다.
필드와 컬럼 매핑
@Column
: 컬럼을 매핑한다.@Enumerated
: 자바의 enum 타입을 매핑한다.@Temporal
: 날짜 타입을 매핑한다.@Lob
: BLOB, CLOB 타입을 매핑한다.@Transient
: 특정 필드를 데이터베이스에 매핑하지 않는다.@Access
: JPA가 엔티티에 접근하는 방식을 지정한다.
@Column
@Column
은 객체 필드를 테이블 컬럼에 매핑한다. 가장 많이 사용되고 기능도 많다.
속성 중에 name
, nullable
이 주로 사용되고 나머지는 잘 사용되지 않는 편이다.
insertable
, updatable
속성은 데이터베이스에 저장되어 있는 정보를 읽기만하고 실수로 변경하는 것을 방지하고 싶을 때 사용한다.
속성
name
: 필드와 매핑할 테이블의 컬럼 이름을 지정한다.- 기본값 : 객체의 필드 이름
insertable
(거의 사용하지 않음) : 엔티티 저장 시 이 필드도 같이 저장한다. false로 설정하면 이 필드는 데이터베이스에 저장하지 않는다. false 옵션은 읽기 전용일 때 사용한다.- 기본값 : true
updateable
(거의 사용하지 않음) : 엔티티 수정 시 이 필드도 같이 수정한다. false로 설정하면 데이터베이스에 수정하지 않는다. false 옵션은 읽기 전용일 때 사용한다.- 기본값 : true
table
(거의 사용하지 않음) : 하나의 엔티티를 두 개 이상의 테이블에 매필할 때 사용한다.(@SecondaryTable 사용) 지정한 필드를 다른 테이블에 매핑할 수 있다.- 기본값 : 현재 클래스가 매핑된 테이블
nullable
(DDL) : DDL 생성 시 null 값의 허용 여부를 설정한다. false로 설정하면 not null 제약조건이 붙는다.- 기본값 : true
unique
(DDL) : @Table의 uniqueConstraints와 같으나 한 컬럼에 간단히 유니크 제약조건을 걸 때 사용한다.- 기본값 : false
columnDefinition
(DDL) : 데이터베이스 컬럼 정보를 직접 줄 수 있다.- 기본값 : 자바 필드의 타입과,데이터베이스 방언 설정 정보를 사용해 적절히 생성
length
(DDL) : 문자 길이 제약조건, String 타입에만 사용한다.- 기본값 : 255
precision
,scale
(DDL) : BigDecimal 타입(혹은 BigInteger)에서 사용한다. precision은 소수점을 포함한 전체 자리수를, scale은 소수의 자리수다.- 기본값 : precision = 0, scale = 0
@Column 생략
int data1; //@Column 생략, 자바 기본타입
data1 integer not null //생성된 DDL
Integer data2 //@Column 생략, 객체 타입
data2 integer //생성된 DDL
@Column
int data3; //@Column 사용, 자바 기본 타입
data3 integer //생성된 DDL
int data1
같은 자바 기본타입에는 null 값을 입력할 수 없다.Integer data2
처럼 객체 타입일 때만 null 값이 허용 된다. 따라서 자바 기본 타입인int data1
을 DDL로 생성할 때는 not null 제약조건을 추가하는 것이 안전하다.
JPA는 이런 상황을 고려해서 DDL 생성 기능을 사용할 때int data1
같은 기본 타입에는 not null 제약조건을 추가한다. 반면에Integer data2
처럼 객체 타입이면 null이 입력될 수 있으므로 not null 제약조건을 설정하지 않는다. 그런데int data3
처럼@Column
을 사용하면@Column
은nullable = true
가 기본값이므로 not null 제약조건을 설정하지 않는다. 따라서 자바 기본 타입에@Column
을 사용하면nullable = false
로 지정하는 것이 안전하다.
@Enumerated
자바의 enum 타입을 매핑할 때 사용한다.
EnumType.ORDINAL
: enum 순서를 데이터베이스에 저장, 기본값- 장점 : 데이터베이스에 저장되는 데이터 크기가 작다.
- 단점 : 이미 저장된 enum의 순서를 변경할 수 없다.
EnumType.STRING
: enum 이름을 데이터베이스에 저장- 장점 : 저장된 enum의 순서가 바뀌거나 enum이 추가되어도 안전하다.
- 단점 : 데이터베이스에 저장되는 데이터 크기가 ORDINAL에 비해서 크다.
@Enumerated(EnumType.STRING)
private RoleType roleType;
주의사항
기본 값인 ORDINAL은 주의해서 사용해야 한다.
enum의 순서가 뒤 바뀔 경우 애플리케이션과 데이터베이스가 의미하는 값이 달라질 수 있다.
따라서 이런 문제가 발생하지 않는 EnumType.STRING
을 권장한다.
@Temporal
날짜 타입을 매핑할 때 사용한다.
TemporalType.DATE
: 날짜, 데이터베이스 date 타입과 매핑(예: 2013-10-11)TemporalType.TIME
: 시간, 데이터베이스 time 타입과 매핑(예: 11:11:11)TemporalType.TIMESTAMP
: 날짜와 시간, 데이터베이스 timestamp 타입과 매핑(예:2013-10-11 11:11:11)
@Temporal(TemporalType.DATE)
private Date date; //날짜
@Temporal(TemporalType.TIME)
private Date time; //시간
@Temporal(TemporalType.TIMESTAMP)
private Date timestamp; //날짜와 시간
@Lob
데이터베이스 BLOB, CLOB 타입과 매핑한다.
@Lob
에는 지정할 수 있는 속성이 없다. 필드 타입이 문자면 CLOB으로 매핑하고 나머지는 BLOB으로 매핑한다.
@Lob
private String lobString;
@Lob
private byte[] lobByte;
@Transient
이 필드는 매핑하지 않는다. 따라서 데이터베이스에 저장하지 않고 조회하지도 않는다. 객체에 임시로 어떤 값을 보관하고 싶을 때 사용한다.
@Transient
private Integer temp;
@Access
JPA가 엔티티 데이터에 접근하는 방식을 지정한다.
- 필드 접근 :
AccessType.FIELD
로 지정한다. 필드에 직접 접근한다. 필드 접근 권한이 private이어도 접근할 수 있다. - 프로퍼티 접근 :
AccessType.PROPERTY
로 지정한다. 접근자(Getter)를 사용한다.
@Entity
@Access(AccessType.FIELD)
public class Member {
@Id
private String id;
}
@Access
를 설정하지 않으면 @Id
의 위치를 기준으로 접근 방식이 설정된다.
@Entity
public class Member {
@Id
private String id;
}
@Id
가 필드에 있으므로 @Access(AccessType.FIELD)
와 같다.
@Entity
public class Member {
private String id;
@Id
public String getId(){
return id;
}
}
@id
가 프로퍼티에 있으므로 @Access(AccessType.PROPERTY)
와 같다.
@Entity
public class Member {
@Id
private String id;
@Trasient
private Stirng firstName;
@Trasient
private Stirng lastName;
private String fullName;
@Access(AccessType.PROPERTY)
public String getFullName() {
return firstName + lastName;
}
}
@Id
가 필드에 있으므로 기본은 필드 접근 방식을 사용하고 getFullName()
만 프로퍼티 접근 방식을 사용한다. 따라서 회원 엔티티를 저장하면 회원 테이블의 FULLNAME 컬럼에 firstName + lastName
의 결과가 저장된다.
참고 자료 :
https://product.kyobobook.co.kr/detail/S000000935744
'백엔드 > JPA' 카테고리의 다른 글
[JPA] 복합 키와 식별 관계 매핑 (0) | 2023.05.30 |
---|---|
[JPA] 상속 관계 매핑 (0) | 2023.05.30 |
[JPA] flush(), detach(), clear(), close(), merge() (0) | 2023.05.02 |
[JPA] 엔티티 조회, 등록, 수정, 삭제 (0) | 2023.05.02 |
[JPA] 영속성 컨텍스트(persistence context)란? (0) | 2023.05.02 |