4. 엔티티 매핑
2025. 6. 12. 17:41ㆍSpring/JPA
[목차]
1. 엔티티 매핑 개요
1) 정의
2) 대표 어노테이션
2. 객체-테이블 매핑
1) @Entity
3) @Table
3. 데이터베이스 스키마 자동 생성
1) 기능 설명
2) 설정 옵션
3) 권장 사용 환경
4. 필드-컬럼 매핑
1) @Column 속성 정리
2) @Enumerated
3) @Temporal
4) @Lob
5) @Transient
5. 기본 키 매핑
5-1. 기본 키 매핑 방법
5-2. @GeneratedValue의 전략(strategy)별 비교
1) IDENTITY 전략
2) SEQUENCE 전략
3) TABLE 전략
4) 왜 allocationSize 기본값이 50일까?
5-3. 권장하는 식별자 전략
1. 엔티티 매핑 개요
1) 정의
JPA에서 엔티티 매핑은 객체와 테이블을 매핑하는 작업
이를 통해 객체 중심의 도메인 모델을 구성하고, 관계형 DB와 연동할 수 있다
2) 대표 어노테이션
매핑 | 어노테이션 | 설명 |
객체-테이블 |
@Entity | 이 클래스는 JPA가 관리하는 엔티티임을 명시 |
@Table | 매핑할 테이블을 설정 | |
필드-컬럼 | @Column | 필드 - 컬럼 매핑 |
기본키 |
@Id | 기본 키 지정 |
연관관계 | @ManyToOne |
다대일 |
@JoinColumn | 조인 |
2. 객체 - 테이블 매핑
1) @Entity
- JPA가 관리하는 객체로 만들기 위한 어노테이션
- 클래스에 붙여야 함
- 필수 조건
- 파라미터 없는 기본 생성자 필수 (public 또는 protected 생성자), JPA 라이브러리가 사용하기 때문에
- class: final, enum, interface, inner 사용 불가
- 필드: final 사용 불가
- 속성
속성 | 설명 | 기본값 |
name | JPA에서 사용할 엔티티 이름 지정 | 클래스 이름 (권장) |
2) @Table
- 엔티티와 매핑할 DB 테이블 지정
- 속성
속성 | 설명 | 기본값 |
name | 매핑할 테이블 이름 | 엔티티 이름 |
catalog | DB catalog | 없음 |
schema | DB schema | 없음 |
uniqueConstraints | 유니크 제약 조건 설정 (DDL 생성 시 사용) 컬럼에서 unique로 설정하는 것 보다 Table에서 uniqueConstraints로 설정하는 것이 좋음 |
없음 |
3. 데이터베이스 스키마 자동 생성
1) 설명
- DDL을 애플리케이션 실행 시점에 자동으로 생성
- 객체를 중심으로 테이블을 자동 생성 (테이블 만들고 객체 만드는 개발 순서 반대로 가능해짐)
- DB 방언(dialect)을 활용해 DB에 맞는 적절한 DDL 생성
- 개발 환경에서는 매우 유용하지만 운영 환경에서는 사용 주의
- 운영 환경에서 DDL 자동 변경은 데이터 유실, 성능 문제 및 서비스 장애를 유발할 수 있다.
- 따라서 스키마 변경은 수동 SQL로, 운영 전 충분한 테스트 후 반영하는 것이 안전
- 또한 SQL 스크립트도 참고는 하되 운영에선 다듬어서 사용해야 한다.
2) 설정 옵션
// persistence.xml
<property name="hibernate.hbm2ddl.auto" value="create" />
옵션 | 설명 |
create | 기존 테이블 삭제 후 새로 생성 (DROP + CREATE) |
create-drop | create와 같지만, 애플리케이션 종료 시 테이블 삭제 |
update | 변경된 부분만 반영 (운영 DB에 사용 금지) |
validate | 매핑만 검증 (테이블은 변경 X) |
none | 자동 생성 사용 안 함 |
3) 권장 사용 환경
환경 | 권장 옵션 |
개발 초기 | create, update (*운영엔 절대 안됨*) |
테스트 서버 | update, validate (create하면 테스트 데이터 날라감) |
운영/스테이징 | validate, none |
4. 객체 필드 - 테이블 컬럼 매핑
1) @Column 속성 정리
속성 | 설명 | 기본값 |
name | 매핑할 컬럼 이름 | 필드 이름 |
insertable / updatable | 등록/수정 가능 여부 | true |
nullable (DDL용) | null 허용 여부 | true |
unique (DDL용) | 유니크 제약 조건 (간단하게 설정) | false |
columnDefinition (DDL용) | 컬럼 정의 직접 작성 (ex. varchar(100) default 'EMPTY') | DB 방언에 따라 자동 생성 |
length (DDL용) | 문자열 길이 (String 타입에만) | 255 |
precision / scale (DDL용) | 소수점 포함 숫자의 전체 자릿수 / 소수 자릿수 (BigDecimal에만 사용) | 없음 |
- 필드에 아무 어노테이션도 달지 않으면 @Column 속성의 기본값으로 자동 매핑된다
- 날짜, enum, BLOB/CLOB 등 특수 타입은 명시적인 어노테이션이 꼭 필요
2) @Enumerated
- enum 매핑
- EnumType.ORDINAL: enum 순서(1,2,3..)를 DB에 저장 → 기본값
단, enum 순서가 바뀌면 데이터 오류 발생! 사용❌ - EnumType.STRING: enum 이름(USER, ADMIN..)을 DB에 저장 → 사용 권장
- EnumType.ORDINAL: enum 순서(1,2,3..)를 DB에 저장 → 기본값
3) @Temporal
- java.util.Date, Calendar 사용 시 필요
- Java 8 LocalDate는 생략 가능
- 속성
속성 | 설명 |
DATE | 날짜 (yyyy-MM-dd) |
TIME | 시간 (HH:mm:ss) |
TIMESTAMP | 날짜 + 시간 (yyyy-MM-dd HH:mm:ss) |
4) @Lob
- VARCHAR를 넘어가는 문자열을 넣고 싶은 경우 사용
- 문자(String, char[])면 CLOB, 나머지(byte[])는 BLOB으로 매핑
5) @Transient
- DB에 저장 X, 조회 X
- 애플리케이션 내부 계산 값 등에 사용
5. 기본 키 매핑
5-1. 기본 키 매핑 방법
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
- 기본 키 직접 할당: @Id만 사용
- 자동 생성: @GeneratedValue 함께 사용
5-2. @GeneratedValue의 전략(strategy)별 비교
전략 | ID생성 주체 | 설명 | 사용 DB |
IDENTITY | DB |
기본키 생성을 DB에 위임 (AUTO_INCREMENT 기반) |
MySQL, MariaDB, MsSQL |
SEQUENCE | JPA @SequenceGenerator |
DB 시퀀스 오브젝트 사용 | Oracle, PostgreSQL, H2 |
TABLE | JPA @TableGenerator |
키 생성용 테이블을 만들어서 DB 시퀀스를 흉내내는 전략 |
모든 DB에 사용가능 (성능 낮음) |
AUTO | DB 방언에 따라 위의 3가지 중 한 개 자동 지정됨 (기본 값) |
1) IDENTITY 전략
DB가 ID를 생성하고, JPA는 그걸 나중에 받아오는 방식
- em.persist(entity) 호출
- JPA는 ID를 아직 모름 → DB에 INSERT SQL을 바로 실행함 (쓰기 지연 X)
INSERT INTO member (name, age, ...) VALUES (?, ?, ...);- JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행
- 그러나! AUTO_ INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에 ID 값을 알 수 있음
- 그러나! 영속성 컨텍스트 1차 캐시에 값을 넣으려면 ID 값이 필요함
- 따라서 IDENTITY 전략만 예외적으로 em.persist() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회
- DB가 AUTO_INCREMENT로 ID 값을 자동 생성함
- JPA는 DB가 생성한 ID 값을 받아와서 → 엔티티의 id 필드에 설정함
- 이제 1차 캐시에 등록 가능 → 이후는 다른 전략과 동일하게 동작 (flush, commit 등)
2) SEQUENCE 전략
DB 시퀀스 오브젝트에서 값을 미리 가져옴
- @SequenceGenerator로 시퀀스 매핑
- em.persist() 하면, INSERT 전에 JPA가 아래 쿼리를 먼저 실행함
SELECT MEMBER_SEQ.NEXTVAL FROM DUAL; - 이 결과로 받은 값을 ID로 먼저 세팅
- 실제 INSERT는 트랜잭션 커밋 시점에 실행됨 (쓰기 지연 O)
@SequenceGenerator 속성 | 설명 | 기본값 |
name | 식별자 생성기 이름 | 필수 |
sequenceName | DB 시퀀스 이름 | hibernate_sequence |
initialValue (DDL용) | 초기 시작 값 | 1 |
allocationSize | 시퀀스 한 번 호출 시 증가값 성능 최적화에 사용됨 |
50 (1로 설정 시 성능↓) |
3) TABLE 전략
키 생성 테이블에서 값을 미리 가져옴
- @TableGenerator로 테이블(MY_SEQUENCES 등) 생성
- em.persist() 시, 다음 쿼리로 ID 값을 먼저 조회 & 증가
SELECT next_val FROM MY_SEQUENCES WHERE sequence_name = 'MEMBER_SEQ';
UPDATE MY_SEQUENCES SET next_val = next_val + 1 WHERE sequence_name = 'MEMBER_SEQ'; - 받은 값을 엔티티의 ID로 세팅
- INSERT는 나중에 실행 (쓰기 지연 O)
@TableGenerator 속성 | 설명 | 기본값 |
table | 키 생성용 테이블 이름 | hibernate_sequences |
pkColumnName | 시퀀스명 컬럼 | sequence_name |
valueColumnName | 시퀀스 값 컬럼 | next_val |
pkColumnValue | 사용할 키 이름 | 엔티티 이름 |
allocationSize | 증가 단위 | 50 |
4) 왜 allocationSize 기본값이 50일까?
- INSERT할 때마다 시퀀스를 SELECT하면 DB 부하 커짐
- 그래서 JPA는 기본적으로 한 번에 50개를 미리 확보해서
50건의 persist를 DB 접근 없이 메모리에서 처리 - 단! allocationSize를 너무 크게 설정하면?
- ID 낭비: 앱이 재시작되면 미사용 ID 버려짐 → ID가 툭툭 점프함
- ID 연속성 없음: 사용자 입장에서 ID가 갑자기 10만 뛰면 당황할 수 있음
- 다중 서버 환경: 각 서버가 큰 범위 가져가면 관리 어려움, DB 시퀀스와 불일치 위험
5-3. 권장하는 식별자 전략
- Long형 + 대체키 키 생성 전략 사용
- Long형 사용 이유
- Integer보다 Long이 더 크기가 크긴 하지만
- 나중에 범위 넘어갔을때 Long으로 바꾸는게 더 힘듦
- 대체키 사용 이유
- 기본키 제약 조건: null 아님 + 유일 + 변하면 안됨
- 미래까지 이 조건을 만족하는 자연키(비즈니스와 관련있는 값)를 찾기 어렵다. 주민등록번호도 적절하지 않음.
- 따라서 대체키(대리키)를 사용 권장