엔티티 매핑 소개

minjun kim·2025년 6월 20일

JPA

목록 보기
4/4

객체와 테이블 매핑 : @Entity @Table
필드와 컬럼 매핑 : @Column
기본 키 매핑 : @Id
연관관계 매핑 : @ManyToOne @JoinColumn

@Entity

  • @Entity가 붙은 클래스는 JPA가 관리, 엔티티라 한다.
  • JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 필수

주의

  • 기본 생성자 필수(파라미터가 없는 public 또는 protected 생성자)
  • final 클래스, enum, interface, inner 클래스 사용X
  • 저장할 필드에 final 사용 X

@Entity 속성 정리

속성: name
• JPA에서 사용할 엔티티 이름을 지정한다.
• 기본값: 클래스 이름을 그대로 사용(예: Member)
• 같은 클래스 이름이 없으면 가급적 기본값을 사용한다.

@Table

@Table은 엔티티와 매핑할 테이블 지정

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

DDL을 애플리케이션 실행 시점에 자동 생성

• 테이블 중심 -> 객체 중심
• 데이터베이스 방언을 활용해서 데이터베이스에 맞는 적절한DDL 생성
• 이렇게 생성된 DDL은 개발 장비에서만 사용
• 생성된 DDL은 운영서버에서는 사용하지 않거나, 적절히 다듬은 후 사용

데이터베이스 스키마 자동 생성 - 속성

데이터베이스 스키마 자동 생성 - 주의

운영 장비에는 절대 create, create-drop, update 사용하면
안된다.
• 개발 초기 단계는 create 또는 update
• 테스트 서버는 update 또는 validate
• 스테이징과 운영 서버는 validate 또는 none

제약조건 추가: 회원 이름은 필수, 10자 초과X

• @Column(nullable = false, length = 10)

• 유니크 제약조건 추가

@Table(uniqueConstraints = {@UniqueConstraint( name = "NAME_AGE_UNIQUE",
columnNames = {"NAME", "AGE"} )})

• DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고
JPA의 실행 로직에는 영향을 주지 않는다.

매핑 어노테이션

@Column

@Enumerated

자바 enum 타입을 매핑할 때 사용

주의! ORDINAL 사용X

ORDINAL 방식의 문제점

🚨 문제 발생 가능성

  • enum의 순서가 바뀌거나 중간에 새로운 값이 추가되면 데이터 불일치 문제 발생
  • 예를 들어, CANCELED를 중간에 추가하면 기존 데이터와 충돌 가능
public enum OrderStatus {
    PENDING,    // 0
    SHIPPED,    // 1
    CANCELED,   // 2 (새로 추가)
    DELIVERED   // 3 (기존 2 → 3으로 변경됨!)
}

기존에 DELIVERED(2)로 저장된 데이터가 CANCELED(2)로 잘못 해석될 수 있음!

데이터 무결성 문제 발생


EnumType.STRING 방식 (추천)

@Enumerated(EnumType.STRING)
private OrderStatus status;

status 값이 SHIPPED일 때, DB에는 "SHIPPED" (문자열)로 저장

enum 값이 변경되어도 기존 데이터 영향 없음

sql
복사편집
SELECT * FROM order;
+----+-----------+
| id | status    |
+----+-----------+
| 1  | PENDING   |
| 2  | SHIPPED   |
| 3  | DELIVERED |
+----+-----------+

@Temporal

날짜 타입(java.util.Date, java.util.Calendar)을 매핑할 때 사용

참고: LocalDate, LocalDateTime을 사용할 때는 생략 가능(최신 하이버네이트 지원)

@Lob

데이터베이스 BLOB, CLOB 타입과 매핑

• @Lob에는 지정할 수 있는 속성이 없다.

• 매핑하는 필드 타입이 문자면 CLOB 매핑, 나머지는 BLOB 매핑

• CLOB: String, char[], java.sql.CLOB

• BLOB: byte[], java.sql. BLOB

@Transient

필드 매핑X

데이터베이스에 저장X, 조회X

주로 메모리상에서만 임시로 어떤 값을 보관하고 싶을 때 사용


기본 키 매핑

  • 직접 할당 : @Id만 사용
  • 자동 생성(@GeneratedValue)
    - Identity : DB에 위임, mysql
    • Seqience : DB 시퀀스 오브젝트 사용, orcle
      @SequenceGenerator 필요
    • Table : 키 생성용 테이블 사용, 모든 DB에서 사용
      @TableGenerator 필요
      • Auto : 방언에 따라 자동 지정, 기본값

권장하는 식별자 전략

기본 키 제약 조건 : null아님, 유일, 변하면 안되낟.
미래까지 이 조건을 만족하는 자연키는 찾기 어렵다. 대리키(대체키)를 사용하자
예를 들어 주민등록번호도 기본 키로 적잘하지 않다.
권장 : Long형 + 대체키 + 키 생성전략 사용


🔹 대체키 (Surrogate Key = 대리키)

의미 없는 숫자 값으로 만든, 순수하게 식별만을 위한 키

예:

id: 1, id: 2, id: 3 이런 식

보통 Long 타입, AUTO_INCREMENT or SEQUENCE로 자동 생성

👉 장점:

  • 절대 안 바뀜
  • 무의미한 값이라 충돌 안 남
  • 성능 좋음 (인덱싱 최적화)

예시

@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)  // 또는 SEQUENCE
    private Long id; // ✅ 대체키 (무의미한 숫자)

    private String name;

    @Column(unique = true)
    private String ssn; // 주민번호 등은 유니크 제약만 걸자
}

🔹 ManyToMany의 문제

그냥 쓰다보니까 ManyTOMany를 쓰지말라고해서 안썼고 중간테이블을 외우는 식으로 썼던거같다.
✅ 다대다 관계가 뭔데?
예를 들어:

하나의 주문은 여러 상품을 담을 수 있음
하나의 상품은 여러 주문에 포함될 수 있음

즉,

ORDER  ⬄  ITEM
  N         N

다대다 매핑을 하면
❌ 다대다를 직접 매핑하면 이런 문제가 생겨:

@ManyToMany
@JoinTable(name = "order_item",
    joinColumns = @JoinColumn(name = "order_id"),
    inverseJoinColumns = @JoinColumn(name = "item_id"))
private List<Item> items;

이렇게 하면:

문제점 이유

  1. 추가 필드 못 넣음 예: 수량(count), 가격(orderPrice) 같은 중간 정보
  2. 비즈니스 로직 표현 어려움 도메인 모델로 다루기 애매해짐
  3. JPA 관리 불안정 양방향 연관관계 관리가 어려움
  4. 성능 최적화 어려움 중간 테이블 자체에 대한 엔티티 제어 불가

✅ 그래서 중간에 "주문상품" (OrderItem) 엔티티를 둬서 푸는 거야

ORDER     →    ORDER_ITEM     ←    ITEM
  1   ⬄     N          N   ⬄    1

즉, 다대다를 두 개의 일대다, 다대일 관계로 쪼개는 것

🔥 한쪽 테이블에 넣으면 생기는 문제들

❌ 1. Item 엔티티에 count를 넣는 경우

@Entity
public class Item {
    ...
    private int count; // ❌ 주문마다 달라지는 값을 상품에 넣는다?
}

상품은 하나의 정보야 (예: "아이폰 1TB")

근데 count는 주문마다 달라지는 값이야

여러 주문이 하나의 상품을 쓰는데, count를 상품에 넣으면 누가 얼마나 샀는지 섞여버림

→ 주문 1에서는 2개 샀고, 주문 2에서는 5개 샀는데, Item.count는 몇일까? 🤯

❌ 2. Order에 countList, discountList 이런 식으로 넣는 경우

private List<Integer> countList;

이건 데이터 정합성 완전 박살남

items.get(0)에 해당하는 상품이 countList.get(0)이겠지? → 인덱스 맞추기 오류나기 딱 좋음

→ 유지보수 지옥 열림

class Order {
    private List<Item> items;            // 주문한 상품들
    private List<Integer> countList;     // 각 상품별 수량
    private List<Integer> discountList;  // 각 상품별 할인율
}

🔥 1. 인덱스가 어긋나면? 끝장

items.add(itemA);           // index 0
countList.add(2);           // index 0
discountList.add(10);       // index 0

items.remove(0);            // 상품만 삭제
// countList, discountList는 그대로… ❌

→ 상품과 수량/할인이 일치하지 않음

→ 정합성 붕괴: 누가 몇 개 샀는지, 얼마에 샀는지 영원히 모르게 됨

🔥 2. 구조가 서로 분리되어 유지보수 지옥

items, countList, discountList 전부 개별 리스트

항상 "같은 인덱스"를 공유한다고 믿고 있어야 해 → 사고 나기 딱 좋음

리스트 하나만 수정하면 다른 리스트는 망가짐

정상적인 구조

class OrderItem {
    private Item item;
    private int count;
    private int discountRate;
}

class Order {
    private List<OrderItem> orderItems;
}

→ 이제 OrderItem 객체 하나가 상품 + 수량 + 할인을 한 덩어리로 묶어줌

→ 수정/삭제/조작 다 안전하게 할 수 있어

→ 인덱스 걱정도 없음, orderItem.getItem(), getCount()으로 접근

✅ 한줄 요약
List + List 방식은 관계형 데이터 세계에서 가장 위험한 조합 중 하나야.
항상 관련 데이터는 객체로 묶어서 같이 움직이게 만들어야 해!

profile
배움의 흔적을 남기고 싶습니다.

0개의 댓글