변경 감지와 병합(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 |