최범균 님의 "도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지" 책을 정리한 포스팅 입니다.
1. JPA를 이용한 리포지터리 구현
모듈 위치
- 리포지토리 인터페이스는 도메인 영역에 속함
- 리포지토리를 구현한 클래스는 인프라스트럭쳐 영역에 속함
2. 스프링 데이터 JPA를 이용한 리포지터리 구현
3. 매핑 구현
4. 애그리거트 로딩 전략
1:N 관계가 여러 개 있을 경우
- Fetch Join 쿼리의 결과는 될 수 있는 모든 경우의 행을 반환함 (카테시안 곱)
- ❌ 실제 필요한 행의 갯수보다 더 많은 행을 얻어옴
- Hibernate에서는 Fetch Join 시, distinct 처리함
- 같은 엔티티가 중복 생성되지 않게 함
- ⚠️ 성능 부하가 일어날 수 있음 (DB, JVM의 연산이 늘어남)
예시) Order - OrderLine, Payment
더보기
SELECT o
FROM Order o
JOIN FETCH o.orderLines
JOIN FETCH o.payments
실제 필요한 행보다 더 많은 행이 조회됨
- 필요한 행: 3(OrderLines) + 2(Payments) = 5 행
- 모든 행: 3(OrderLines) × 2(Payments) = 6 행 (중복된 데이터 포함)
해결 방법
구분 | Lazy + @BatchSize | EntityGraph |
쿼리 분리 (DTO 조회)
|
핵심 개념 | - fetch join은 최대 1개만 사용 - 나머지는 LAZY + @BatchSize(IN 묶음 조회)로 초기화 |
필요한 연관관계만 그래프로 fetch |
조인으로 필요한 필드만 DTO/프로젝션으로 한 방 조회
|
장점 | - 곱집합 방지 - 실무 기본값으로 성능 안정 |
- JPQL 수정 없이 유연한 로딩 제어 - 단건 조회에 적합 |
- 고성능
- 중복 없음 - 네트워크/메모리 효율 - 페이징 친화적 |
단점 | - 트리거 방식(접근 시 로딩) - @BatchSize 설정 필요 - 컬렉션 fetch join과 페이징 병행 지양 |
- 컬렉션을 여러 개 fetch하면 곱집합 가능 - 조합 관리 비용 |
- 엔티티 아님(변경 추적 X)
- 재사용 제한 - 매핑 코드/쿼리 관리 필요 |
예시) Lazy + @BatchSize
더보기
// Order 엔티티
@Entity
public class Order {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
@BatchSize(size = 100) // 컬렉션 배치 크기
private List<OrderLine> orderLines = new ArrayList<>();
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
@BatchSize(size = 100)
private List<Payment> payments = new ArrayList<>();
// ...
}
// 목록 조회: fetch join은 최대 1개만 (혹은 아예 없이 순수 엔티티만)
@Query("""
select distinct o
from Order o
join fetch o.orderLines -- 필요시 1개만
where o.status = :status
""")
List<Order> findWithLines(@Param("status") OrderStatus status);
- 이후 o.getPayments() 접근 시 @BatchSize로 IN 쿼리(묶음) 실행
select *
from payment
where order_id in (?, ?, ... 최대 100개 ...)
- 영속성 컨텍스트 내 부모 fk를 기준으로 100개씩 IN 구문을 사용하여 조회함
예시) EntityGraph
더보기
// 엔티티에 네임드 엔티티그래프 정의
@Entity
@NamedEntityGraphs({
@NamedEntityGraph(
name = "Order.withLines",
attributeNodes = @NamedAttributeNode("orderLines")
),
@NamedEntityGraph(
name = "Order.withPayments",
attributeNodes = @NamedAttributeNode("payments")
)
})
public class Order { /* ... */ }
// Repository 메서드에 적용 (단건/특정 케이스)
@EntityGraph(value = "Order.withLines", type = EntityGraph.EntityGraphType.LOAD)
@Query("select o from Order o where o.id = :id")
Optional<Order> findOneWithLines(@Param("id") Long id);
예시) 쿼리 분리 (DTO 조회)
더보기
// 조회 전용 DTO
public record OrderView(Long orderId, int lineCount, int paymentCount,
String userName, BigDecimal totalAmount) {}
// 한 방 조회 (조인 + 그룹)
List<OrderView> findOrderViews() {
return em.createQuery("""
select new com.example.api.view.OrderView(
o.id,
count(distinct l.id),
count(distinct p.id),
u.name,
sum(l.price * l.quantity)
)
from Order o
join o.user u -- 엔티티 연관이 없다면 on 조건으로 조인
left join o.orderLines l
left join o.payments p
where o.createdAt between :from and :to
group by o.id, u.name
order by o.id desc
""", OrderView.class)
.setParameter("from", from)
.setParameter("to", to)
.setMaxResults(50)
.getResultList();
}
5. 애그리거트의 영속성 전파
- 애그리거트 상태가 온전하기 위해서는 루트만 저장/삭제하는 것이 아니라 루트에 속한 모든 객체를 저장/삭제해야 함
cascade 속성
- 연관된 엔티티에 대해 부모의 엔티티 연산을 자식에게 전파하는 옵션
타입 | 저장 위치 | cascade 필요 여부 | 이유/동작 |
@Entity | 별도 테이블 | ✅ | 부모에서 자식 엔티티 연산을 전파(생성/삭제 등) |
@Embeddable | 부모 엔티티 테이블의 컬럼 | ❌ | 부모의 상태에 포함되어 자동 저장/삭제 |
예시) @Entity
더보기
@Entity
class Order {
@OneToMany(mappedBy = "order",
cascade = {CascadeType.PERSIST, CascadeType.REMOVE},
orphanRemoval = true)
private List<OrderLine> lines = new ArrayList<>();
}
예시) @Embeddable
더보기
@Embeddable
class Address {
private String city;
private String street;
}
@Entity
class User {
@Embedded
private Address address; // cascade 불필요: User와 함께 자동 저장/삭제
}
6. 식별자 생성 기능
- 사용자 직접 생성: 필드 값 사용 (email 등)
- 도메인 로직으로 생성: 도메인 서비스에서 생성하기
- DB를 이용한 일련번호 사용
7. 도메인 구현과 DIP
'Code' 카테고리의 다른 글
[도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지] 8. 애그리거트 트랜잭션 관리 (0) | 2025.06.19 |
---|---|
[도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지] 7. 도메인 서비스 (0) | 2025.06.19 |
[도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지] 3. 애그리거트 (0) | 2025.06.18 |
[도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지] 2. 아키텍처 개요 (0) | 2025.06.18 |
[도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지] 1. 도메인 모델 시작하기 (0) | 2025.06.16 |