도메인 주도 개발 시작하기 : 5장 스프링 데이터 JPA를 이용한 조회 기능

일단 해볼게·2025년 8월 3일
0

book

목록 보기
22/31

5.1 시작

  • CQRS
    • 명령모델과 조회 모델을 분리하는 패턴
  • 엔티티, 애그리거트, 리포지터리
    • 상태를 변경할 때 사용 → 명령 모델

    • DAO

      • DB 접근 모델을 추상화한 객체
    • 리포지터리
      - 도메인 중심의 컬렉션 인터페이스

      // DAO 스타일
      public class MemberDao {
          public Member findById(Long id) {
              return jdbcTemplate.queryForObject(...);
          }
      
          public void save(Member member) {
              jdbcTemplate.update(...);
          }
      }
      
      // Repository 스타일 (Spring Data JPA 기준)
      public interface MemberRepository extends JpaRepository<Member, Long> {
          Optional<Member> findByEmail(String email);
      }
      

5.2 검색을 위한 스펙

  • Specification
    • 애그리거트가 특정 조건을 충족하는지를 검사

    • 검색 조건을 다양하게 조합해야할 때 사용

      public interface Speficiation<T> {
      	public boolean isSatisfiedBy(T agg);
      }
    • 리포지터리에 사용하면 agg는 애그리거트 루트

    • DAO에 사용하면 agg는 검색 결과로 리턴

5.3 스프링 데이터 JPA를 이용한 스펙 구현

public class OrdererldSpec implements Specification<OrderSummary> {
	private String ordererld;
	public OrdererIdSpec(String ordererld) {
		this.ordererld = ordererld;
	}

	@Override
	public Predicate toPredicate(Root<OrderSummary> root,
	CriteriaQuery<?> query,
	CriteriaBuilder cb) {
		return cb.equal(root .get(OrderSummary_.ordererld), ordererld);
	}
}
  • ordererId와 동일한지 비교하는 Predicate 생성
  • 정적 메타 모델
    • @StaticMetamodel 어노테이션을 이용해서 관련 모델 지정
    • 문자열을 이용해서 구현할 수도 있지만 오타, 코드 자동완성 기능을 이용하지 못해 정적 메타모델 사용
    • 람다식 이용 가능
    • 예시 > OrderSummary_

5.4 리포지터리/DAO에서 스펙 사용하기

public interface OrderSummaryDao
	extends Repository<OrderSummary, String> {
	List<OrderSummary> findAll(Specification<OrderSummary> spec);
}
  • 스펙을 파라미터로 넣고 findAll() 메서드 적용

5.5 스펙 조합

  • and, or로 스펙 조합
  • not()
    • 조건을 반대로 적용
  • where()
    • null 전달 시 아무 조건도 생성하지 않는 스펙 객체 리턴

5.6 정렬 지정하기

findByOrdererldOrderByOrderDateDescNumberAsc()
  • 정렬 조건이 2개인 경우
List<OrderSummary> findByOrdererId(String ordererld, Sort sort);
List<OrderSummary> findAll(Specification<OrderSummary> spec, Sort sort);
Sort sort = Sort.by("number").ascending();
List<OrderSummary> results = orderSummaryDao.findByOrdererId("userl", sort);
  • Sort 타입으로 쿼리 정렬

5.7 페이징 처리하기

  • Pageable 타입 이용
    • PageRequest를 이용해 생성
    • Sort 객체 사용 가능
    • Count 쿼리도 실행된다.
    • 스펙에서도 이용 가능
  • 처음 N개의 데이터가 필요할 때
    List<MemberData> findFirst3ByNameLike0rderByName(String name)

5.8 스펙 조합을 위한 스펙 빌더 클래스

  • if 문으로 스펙을 조합하지 않고 스펙 빌더를 이용해 조합한다.
    Specification<MemberData> spec = SpecBuilder.builder(MemberData.class)
    	.ifTrue(earchRequest.isOnlyNotBlocked(),
    		() -> MemberDataSpecs.nonBlocked())
    	.ifHasText(searchRequest.getName(),
    		name -> MemberDataSpecs.nameLike(searchRequest.getName()))
    	.toSpecQ;
    
    List<MemberData> result = memberDataDao.findAll(spec, PageRequest.of(0, 5));
    • 메서드 체인으로 코드 가독성 증가 및 구도 단순화

5.9 동적 인스턴스 생성

public interface OrderSummaryDao
extends Repository<OrderSummary, String> {
	@Query("""
		select new com.myshop.order.query.dto.OrderView(
			o.number, o.state, m.name, m.id, p.name
		)
		from Order o join o.orderLines ol. Member m. Product p
		where o.orderer.memberld .id = :ordererld
		and o.orderer.memberld.id = m.id
		and index(ol) = 0
		and ol.productld.id = p.id
		order by o.number.number desc
		"""")
		List<OrderView> findOrderView(String ordererld);
}
  • new OrderView로 동적 인스턴스 생성
    • 생성자에 인자로 필요한 값 전달
    • 지연/즉시 로딩과 같은 고민 없이 원하는 모습으로 데이터 조회 가능

5.10 하이버네이트 @Subselect 사용

@Entity
@Immutable
@Subselect(
"""
	select o.order_number as number,
	o.version, o.orderer_id, o.orderer_name,
	o.total_amounts, o.receiver_name, o.state, o.order_date,
	p.product_id, p.name as product_name
	from purchase_order o inner join order_line ol
	on o.order_number = ol.order_number
	cross join product p
	where
	ol.line_idx = 0
	and ol.product_id = p.product_id"""
)
  • 쿼리 결과를 @Entity로 매핑할 수 있는 기능
  • 쿼리 실행 결과를 매핑할 테이블처럼 사용
  • 처럼 수정 불가능
  • @Immutable
    • 엔티티의 매핑 필드가 변경되어도 DB에 반영하지 않고 무시
  • @Synchronize
    • 엔티티 상태 변경 후 @Subselect로 조회 시 변경사항 반영된 상태로 조회
    • 변경이 발생하면 flush
profile
시도하고 More Do하는 백엔드 개발자입니다.

0개의 댓글