[JPA] 엔티티(Entity) 매핑 (2)

2026. 3. 11. 21:18·Development/개발 공부

우리는 지난번 JPA에 대한 개념과 핵심 용어에 대해서 알아보았다.

이전 글 참조 | 2026.02.08 - [JPA] - [JPA] JPA(Java Persistence API)란? (1)

 

이번에는 JPA를 실제 코드로 다뤄보면서, JPA를 사용할 때 주의해야 할 점에 대해 알아보려 한다.

 

그전에 JPA를 왜 사용하는지에 대해 한번 더 짚고 넘어가자!

JPA는 ORM(Object-Relational Mapping) 기술을 통해 자바 코드 레벨에서 데이터베이스를 다룰 수 있게 해준다.

따라서 JPA에서 가장 중요한 포인트는 다음 두 가지이다.

  1. DB 테이블 구조를 자바 객체, 즉 엔티티로 매핑하는 것
  2. 데이터 조회나 변경 같은 DB 작업을 자바 코드로 구현하는 것

이제 위의 두 가지 관점을 기준으로 JPA에 대해 구체적으로 알아보자!

1번 만으로도 설명해야 할 내용이 많으므로, 2번은 차차 다루도록 하겠다....... 😅


단일 테이블인 경우

엔티티를 자바 객체로 정의하기 위해서는 테이블 구조가 먼저 필요하다.

MEMBER 테이블을 예시로 살펴보자.

필드명(Column) 타입 제약 사항
MEMBER_ID BIGINT PK, Auto Increment
LOGIN_ID VARCHAR(20) Not Null, Unique
NAME VARHCAR(20) Not Null
CREATED_AT TIMESTAMP Default CURRENT_TIMESTAMP

 

MEMBER 테이블 구조를 자바의 Entity로 구현한 결과는 다음과 같다.

@Entity // JPA가 관리하는 객체
@Table(name = "MEMBER") // DB의 MEMBER 테이블과 매핑
@Getter @Setter
public class Member {
    @Id // PK 지정
    @GeneratedValue(strategy = GenerationType.IDENTITY) // PK를 생성하는 방식
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "LOGIN_ID", nullable = false, unique = true, length = 20)
    private String loginId;

    @Column(nullable = false, length = 20)
    private String name;

    private LocalDateTime createdAt = LocalDateTime.now();
}

※ lombok 어노테이션 설명은 생략

 

id는 PK를 지정하는 field이므로 @Id 어노테이션을 붙여준다.

@GeneratedValue 어노테이션의 strategy 속성은 기본 키(PK)를 어떤 방식으로 생성할 것인지 결정하는 옵션이다.

 

PK를 생성하는 방식(strategy)은 다음과 같다.

  • IDENTITY: DB가 auto-increment로 PK 값을 생성하여, JPA가 조회하는 방식
    • 주로 MYSQL이 해당 방식 사용
    • INSERT를 통해 DB에 즉시 전송하면, DB는 데이터를 저장하고 PK ID를 생성하도록 강제
  • SEQUENCE: DB의 시퀀스 객체를 사용하여 PK 값을 생성하는 방식
    • 주로 Oracle, PostgreSQL이 해당 방식 사용
    • INSERT하기 전에 시퀀스에서 다음 PK 값 조회
  • TABLE: 별도로 PK 값을 저장하는 테이블을 만들어 PK 값을 조회하는 방식
    • JPA가 해당 테이블에 접근하여 현재 ID 값을 읽고, 1 증가시킨 후 다시 저장하는 작업을 수행
  • AUTO: 사용하는 DB에 따라 JPA가 IDENTITY 또는 SEQUENCE 선택

따라서 사용하는 DB를 MYSQL이라고 가정하였을 때,

PK를 가지는 id 필드는 auto-increment 기능을 가지기 때문에 → PK 생성 전략으로 IDENTITY 방식을 사용한다.

 

참고로 Snake와 Camel Case가 같은 field는 column 이름을 따로 명시할 필요가 없다.

 

복합키 설정

DB에서 PK를 지정할 때는 하나의 컬럼만 사용할 수도 있지만, 두 개 이상의 컬럼을 함께 사용할 수도 있다.

이처럼 2개 이상의 컬럼을 하나의 PK로 지정하는 것을 "복합키"라고 한다.

엔티티에서 복합키를 설정하는 방식은 @IdClass와 @EmbeddedId 두 가지 이다.

 

먼저 복합키를 설정하기 위해서는 복합키를 구성하는 id 클래스를 따로 선언해야 한다.

다음은 MemberId 클래스의 예시이다.

@NoArgsConstructor
@EqualsAndHashCode
public class MemberId implements Serializable {
    private String id1;
    private String id2;
}

 

id1과 id2가 하나의 복합키로 구성되었다. 

@NoArgsConstructor가 필요한 이유는 JPA는 리플렉션을 이용해 row를 객체로 변환하기 때문이다.

(런타임에 클래스 정보를 활용해 기본 생성자를 호출하여 인스턴스를 생성하는 방식)

리플렉션에 대해 궁금하다면 .. 2025.10.23 - [Java] - [Java] Reflection

 

@EqualsAndHashCode가 필요한 이유는 두 식별자(id1, id2)를 기준으로 객체를 비교해야 하기 때문이다.

 

DB에 인덱스가 설정된 경우

만약 MEMBER 테이블에서 loginId를 인덱스로 설정했다면, 엔티티 클래스는 이 인덱스를 어떻게 표현할까?

@Entity
@Table(
    name = "member",
    indexes = {
        @Index(name = "idx_member_login_id", columnList = "login_id")
    }
)
public class Member {
	...
}

먼저 Member 엔티티 위에 indexes 속성을 설정해야 한다.

이후 columnList에 인덱스를 걸 컬럼들을 나열하면 된다.

복합 인덱스라면 ,(쉼표)로 구분하여 나열한다. (login_id, name, ...)


연관 관계(조인)가 있는 경우

연관 관계(조인)가 있는 두 개 이상의 테이블을 객체로 매핑하기 위해서는 엔티티 간 관계 매핑이 필요하다.

위 MEMBER 테이블과 조인 관계인 ORDERS 테이블의 구조를 살펴보자.

필드명(Column) 타입 제약 사항
ORDER_ID BIGINT PK, Auto Increment
MEMBER_ID BIGINT FK (Member.MEMBER_ID)
TOTAL_PRICE INT Not Null, Default: 0
CREATED_AT TIMESTAMP Default CURRENT_TIMESTAMP
 
ORDERS 테이블의 MEMBER_ID는 MEMBER 테이블의 MEMBER_ID를 참조하는 외래키이다.
 
 
MEMBER 테이블과 ORDERS 테이블을 자바의 Entity로 구현한 결과는 다음과 같다.
 

[MEMBER 테이블]

@Entity
@Table(name = "MEMBER")
@Getter @Setter
public class Member {
    ...

    private LocalDateTime createdAt = LocalDateTime.now();
    
    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();
}

 

[1]

MEMBER와 ORDERS의 관계를 생각해보자.

한 회원은 여러 주문을 가질 수 있고, 각 주문은 하나의 회원에만 연관된다!

즉, 한 회원(MEMBER)에 여러 주문(ORDERS)이 매핑되는 1:N(일대다) 관계이다..

따라서 Member 클래스에서는 주문을 여러 개 가질 수 있으므로 orders를 선언하였다.

 

[2]

1:N 관계에서 1(Member)은 @OneToMany 어노테이션을 가지게 된다.

이때 mappedBy라는 속성은 반대편인 N(Order) 엔티티에 있는 외래키 관리 필드(MEMBER_ID)를 가리키게 된다.

(DB의 MEMBER_ID를 객체에서는 member라는 field로 들고 있기 때문)
따라서 member를 mappedBy의 속성 값으로 지정해야 한다.

 

[ORDERS 테이블]

@Entity
@Table(name = "ORDERS")
@Getter @Setter
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ORDER_ID")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "MEMBER_ID")
    private Member member;

    @Column(nullable = false)
    private int totalPrice = 0;

    private LocalDateTime createdAt = LocalDateTime.now();
}

 

N 관계인 Order 클래스를 살펴보자.

(ORDER는 DB 예약어이므로, 테이블명을 ORDERS로 지정한 후 → @Table 어노테이션으로 Order 클래스에 매핑하였다.)

 

[1]

N(Order) 엔티티에 있는 1(member) 필드는 @ManyToOne 어노테이션을 가지게 된다.

이때 @JoinColumn(name = "MEMBER_ID")를 사용하여 DB의 외래 키 컬럼과 직접 매핑한다.

 

[2]

1:N 관계에서는 외래키를 가지고 있는 N이 주인, 1이 비주인 관계라고 한다! **

 

[3]

@ManyToOne에 있는 fetch 속성에 대해서 설명하도록 하겠다.

fetch는 연관된 엔티티를 언제 로딩할지 저장하는 옵션으로, 가질 수 있는 값은 FetchType.EAGER와 FetchType.LAZY 두 개이다.

  • FetchType.EAGER: 즉시 로딩으로, 엔티티를 조회할 때 연관된 필드도 바로 조회
    • @ManyToOne 어노테이션을 갖는 FetchType의 default 값
  • FetchType.LAZY: 지연 로딩으로, 엔티티 조회 후 연관된 필드에 접근하는 시점에 조회
    • @OneToMany 어노테이션을 갖는 FetchType의 default 값
    • 지연 로딩이 설정된 연관 필드는 초기에 프록시 객체 → 연관 필드에 접근할 때 프록시를 초기화함
      ex) Member 엔티티의 orders에 접근할 때, MEMBER_ID로 ORDERS를 조회한 결과를 객체에 채운다.

Cascade

Cascade는 특정 엔티티의 상태 변화가 연관된 엔티티로도 전이되도록 지정하는 기능이다.

만약 Cascade를 사용하지 않는다면, 연관된 엔티티들 각각에 persist나 remove를 개별적으로 적용시켜 주어야 한다.

 

Cascade의 대표적인 6가지 타입은 다음과 같다!

(※ 특정 엔티티는 부모, 연관된 엔티티는 자식 엔티티라고 부른다.)

  • PERSIST(영속화 전이): 부모를 저장(persist)할 때, 연관된 자식도 함께 저장(영속화)
  • REMOVE(삭제 전이): 부모를 삭제(remove)할 때, 연관된 자식도 함께 삭제
  • MERGE(병합 전이): 준영속 상태의 부모를 병합(merge)할 때, 연관된 자식도 함께 병합
  • REFRESH(새로고침 전이): 부모를 새로고침(refresh, DB 상태로 되돌림)할 때, 연관된 자식도 함께 새로고침
  • DETACH(준영속 전이): 부모를 준영속 상태로 만들 때, 연관된 자식도 함께 준영속 상태가 됨
  • ALL(모두 전이): 위 5가지가 모두 전이

Cascade를 엔티티에 적용한 예시는 다음과 같다.

@Entity
@Table(name = "MEMBER")
@Getter @Setter
public class Member {
    ...
    
    @OneToMany(mappedBy = "member", cascade = CascadeType.REFRESH)
    private List<Order> orders = new ArrayList<>();
}

 

orders에 CascadeType으로 REFRESH를 설정하면,

부모인 member를 refresh()할 때 연관된 orders들도 최신 상태로 refresh() 된다.

만약 CascadeType을 REMOVE로 설정하면 member가 삭제되면, 해당 회원이 주문한 orders도 모두 삭제된다... (조심)

 

OrphanRemoval

OrphanRemoval은 CascadeType.REMOVE처럼 자식 엔티티들을 함께 삭제하는 기능을 제공한다.

하지만 차이점은 부모-자식의 연관 관계가 끊어지면 자식 엔티티는 자동으로 삭제된다는 점이다.

 

예를 들어 "한 회원이 가진 주문 목록에서 특정 주문을 제거"하면

↓

즉 부모 컬렉션(주문 목록)에서 자식 엔티티(주문)가 제거된다면..

해당 주문은 orphan이 되어 ORDERS 테이블에서 자동으로 삭제된다.

(JPA가 트랜잭션을 커밋할 때 orphan 엔티티를 감지한 뒤, DB에서 자동으로 DELETE 쿼리를 실행한다.)

 

해당 기능은 @OneToMany 관계에서 주로 쓰인다.

다만 1:N 관계에서 N이 다른 엔티티와 연관되지 않고 독립적이어야 한다. 만약 N이 다른 엔티티를 참조하면 삭제에 실패하기 때문이다.

public class Member {
    ...    
    @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Order> orders = new ArrayList<>();
}

OrphanRemoval은 다음과 같이 쓰인다.


이렇게 JPA가 객체와 데이터베이스 간의 관계를 자동으로 관리해주는 여러 매핑 방식들을 살펴보았다.

다음에는 자바에서 쿼리를 조작하는 방법에 대해 알아보도록 하자.. !

 

👏

 

'Development > 개발 공부' 카테고리의 다른 글

[Docker] 도커 컴포즈(Docker Compose) & 볼륨(Docker Volume) (2)  (1) 2026.04.06
[JPA] JPA 쿼리 (3)  (0) 2026.03.17
[JPA] JPA(Java Persistence API)란? (1)  (0) 2026.02.08
[Git] 깃 브랜치 전략(Git Flow, Github Flow)(+TBD)  (0) 2025.12.14
AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)  (0) 2025.11.10
'Development/개발 공부' 카테고리의 다른 글
  • [Docker] 도커 컴포즈(Docker Compose) & 볼륨(Docker Volume) (2)
  • [JPA] JPA 쿼리 (3)
  • [JPA] JPA(Java Persistence API)란? (1)
  • [Git] 깃 브랜치 전략(Git Flow, Github Flow)(+TBD)
jjangsudiary
jjangsudiary
jjangsudiary 님의 블로그 입니다.
  • jjangsudiary
    jjangsudiary 님의 블로그
    jjangsudiary
  • 전체
    오늘
    어제
    • 분류 전체보기 (81) N
      • 이모저모 (0)
        • 회고 (0)
      • Development (17) N
        • 개발 공부 (14) N
        • 프로젝트 (2)
      • Android (10)
        • Compose (1)
      • AI (15)
      • Computer Science (25)
        • 네트워크 (8)
        • 데이터베이스 (10)
        • 운영체제 (6)
        • 자료구조 (0)
        • 컴퓨터구조 (1)
      • Java (9)
        • 디자인패턴 (2)
      • Spring (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • GitHub
  • 공지사항

  • 인기 글

  • 태그

    운영체제
    인공지능
    CS
    프로그래머스
    os
    Ai
    코딩 테스트
    딥러닝
    머신러닝
    파이썬
    android
    백준
    database
    안드로이드
    자바
    db
    java
    Python
    TensorFlow
    baekjoon
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
jjangsudiary
[JPA] 엔티티(Entity) 매핑 (2)
상단으로

티스토리툴바