스프링 부트와 JPA 활용

2026. 1. 26. 17:03·JPA

변경 감지와 병합(merge)

준영속 엔티티를 수정하는 2가지 방법

- 변경 감지(dirty checking) 사용

- 병합(merge) 사용

 

1. 변경 감지 사용

@Transactional
	void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
    	Item findItem = em.find(Item.class, itemParam.getId()); //같은 엔티티를 조회한다.
        findItem.setPrice(itemParam.getPrice()); //데이터를 수정한다.
}

- 영속성 컨텍스트에서 엔티티를 다시 조회한 후 데이터를 수정하는 방법

- 트랜잭션 안에서 엔티티를 다시 조회 -> 변경할 값 선택 -> 트랜잭션 커밋 시점에서 더티체킹 동작 -> update sql 실행

 

 

2. 병합 사용

@Transactional
	void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
    	Item mergeItem = em.merge(itemParam);
}

- merge() 실행

- 준영속 엔티티의 식별자 값으로 영속 엔티티 조회

- 영속 엔티티의 값을 준영속 엔티티 값으로 모두 교체 (병합)

- 트랜잭션 커밋 시점에서 변경 감지 기능이 동작해 DB에 update sql 실행

* 원하는 속성만 선택해서 변경할 수 있지만, 병합을 사용하면 모든 속성이 변경됨. 병합 시 값이 없으면 null로 업데이트 될 수 있다.

 

결론은 변경감지를 사용해라. 

 

 

API 개발 (요약)

1. 엔티티를 API 스펙에 노출하지 않기

- 엔티티에 프레젠테이션 계층을 위한 로직이 추가됨

- 검증 로직이 들어가게 됨 (@NotEmpty 등)

- 회원 엔티티를 위한 API가 다양하게 만들어질 때, 한 엔티티에 가각의 API를 위한 모든 요청 요구사항을 담기 힘듬

- 엔티티가 변경되면 API 스펙이 변경됨

- 엔티티의 모든 값이 노출 됨

- 컬렉션으로 직접 반환하면 API 스펙 변경하기 번거로워짐

*결론: API응답 스펙에 맞춰 DTO를 파라미터로 받자

 

2. 지연 로딩과 조회 성능 최적화

- 기본 원칙은 엔티티를 DTO로 변환해서 반환

- 1차 성능 최적화는 fetch join 사용 (N+1 줄이기)

대부분의 성능 최적화는 이부분에서 해결 가능, 영속성 컨텍스트 활용 가능, 재사용성 좋음

 

그래도 느리면? -> DTO 직접 조회

장점

- 성능이 정말 중요한 경우에 최소 컬럼만 조회할 수 있음.

- 대량 조회, 통계, 목록 API에 강함

 

단점 (트레이드 오프 영역)

- 영속성 컨텍스트 사용X

- 재사용성 낮음

- 쿼리와 DTO가 강결합됨

(성능을 위해 설계 일부를 포기하는 방법)

 

Repository 분리 전략

repository는 순수하게 엔티티를 조회하는 용으로 사용하는것을 권장한다.

 

repository
 ├─ OrderRepository        // 엔티티 조회
 └─ query
     └─ OrderQueryRepository  // DTO 전용 조회

 

- 책임 분리

- 엔티티 모델 보호

- 조회 최적화는 조회 전용 코드에서만 사용하게 분리

 

API - 컬렉션 조회 (요약)

엔티티 조회

- 컬렉션 조회도 마찬가지로 DTO로 변환 후 조회하면 된다.

- fetch join으로 쿼리 수 최적화 하면 됨

컬렉션 페이징

- 컬렉션은 fetch join시 페이징이 불가능

(1:N을 컬렉션 fetch join하면 SQL 결과가 뻥튀기 됨. 주문1개가 주문상품 3개면 주문 row가 3줄로 복제, 따라서 원하는 주문기능으로 페이징할 수 없음)

- ToOne관계는 fetch join으로 쿼리 수 최적화

- 컬렉션은 fetch join 대신, 지연 로딩을 유지하고 batchSize 옵션을 사용해서 최적화

* batchSize는 하이버네이트가 지금 초기화 필요한 order_id를 모아서 where order_id in (?, ?, ?, ...)로 몇번의 쿼리를 묶어서 가져옴

 

DTO 직접 조회

성능이 중요한 경우 DTO 직접 조회할 수 있음 (대신 위에서 말햇듯 단점 존재)

 

단건 조회시 (루트 1번, 컬렉션 N번 실행)

- ToOne (N;1, 1:1) 먼저 조회 후 toMany는 각각 별도로 처리.

(ToOne은 조인해도 데이터 row 증가X, toMany는 조인하면 row 증가O, 따라서 toMany는 별도의 메서드로 조회)

 

여러건 조회시 (루트 1번, 컬렉션 1번)

- ToOne 먼저 조회 -> 여기서 얻은 식별자로 ToMany 관계은 OrderItem 한꺼번에 조회

- MAP을 사용해 매칭 성능 향상 (O(1))

 

플랫 데이터 최적화 (한방 쿼리)

- 쿼리는 한번이지만 join으로 인해 중복 데이터가 추가되므로 여러건 조회방법보다 느릴 수 있음.

- 페이징 불가

 

결론

엔티티 조회 방식으로 우선 접근

페이징이 필요한가? -> BatchSize로 최적화

페이징이 필요없는가? -> fetch join 사용

 

엔티티 조회 방식으로 해결 안됨 -> DTO 조회 방식 사용

그래도 안되면 네이티브sql, jdbctemplate 사용

 

정답은 없고 규모나 상황에 맞게 쓰면 되는것이다. (대부분 fetch join으로 해결 가능함)

 

 

 

'JPA' 카테고리의 다른 글

스프링 데이터 JPA  (0) 2026.01.29
JPA의 값 타입  (0) 2025.12.27
프록시와 연관관계 관리  (0) 2025.12.26
고급 매핑 - 상속 관계, Mapped Superclass  (0) 2025.12.25
엔티티 매핑, 연관관계  (0) 2025.12.12
'JPA' 카테고리의 다른 글
  • 스프링 데이터 JPA
  • JPA의 값 타입
  • 프록시와 연관관계 관리
  • 고급 매핑 - 상속 관계, Mapped Superclass
공부처음하는사람
공부처음하는사람
  • 공부처음하는사람
    lazzzykim
    공부처음하는사람
  • 전체
    오늘
    어제
    • 분류 전체보기 (161)
      • Kotlin (31)
      • Java (56)
      • Spring (44)
      • JPA (8)
      • Algorithm (3)
      • TroubleShooting (1)
      • 내일배움캠프 프로젝트 (14)
      • Setting (2)
      • ... (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • 링크

  • 인기 글

  • 태그

    Di
    캡슐화
    배열
    트랜잭션
    OCP
    다형성
    싱글톤
    java
    빈 생명주기
    내일배움캠프
    kotlin
    제네릭
    김영한의 실전 자바
    김영한의 실전자바
    김영한
    중첩클래스
    예외처리
    언체크예외
    spring
    jpa
  • hELLO· Designed By정상우.v4.10.3
공부처음하는사람
스프링 부트와 JPA 활용
상단으로

티스토리툴바