DaehunGwak / study-start-ddd

'도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지' 책 스터디
5 stars 3 forks source link

5주차 - 05. 스프링 데이터 JPA를 이용한 조회 기능 #6

Open DaehunGwak opened 1 year ago

DaehunGwak commented 1 year ago

진도

  1. 스프링 데이터 JPA를 이용한 조회 기능

일정

DaehunGwak commented 1 year ago

5.1 시작에 앞서

5.2 검색을 위한 스펙

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

5.5 스펙 조합

5.6 정렬 지정하기

5.7 페이징 처리하기

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

5.9 동적 인스턴스 생성

5.10 하이버네이트 @Subselect 사용

progress0407 commented 1 year ago

원본 보러 가기

chapter 5. 스프링 데이터 JPA를 이용한 조회 기능

Intro

전반적으로 Criteria 라는 기술을 사용하고 있다.

그러나 이 기술은 실무에 적용하기엔 너무나 번잡하고 더 좋은 대안들이 있다. (by 김영한님)

QueryDSL을 사용하도록 하자.

[덧붙임 생각]

Repository의 메서드를 생략하기 위해서 Spec을 생성하는데... 이럴 바엔 차라리 Spec을 생성하는게 나을 것 같다.

5.1 시작에 앞서

5.2 검색을 위한 스펙

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

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

5.5 스펙 조합

5.6 정렬 저장하기

Criteria

그나마 괜찮다고 생각한 기능이 있지만

Sort sort = Sort.by("number").ascending().and(Sort.by("orderDate").decending())

이것마저도 QueryDsl은 더 간단하게 기술할 수 있다.

QueryDSL

List<Member> result = queryFactory
 .selectFrom(member)
 .where(member.age.eq(100))
 .orderBy(member.age.desc(), member.username.asc().nullsLast())
 .fetch();

5.7 페이징 처리하기

Spring Data JPA - PageRequest

PageRequest pageReq = PageRequest.of(1, 10);
List<Xxx> Xxxs = XxxDao.findBy("사용자", pageReq);

QueryDSL

List<Member> result = queryFactory
 .selectFrom(member)
 .orderBy(member.username.desc())
 .offset(1) //0부터 시작(zero index)
 .limit(2) //최대 2건 조회
 .fetch();

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

QueryDSL - BooleanBuilder

@Test
public void XxxService_BooleanBuilder() throws Exception {
    String usernameParam = "member1";
    Integer ageParam = 10;
    List<Member> result = searchMember1(usernameParam, ageParam);
 }

    private List<Member> searchMember1(String usernameCond, Integer ageCond) {
    BooleanBuilder builder = new BooleanBuilder();
    if (usernameCond != null) {
        builder.and(member.username.eq(usernameCond));
    }
    if (ageCond != null) {
        builder.and(member.age.eq(ageCond));
    }
    return queryFactory
    .selectFrom(member)
    .where(builder)
    .fetch();
}

QueryDSL - WhereParam

public void XxxService_WhereParam() throws Exception {
    String usernameParam = "member1";
    Integer ageParam = 10;
    List<Member> result = searchMember2(usernameParam, ageParam);
}

private List<Member> searchMember2(String usernameCond, Integer ageCond) {
    return queryFactory
    .selectFrom(member)
    .where(usernameEq(usernameCond), ageEq(ageCond))
    .fetch();
}
private BooleanExpression usernameEq(String usernameCond) { 
    return usernameCond != null ? member.username.eq(usernameCond) : null;
}
private BooleanExpression ageEq(Integer ageCond) {
    return ageCond != null ? member.age.eq(ageCond) : null;
}

5.9 동적 인스턴스 생성

QueryDsl Version

List<MemberDto> result = queryFactory
 .select(new QMemberDto(member.username, member.age))
 .from(member)
 .fetch();

5.10 하이버네이트 @Subselect 사용

RDBMS의 View 기능을 JPA에서 구현한 것으로 보인다.

그러나 나라면

일반 Join + DTO + @Tx ReadOnly

를 사용할 것 같다.

엄마 vs 아빠 싸움

헛소리 주의

엄마 - 김영한님

아빠 - 최범균님

결론

제가 영한님을 좋아하는 것은 사실입니다만

image

아무래도 JPA만큼은 영한님 말씀을 ...

kdg0209 commented 1 year ago

CQRS 패턴



Pageable

Page<Member> findByUsed(Pageable pageable);
List<Member> findByUsed(Pageable pageable);
List<Member> findAll(Pageable pageable);


하이버네이트의 @Subselect

Order order = orderRepository.findById(1L);
order.changeShippingInfo(newInfo); // 상태 변경

// 변경 내역이 DB에 반영되지 않았는데 조회
List<OrderSummary> summaries = orderSummary.findByOrdererId(1L);
developer-wonjin commented 1 year ago

5장 리포지토리의 조회기능

5.1. 검색을 위한 스펙

SpringDataJPA를 사용하는 경우

public interface OrderRepository extends JpaRepository<Order, Long>, JpaSpecificationExecutor<Order>{
   //공백   
}

위 소스와 같이 JpaSpecificationExecutor을 상속하는 것만으로 Specification인자를 파라미터로 갖는 메소드를 갖게된다.

T findOne(Specification<T> spec);
List<T> findAll(Specification<T> spec);
Page<T> findAll(Specification<T> spec, Pageable pageable);
List<T> findAll(Specification<T> spec, Sort sort);
long count(Specification<T> spec);

스펙 정의코드

public class OrderSpec{
    public static Specification<Order> memberName(final String memberName){
        return new Specification<Order>(){
            pulbic Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder builder){
                //Order엔티티에서 시작
                if(StringUtils.isEmpty(memberName))return null;

                Join<Order, Member> m = root.join("member", JoinType.INNER);//Member엔티티 조인
                return builder.equal(m.get("name"), memberName);//입력값 memberName을 갖는 엔티티만 필터링
            }
        };
    }

    public static Specification<Order> isOrderStatus(){
        return new Specification<Order>(){
            public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder builder){

                return builder.equal(root.get("status"), OrderStatus.ORDER);
            }
        }
    }
}

스펙을 사용하는 서비스코드

import static OrderSpec.*;

//OrderService
public List<Order> findOrders(String name){
    Specification<Order> spec = where(memberName(name)).and(isOrderStatus());
    List<Order> result = orderRepository.findAll(spec);
}

5.2. 정렬, 페이징

Criteria 에 의존하기 보다 QueryDSL 로 구현하는게 더 나을듯

5.3. 조회전용 기능 구현