일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- Xmx
- 글또
- Probe
- spring boot
- 회고
- JVM
- evicted
- Heap
- ORM
- application properties
- diskpressure
- java
- startup probe
- jpa
- readiness probe
- logback
- pod
- k8s
- xms
- liveness probe
- properties
- property
- configmap
- Java Virtual Machine
- Kubernetes
- OOM
- Today
- Total
여우발개발
4장. 엔티티 매핑 본문
4.1 @Entity
@Entity 를 선언한 클래스는 JPA가 entity로써 관리한다.
- 파라미터가 없는 기본 생성자 필수 (public, protected 가능)
- lombok @NoArgConstructor(access = AccessLevel.PROTECTED) 사용 추천
- final class, enum, interface, inner class에는 사용 불가
- 저장할 field가 final이면 안 된다.
속성 | 기능 | 기본값 |
---|---|---|
name | JPA에서 사용할 엔티티 이름을 지정한다. | {class 이름} |
4.2 @Table
속성 | 기능 | 기본값 |
---|---|---|
name | 매핑할 테이블 이름 | {entity 이름} |
catalog | catalog 기능이 있는 DB에서 catalog 매핑 | |
schema | schema기능이 있는 DB에서 schema매핑 | |
uniqueConstraints (DDL) | DDL 생성 시 unique 제약조건 생성 | |
(스키마 자동 생성 기능을 사용해서 DDL 만들 때만 사용됨) |
4.3 다양한 매핑 사용
책에서 뒷 내용 설명을 위한 간단한 예시를 보여주고 있다.
4.4 데이터베이스 스키마 자동 생성
JPA는 DB 스키마를 자동으로 생성하는 기능을 지원함
- spring-data-jpa에서의 설정
spring:
jpa:
hibernate:
ddl-auto: {옵션 값}
옵션 | 설명 | 추천 사용 시기 |
---|---|---|
create | 기존 테이블을 삭제하고 새로 생성 (DROP + CREATE) | 개발 초기, 개발, CI |
create-drop | create + 어플리케이션 종료 시 생성한 DDL 제거 (DROP + CREATE + DROP) | 개발, CI |
update | DB 테이블과 entity 매핑정보를 비교해서 변경 사항만 수정 | 개발 초기, 테스트 |
validate | DB 테이블과 entity 매핑정보를 비교해서 차이가 있으면 경고하고 어플리케이션 실행시키지 않음 (DDL 수정 X) | staging, prod |
none | invalid한 값을 주면 동작하지 않음 | staging, prod |
- 이름 매핑 전략 변경하기
- java는 관례상 camel case를 사용
- DB는 관례상 snake case를 사용
- naming_strategy 속성을 사용하면 이름 매핑 전략 변경 가능
💡 Hibernate5부터 org.hibernate.cfg.NamingStrategy는 사용되지 않음
- hibernate.implicit_naming_strategy, hibernate.physical_naming_strategy 로 분리 대체
- implicit_naming_strategy (암시적 명칭 전략) : naming이 지정되지 않은 경우 적용
- physical_naming_strategy (물리적 명칭 전략) : naming이 지정되어도 적용
- physical_naming_strategy 는 implicit_naming_strategy 가 적용된 이후에도 덮어씌움
- spring-boot-jpa에서의 설정 방법
spring.jpa.hibernate.naming.strategy: {설정 값}
spring.jpa.hibernate.naming:
implicit-strategy: {설정 값}
physical-strategy: {설정 값}
4.5 DDL 생성 기능
스키마 자동 생성하기를 통해 만들어지는 DDL에 제약조건을 추가할 수 있다.
- @Table의 uniqueConstraints
@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;
...
}
ALTER TABLE MEMBER
ADD CONSTRAINT NAME_AGE_UNIQUE UNIQUE (NAME, AGE)
- DDL 관련 annotaion은 DDL 생성 시에만 사용되고 JPA의 실행 로직에는 영향을 주지 않음
- 직접 DDL을 만든다면 사용할 필요가 없음
- 대신 개발자가 Entity class만 보고 손쉽게 제약조건을 파악할 수 있다
4.6 기본 키 매핑
기본 키를 직접 할당하는 대신 DB가 생성하는 방식으로 생성하려면?
DB마다 기본 키 생성 방식이 서로 다른 점을 유의해야 한다
💡 책에서는 hibernate.id.new_generator_mappings 옵션을 true로 설정해야 키 생성 전략의 성능 최적화가 가능하다고 되어있으나 Hibernate 4 기준이고 5 이상인 경우 기본 true로 설정되어 있다.
4.6.1 기본 키 직접 할당 전략
@Id
@Column
private long id;
- 구현체에 따라 @Id 적용 가능 자바 타입이 달라질 수 있다.
- 책에서는 7가지로 안내되어 있는데 요새는 UUID를 사용하는 케이스도 많아졌다고 함
Board board = new Board();
board.setId(1L);
em.persist(board);
- 기본 키 직접 할당 전략
- entity를 저장하기 전에 application에서 기본 키를 직접 할당하는 방법
- 식별자 값 없이 저장하면 예외 발생 (Hibernete : IdentifierGenerationException → (래핑) → JPA : PersistenceException)
4.6.2 IDENTITY 전략
-- DDL
CREATE TABLE BOARD {
ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
DATA VARCHAR(255)
};
-- ID값이 없어도 DB가 자동으로 셋팅해 줌
INSERT INTO BOARD(DATA) VALUES('A');
- 기본 키 생성을 DB에 위임하는 전략
- MySQL, PostgreSQL, SQL Server, DB2에서 지원
- DB에 값을 저장하고 나서야 기본 키 값을 구할 수 있을 때 사용
@Entity
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
}
- IDENTITY 전략은 DB에 INSERT한 후에 기본 키 값을 알 수 있다.
- entity가 영속 상태가 되려면 식별자가 필요하다
- 영속화시킬 때 바로 INSERT가 필요함
- 쓰기 지연 불가능
- entity에 식별자 값을 할당하려면 DB 조회가 필요
- hibernete는 저장하면서 동시에 생성된 키 값을 얻어오는 Statement.getGeneratedKeys()를 사용해 DB와 한 번만 통신한다.
- 영속화시킬 때 바로 INSERT가 필요함
- entity가 영속 상태가 되려면 식별자가 필요하다
4.6.3 SEQUENCE 전략
-- DDL
CREATE TABLE BOARD {
ID BIGINT NOT NULL PRIMARY KEY,
DATA VARCHAR(255)
};
-- 시퀀스 생성
CREATE SEQUENCE **BOARD_SEQ** START WITH 1 INCREMENT BY 1;
INSERT INTO BOARD(DATA) VALUES('A');
- 데이터베이스 시퀀스 (유일한 값을 순서대로 생성하는 오브젝트) 를 사용해서 기본 키 생성
- Oracle, PostgreSQL, DB2, H2에서 지원
@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**")
// 여기에 @SequenceGenerator 를 설정해도 됨
private long id;
}
@SequenceGenerator
속성 | 기능 | 기본값 |
---|---|---|
name | Sequence Generator 이름 | 필수 |
sequenceName | DB에 등록되어 있는 시퀀스 이름 | hibernate_sequence |
initialValue (DDL) | 시퀀스 DDL을 생성할 때 처음 시작하는 수 | 1 |
allocationSize | 시퀀스 한 번 호출에 증가하는 수 (성능 최적화에 사용) | 50 |
catalog, schema | DB catalog, schema 이름 |
-- 생성되는 DDL
CREATE SEQUENCE {sequenceName}
start with {initialValue} increment by {allocationSize}
- SEQUENCE 전략과 최적화
- SEQUENCE 전략은 DB 시퀀스를 통해 만들어진 식별자를 추가로 조회해야 한다.
SELECT BOARD_SEQ.NEXTVAL FROM DUAL INSERT INTO BOARD ...
- 시퀀스에 접근하는 횟수를 줄이기 위해서 allocationSize 만큼 미리 시퀀스를 받아올 수 있음
- INSERT의 성능이 중요하지 않으면 allocationSize를 1로 설정
- hibernate.id.new_generator_mappings 이 false인 경우 설정방식이 달라지므로 성능 최적화를 하려면 true 로 설정 필요
4.6.4 TABLE 전략
CREATE TABLE **MY_SEQUENCES** {
sequence_name varchar(255) not null,
next_val bigint,
primary key (sequence_name)
}
- 키 생성 전용 테이블을 만들어 시퀀스를 흉내내는 전략
- 테이블을 사용하기 때문에 모든 DB에 적용할 수 있다
@Entity
@TableGenerator(
name = "**BOARD_SEQ_GENERATOR**",
table = "**MY_SEQUENCES**",
pkColumnValue = "BOARD_SEQ", alloacationSize = 1
)
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "**BOARD_SEQ_GENERATOR**")
private long id;
...
}
- TABLE 전략은 SEQUENCE 대신에 테이블을 사용한다는 것 외에 동작방식이 같다.
- 스키마 자동 생성 옵션을 키면 JPA가 table도 생성함
- row도 JPA 가 알아서 처리해줌
@TableGenerator
속성 | 기능 | 기본값 |
---|---|---|
name | 식별자 생성기 이름 | 필수 |
table | 키 생성 테이블명 | hibernate_sequences |
pkColumnName | 시퀀스 컬럼명 | sequence_name |
valueColumnName | 시퀀스 값 컬럼명 | next_val |
pkColumnValue | 키로 사용할 값 이름 | {entity 이름} |
initialValue | 초기 값 | 0 |
allocationSize | 시퀀스 한 번 호출에 증가하는 수 (성능 최적화에 사용) | 50 |
catalog, schema | DB catalog, schema 이름 | |
uniqueConstraints (DDL) | 유니크 제약 조건 지정 |
- MY_SEQUENCES table 예시
{pkColumnName} | {valueColumnName} |
---|---|
{pkColumnValue} | {initialValue} |
- Table 전략은 조회를 위한 select, 값 증가를 위한 update가 필요
- DB와 2번 통신
- allocationSize를 사용해 성능 최적화가 가능
4.6.5 AUTO 전략
@Entity
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
...
}
- 선택한 DB의 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 자동으로 선택
- @GeneratedValue.strategy 의 기본값 → AUTO
- DB를 변경해도 코드를 수정할 필요가 없음
4.6.6 기본 키 매핑 정리
- 직접 할당
- em.persist()를 호출하기 전에 직접 식별자 값을 할당해야 함
- 식별자 값이 없으면 예외 발생
- SEQUENCE
- DB sequence에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장
- 쓰기 지연 가능
- TABLE
- DB sequence 생성용 table에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장
- 쓰기 지연 가능
- IDENTITY
- DB에 entity를 저장하면서 식별자 값을 획득해 영속성 컨텍스트에 저장
- 쓰기 지연 불가능
권장 식별자 선택 전략
- Primary key의 필수 조건
- not null
- unique
- Immutable (불변의)
- 기본 키 선택 전략
- 자연 키 (natural key)
- business에 의미가 있는 키
- ex. 주민등록번호, 이메일, 전화번호
- 대리키 (surrogate key)
- business와 관련 없는 임의로 만들어진 키
- ex. UUID, sequence
- 자연 키보다 대리 키 권장
- 현실과 비즈니스 규칙은 생각보다 쉽게 변한다
- 대리 키는 비즈니스와 무관하므로 요구사항이 변경되어도 기본 키가 변경되지 않을 수 있다
- 자연 키는 필요에 따라 유니크 인덱스를 설정해 사용하는 것을 권장
- 자연 키 (natural key)
💡 저장된 entity의 기본 키 값은 변경될 수 없다. 변경 시 JPA에서 정상 작동하지 않는다.
4.7 필드와 컬럼 매핑: 레퍼런스
분류 | 매핑 어노테이션 | 설명 |
---|---|---|
필드 - 컬럼 매핑 | @ Column | 컬럼 매핑 |
필드 - 컬럼 매핑 | @Enumerated | enum 타입 매핑 |
필드 - 컬럼 매핑 | @Temporal | 날짜 타입 배핑 |
필드 - 컬럼 매핑 | @Lob | BLOB, CLOB |
필드 - 컬럼 매핑 | @Transient | DB 컬럼과 매핑하지 않을 필드 설정 |
기타 | @Access | JPA가 entity에 접근하는 방식 지정 |
4.7.1 @Column
- 객체 필드를 테이블 컬럼에 매핑
속성 | 기능 | 기본값 |
---|---|---|
name | 필드와 매핑할 테이블의 이름 | 객체의 필드 이름 |
insertable | entity 저장 시 컬럼에 필드 저장 여부 | |
읽기 전용 필드일 때 false로 설정 | true | |
updatable | entity 수정 시 컬럼에 필드 수정 | |
읽기 전용 필드일 때 false로 설정 | true | |
table | 하나의 entity를 두 개 이상의 table에 매핑할 때 사용 | |
지정한 필드를 다른 테이블에 매핑할 수 있다 | 현재 class가 매핑 된 table | |
nullable (DDL) | null 값 허용 여부 | true |
unique (DDL) | 한 컬럼에 간단히 unique 제약조건을 걸 때 사용 | |
columnDefinition (DDL) | DB 컬럼 정보 정의 | {JPA가 field의 java type과 DB 방언 정보를 사용해 판단한 컬럼 타입} |
length (DDL) | 문자 길이 제약조건 (String 에서 사용) | 255 |
precision (DDL) | 소수점을 포함한 전체 자리수 | |
(BigDecimal, BigInteger 에서 사용) | 19 | |
scale (DDL) | 소수점 아래의 자리수 | |
(BigDecimal 에서 사용) | 2 |
💡 @Column 생략 시
대부분 @Column 속성의 기본값이 적용되나 nullable의 경우 예외가 있음
primitive type은 null 입력이 불가하므로 JPA는 not null로 DDL을 생성함
- primitive type에 @Column을 명시할 시 nullable = false로 지정하는 것이 안전하다
4.7.2 @Enumerated
속성 | 기능 | 기본값 |
---|---|---|
value | EnumType.ORDINAL : enum 순서를 저장 EnumType.STRING : enum 이름을 저장 | EnumType.ORDINAL |
- EnumType.ORDINAL
- 장점 : DB에 저장되는 데이터 크기가 작다
- 단점 : 이미 저장된 enum의 순서를 변경할 수 없다
- EnumType.STRING (권장)
- 장점 : 저장된 enum의 순서가 바뀌거나 enum이 추가되어도 안전하다
- 단점 : DB에 저장되는 데이터 크기가 ORDINAL에 비해서 크다
4.7.3 @Temporal
java 8 이후로는 java.util.Date, java.util.Calendar 사용을 권장하지 않음
대신 java.time 패키지 (Instant, LocalDateTime, ZonedDateTime) 사용을 권장하고, JPA에서 @Temporal 없이 자동 매핑이 가능하다.
4.7.4 @Lob
DB의 BLOB (Binary Large OBject), CLOB (Character Large OBject) 타입과 매핑
- CLOB : String, char[], java.sql.CLOB
- BLOB : byte[], java.sql.BLOB
4.7.5 @Transient
선언된 필드를 DB 컬럼과 매핑하지 않음. DB에 값을 저장하지도, 조회하지도 않는다
객체에 임시로 어떤 값을 보관하고 싶을 때 사용
❓ Entity class에 이 어노테이션을 사용하는 것이 맞는가?
- Entity class는 본질적으로 domain 객체임
- 비즈니스 로직이나 dto의 역할을 같이 수행하게 될 가능성이 있음
→ 관련 field를 dto로 빼거나, 서비스 계층에서 처리하는 것이 좋은 방식이나 . . .
→ 실무에서 실용적으로 사용하기 위해 사용될 수 있음
ex. DB에 저장된 필드들로 산출되는 값
- totalPrice = price * quantity
- fullName = firstName + lastName
4.7.6 @Access
JPA가 Entity에 접근하는 방식 지정
- AccessType.FIELD (필드 접근)
- 필드에 직접 접근
- private여도 접근할 수 있다
- AccessType.PROPERTY (프로퍼티 접근)
- 접근자(getter)를 사용해 접근
- @Access의 기본값
- @Id의 위치를 기준으로 설정됨
// field에 @Id를 설정한 경우
@Id private String id;
===========================================
// property에 @Id를 설정한 경우
@Id public String getId() { return id; }
===========================================
// 같이 설정하는 경우
// 기본은 FIELD로 접근
@Id
private String id;
@Transient
private String firstName;
@Transient
private String lastName;
// fullName 만 PROPERTY로 접근
@Access(AccessType.PROPERTY)
public String getFullName() {
return firstName + lastName;
}
// entity 접근 방식을 PROPERTY으로 설정한 경우 setter 필수
public void setFullName(String fullName) {
this.firstName = ...; this.lastName = ...;
}
💡 보통은 FIELD 방식을 사용함
PROPERTY 방식 사용 시,
- 리플렉션 기반이 아니라 지연로딩이 되지 않고 프록시 초기화 문제가 발생할 수 있음
- getter, setter 필수 → Immutable 객체 만들기가 어려움
'기술 도서 리뷰 > 자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
3장. 영속성 관리 (1) | 2025.02.17 |
---|---|
2장. JPA 시작 (2) | 2025.02.07 |
1장. JPA 소개 (1) | 2025.02.02 |