Chapter 07. 엔티티 매핑

Chapter 07. 엔티티 매핑

엔티티 매핑 개요

JPA는 다양한 매핑 어노테이션을 제공하여 객체와 테이블을 정확히 연결한다.

┌─────────────────────────────────────────────────────────┐
│                   엔티티 매핑 구조                         │
│                                                         │
│  ┌──────────────┐         매핑         ┌──────────────┐ │
│  │  Java 객체   │ ←──────────────────→ │  DB 테이블    │ │
│  │              │                      │              │ │
│  │  @Entity     │                      │  CREATE TABLE│ │
│  │  class Member│                      │  MEMBER      │ │
│  │  {           │                      │  (           │ │
│  │    @Id       │  ← 기본 키 매핑 →     │    id PK     │ │
│  │    Long id   │                      │    ...       │ │
│  │              │                      │  )           │ │
│  │    @Column   │  ← 필드/컬럼 매핑 →   │              │ │
│  │    String... │                      │              │ │
│  │  }           │                      │              │ │
│  └──────────────┘                      └──────────────┘ │
└─────────────────────────────────────────────────────────┘

매핑 어노테이션 분류

분류어노테이션설명
객체-테이블@Entity, @Table엔티티와 테이블 매핑
기본 키@Id, @GeneratedValue기본 키 매핑 전략
필드-컬럼@Column필드와 컬럼 매핑
열거형@EnumeratedEnum 타입 매핑
날짜@Temporal날짜 타입 매핑 (레거시)
대용량@LobBLOB, CLOB 매핑
제외@Transient매핑 제외 필드

1. @Entity

JPA가 관리하는 엔티티 클래스로 지정

@Entity
public class Member {
    @Id
    private Long id;
    private String username;
}

@Entity 속성

속성기능기본값
nameJPA에서 사용할 엔티티 이름클래스 이름

필수 요구사항

┌────────────────────────────────────────────┐
│        @Entity 적용 시 주의사항             │
├────────────────────────────────────────────┤
│ ✅ 기본 생성자 필수 (public/protected)      │
│ ❌ final 클래스 사용 불가                   │
│ ❌ enum, interface, inner 클래스 불가       │
│ ❌ 저장할 필드에 final 사용 불가             │
└────────────────────────────────────────────┘
@Entity
public class Member {

    // 기본 생성자 필수!
    protected Member() {}

    public Member(String username) {
        this.username = username;
    }
}

2. @Table

엔티티와 매핑할 테이블 지정

@Entity
@Table(name = "MEMBER",
       schema = "public",
       uniqueConstraints = {
           @UniqueConstraint(
               name = "UK_MEMBER_EMAIL",
               columnNames = {"email"}
           )
       })
public class Member { ... }

@Table 속성

속성기능기본값
name매핑할 테이블 이름엔티티 이름
catalog카탈로그 매핑-
schema스키마 매핑-
uniqueConstraintsDDL 생성 시 유니크 제약조건-

3. 기본 키 매핑

┌───────────────────────────────────────────────────────────┐
│                   기본 키 생성 전략                         │
│                                                           │
│  ┌──────────┐                                            │
│  │   @Id    │                                            │
│  └────┬─────┘                                            │
│       │                                                   │
│       ├─── 직접 할당 ─→ 애플리케이션에서 직접 설정          │
│       │                                                   │
│       └─── @GeneratedValue ─┐                            │
│                             │                            │
│            ┌────────────────┼────────────────┐           │
│            ▼                ▼                ▼           │
│        IDENTITY         SEQUENCE          TABLE          │
│      (DB 위임)       (시퀀스 사용)      (키 테이블)        │
│     MySQL, PG        Oracle, PG         모든 DB          │
└───────────────────────────────────────────────────────────┘

3.1 직접 할당 전략

@Entity
public class Member {
    @Id
    private String id;  // 직접 할당

    private String username;
}

// 사용
Member member = new Member();
member.setId("user001");  // 직접 설정 필수!
em.persist(member);

적용 가능 타입: 자바 기본형, Wrapper, String, Date, BigDecimal, BigInteger


3.2 IDENTITY 전략

기본 키 생성을 데이터베이스에 위임 (MySQL AUTO_INCREMENT)

@Entity
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
}

동작 방식

┌─────────────────────────────────────────────────────────┐
│          IDENTITY 전략 동작 흐름                         │
│                                                         │
│  em.persist(member)                                     │
│         │                                               │
│         ▼                                               │
│  ┌──────────────┐                                       │
│  │ INSERT 즉시! │ ← 식별자를 얻기 위해 즉시 실행          │
│  └──────┬───────┘                                       │
│         │                                               │
│         ▼                                               │
│  DB에서 ID 생성 (AUTO_INCREMENT)                         │
│         │                                               │
│         ▼                                               │
│  영속성 컨텍스트 저장 (ID 할당 완료)                       │
└─────────────────────────────────────────────────────────┘

특징

항목설명
장점간단, DB 기능 활용
단점쓰기 지연 불가, 배치 INSERT 불가
적합 DBMySQL, PostgreSQL, SQL Server

⚠️ em.persist() 호출 즉시 INSERT SQL 실행 (쓰기 지연 동작 안 함)


3.3 SEQUENCE 전략

데이터베이스 시퀀스를 사용하여 기본 키 생성

@Entity
@SequenceGenerator(
    name = "MEMBER_SEQ_GENERATOR",
    sequenceName = "MEMBER_SEQ",
    initialValue = 1,
    allocationSize = 50  // 성능 최적화!
)
public class Member {
    @Id
    @GeneratedValue(
        strategy = GenerationType.SEQUENCE,
        generator = "MEMBER_SEQ_GENERATOR"
    )
    private Long id;
}

동작 방식

┌─────────────────────────────────────────────────────────┐
│         SEQUENCE 전략 동작 흐름                          │
│                                                         │
│  em.persist(member)                                     │
│         │                                               │
│         ▼                                               │
│  ┌──────────────┐                                       │
│  │시퀀스 조회    │ ← DB에서 시퀀스 값 획득                 │
│  └──────┬───────┘                                       │
│         │                                               │
│         ▼                                               │
│  영속성 컨텍스트 저장 (ID 할당 완료)                       │
│         │                                               │
│         ▼                                               │
│  트랜잭션 커밋 시 INSERT 실행 (쓰기 지연 O)               │
└─────────────────────────────────────────────────────────┘

@SequenceGenerator 속성

속성기능기본값
name식별자 생성기 이름필수
sequenceNameDB 시퀀스 이름hibernate_sequence
initialValue시퀀스 시작 값1
allocationSize한 번에 할당할 크기50 (성능 최적화)

성능 최적화: allocationSize

// allocationSize = 50 설정 시
// 1. 첫 persist(): DB 시퀀스에서 1~50 할당
// 2. 메모리에서 1~50 사용 (DB 조회 없음!)
// 3. 51번째 persist(): DB에서 51~100 할당

💡 allocationSize는 기본값 50 권장 - DB 접근 횟수를 대폭 감소


3.4 TABLE 전략

키 생성 전용 테이블을 만들어 시퀀스 흉내

@Entity
@TableGenerator(
    name = "MEMBER_SEQ_GENERATOR",
    table = "MY_SEQUENCES",
    pkColumnName = "sequence_name",
    valueColumnName = "next_val",
    pkColumnValue = "MEMBER_SEQ",
    allocationSize = 1
)
public class Member {
    @Id
    @GeneratedValue(
        strategy = GenerationType.TABLE,
        generator = "MEMBER_SEQ_GENERATOR"
    )
    private Long id;
}

생성되는 키 테이블

CREATE TABLE MY_SEQUENCES (
    sequence_name VARCHAR(255) NOT NULL,
    next_val BIGINT,
    PRIMARY KEY (sequence_name)
);

-- 데이터
-- MEMBER_SEQ | 100

특징

항목설명
장점모든 DB 사용 가능 (호환성)
단점성능 저하 (테이블 조회/수정)
적합 상황DB 변경 가능성 높을 때

⚠️ 성능 이슈로 실무에서는 비권장


3.5 AUTO 전략

DB 방언에 따라 자동으로 전략 선택

@Entity
public class Member {
    @Id
    @GeneratedValue  // strategy 생략 시 AUTO
    private Long id;
}

전략 선택

  • MySQL → IDENTITY
  • Oracle → SEQUENCE
  • H2 → SEQUENCE

전략별 비교표

전략쓰기 지연성능호환성적합 DB실무 추천
IDENTITY보통MySQL, PostgreSQL⭐⭐⭐
SEQUENCE우수Oracle, PostgreSQL⭐⭐⭐⭐⭐
TABLE낮음최고모든 DB
AUTO--높음-⭐⭐ (개발 초기)

실무 권장

// MySQL 사용 시
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

// Oracle 사용 시
@SequenceGenerator(allocationSize = 50)  // 성능 최적화!
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;

4. 필드와 컬럼 매핑

4.1 @Column

객체 필드를 테이블 컬럼에 매핑

@Entity
public class Member {
    @Id
    private Long id;

    @Column(
        name = "username",           // 컬럼명
        nullable = false,            // NOT NULL
        length = 50,                 // VARCHAR(50)
        unique = true,               // UNIQUE 제약
        columnDefinition = "VARCHAR(100) DEFAULT 'EMPTY'"  // DDL 직접 정의
    )
    private String username;

    @Column(precision = 10, scale = 2)  // DECIMAL(10,2)
    private BigDecimal salary;
}

주요 속성

속성기능기본값
name컬럼 이름필드 이름
nullableNULL 허용 여부 (DDL)true
unique유니크 제약조건 (DDL)false
length문자 길이 (DDL)255
columnDefinitionDDL 직접 정의-
precision, scaleBigDecimal 정밀도19, 2

@Column 생략 시 동작

int age;        // → age integer NOT NULL (기본형)
Integer age;    // → age integer (객체형, null 가능)
String name;    //  name varchar(255)

💡 기본형은 NOT NULL, 객체형은 NULL 허용


4.2 @Enumerated

Java Enum 타입 매핑

public enum RoleType {
    ADMIN, USER, GUEST
}

@Entity
public class Member {
    @Enumerated(EnumType.STRING)  // 반드시 STRING!
    private RoleType roleType;
}

EnumType 비교

타입저장 값장점치명적 단점권장
ORDINAL순서 (0, 1, 2)DB 크기 작음Enum 순서 변경 시 데이터 오염!❌ 절대 금지
STRING이름 (“ADMIN”)안전, 명확DB 크기 다소 증가✅ 필수

ORDINAL 사용 금지 이유

// 초기 상태
enum RoleType { ADMIN, USER }  // ADMIN=0, USER=1

// DB 저장
member.setRoleType(ADMIN);  // → DB에 0 저장

// 나중에 Enum 수정
enum RoleType { GUEST, ADMIN, USER }  // GUEST=0, ADMIN=1, USER=2

// 기존 데이터 조회
// DB의 0은 이제 GUEST로 조회됨! (원래는 ADMIN) 💥

⚠️ 반드시 EnumType.STRING 사용!


4.3 @Temporal (레거시)

java.util.Date, Calendar 매핑 (Java 8 이전)

@Temporal(TemporalType.DATE)
private Date birthDate;  // 날짜만: 2025-12-19

@Temporal(TemporalType.TIME)
private Date wakeUpTime;  // 시간만: 14:30:00

@Temporal(TemporalType.TIMESTAMP)
private Date createdDate;  // 날짜+시간: 2025-12-19 14:30:00

Java 8+ 권장 방식 (최신)

@Entity
public class Member {
    private LocalDate birthDate;           // @Temporal 불필요!
    private LocalTime wakeUpTime;          // @Temporal 불필요!
    private LocalDateTime createdDateTime; // @Temporal 불필요!
    private ZonedDateTime registeredAt;    // 타임존 포함
}

날짜 타입 권장사항

타입@Temporal 필요권장용도
java.util.Date✅ 필수레거시 코드만
LocalDate❌ 불필요날짜 (2025-12-19)
LocalTime❌ 불필요시간 (14:30:00)
LocalDateTime❌ 불필요날짜+시간
ZonedDateTime❌ 불필요타임존 포함 (국제화)

4.4 @Lob

대용량 데이터 (BLOB, CLOB) 매핑

@Entity
public class Article {
    @Id
    private Long id;

    @Lob
    private String content;  // CLOB (문자형)

    @Lob
    private byte[] thumbnail;  // BLOB (바이너리)
}

매핑 규칙

  • 문자형 필드 (String, char[]) → CLOB
  • 바이너리 필드 (byte[]) → BLOB

4.5 @Transient

매핑 제외 (메모리에서만 사용)

@Entity
public class Member {
    @Id
    private Long id;

    private String username;

    @Transient
    private int temp;  // DB에 저장 안 됨, 메모리에서만 사용
}

5. 데이터베이스 스키마 자동 생성

JPA는 엔티티 매핑 정보를 바탕으로 DDL을 자동 생성할 수 있다.

hibernate.hbm2ddl.auto 옵션

<!-- persistence.xml -->
<property name="hibernate.hbm2ddl.auto" value="create" />
옵션동작사용 환경위험도
createDROP + CREATE개발 초기⚠️ 높음
create-dropCREATE + 종료 시 DROP테스트⚠️ 높음
update변경사항만 반영 (추가만 가능)개발 중⚠️ 중간
validate매핑 확인만 (변경 없음)테스트/운영✅ 안전
none사용 안 함운영✅ 안전

환경별 권장 설정

┌──────────────────────────────────────────────┐
│         환경별 권장 hbm2ddl.auto 설정         │
├──────────────┬───────────────────────────────┤
│ 개발 초기     │ create, update                │
│ 테스트 서버   │ create, create-drop, validate │
│ 스테이징      │ validate, none                │
│ 운영 서버     │ validate, none (필수!)        │
└──────────────┴───────────────────────────────┘

⚠️ 운영 서버에서 create, update 절대 금지! - 데이터 유실 위험

네이밍 전략

<!-- 카멜케이스 → 언더스코어 자동 변환 -->
<property name="hibernate.ejb.naming_strategy"
          value="org.hibernate.cfg.ImprovedNamingStrategy"/>
// Java
private LocalDateTime createdDate;

// DB 컬럼
created_date

6. 실전 예제

package com.example.entity;

import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Entity
@Table(name = "MEMBER",
       uniqueConstraints = {
           @UniqueConstraint(
               name = "UK_MEMBER_EMAIL",
               columnNames = "email"
           )
       })
public class Member {

    // 기본 키 - IDENTITY 전략 (MySQL)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "member_id")
    private Long id;

    // 필수 문자열 필드
    @Column(name = "username", nullable = false, length = 50)
    private String username;

    @Column(name = "email", nullable = false, length = 100)
    private String email;

    // NULL 가능 필드 (Integer 사용)
    @Column(name = "age")
    private Integer age;

    // Enum 타입 - 반드시 STRING
    @Enumerated(EnumType.STRING)
    @Column(name = "role_type", nullable = false, length = 20)
    private RoleType roleType;

    // 대용량 텍스트
    @Lob
    @Column(name = "description")
    private String description;

    // 날짜 타입 - Java 8+
    private LocalDateTime createdDate;
    private LocalDateTime lastModifiedDate;

    // 매핑 제외
    @Transient
    private String tempData;

    // 기본 생성자 (JPA 필수!)
    protected Member() {}

    // 생성자
    public Member(String username, String email, RoleType roleType) {
        this.username = username;
        this.email = email;
        this.roleType = roleType;
        this.createdDate = LocalDateTime.now();
        this.lastModifiedDate = LocalDateTime.now();
    }

    // Getter, Setter
    public Long getId() { return id; }
    public String getUsername() { return username; }
    public void setUsername(String username) {
        this.username = username;
        this.lastModifiedDate = LocalDateTime.now();
    }
    // ... 나머지 getter/setter
}

enum RoleType {
    ADMIN, USER, GUEST
}

생성되는 DDL

CREATE TABLE MEMBER (
    member_id BIGINT NOT NULL AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL,
    age INTEGER,
    role_type VARCHAR(20) NOT NULL,
    description CLOB,
    created_date TIMESTAMP,
    last_modified_date TIMESTAMP,
    PRIMARY KEY (member_id),
    CONSTRAINT UK_MEMBER_EMAIL UNIQUE (email)
);

요약

핵심 어노테이션

어노테이션용도필수 여부
@Entity엔티티 클래스 지정✅ 필수
@Table테이블 이름 지정선택
@Id기본 키 지정✅ 필수
@GeneratedValue기본 키 자동 생성권장
@Column컬럼 상세 설정권장
@EnumeratedEnum 매핑Enum 사용 시 필수
@Lob대용량 데이터필요 시
@Transient매핑 제외필요 시

실무 체크리스트

✅ @Entity 클래스에 기본 생성자 추가 (protected)
✅ 기본 키 전략 선택 (MySQL=IDENTITY, Oracle=SEQUENCE)
✅ @Enumerated는 반드시 STRING 사용
✅ 날짜는 LocalDate/LocalDateTime 사용 (@Temporal 불필요)
✅ NOT NULL 컬럼은 nullable=false 명시
✅ 운영 환경에서는 hbm2ddl.auto=validate 또는 none
✅ Long 타입 + 대리키 + 자동 생성 전략 조합 권장

기본 키 전략 선택 가이드

// MySQL 환경
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

// Oracle 환경 (성능 최적화)
@SequenceGenerator(
    name = "SEQ_GEN",
    sequenceName = "MEMBER_SEQ",
    allocationSize = 50
)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_GEN")
private Long id;

// 개발 초기 (DB 미정)
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

주의사항

⚠️ 절대 금지

  • @Enumerated(EnumType.ORDINAL) 사용
  • 운영 환경에서 hbm2ddl.auto=create/update 사용
  • @Entity 클래스에서 기본 생성자 누락
  • 저장할 필드에 final 사용

💡 권장사항

  • 기본 키는 Long + 대리키 + 자동생성 전략
  • SEQUENCE 전략 시 allocationSize=50 설정
  • LocalDate/LocalDateTime 사용 (java.util.Date 지양)
  • 제약조건은 가급적 DB 레벨에서도 설정