TIL

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

5.1 시작에 앞서

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

public interface Specification<T> {
  public boolean isSatisfiedBy(T agg);
}

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

public interface Specification<T> extends Serializable {
  @Nullable
  Predicate toPredicate(Root<T> root, 
                       CriteriaQuery<?> query,
                       CriteriaBuilder cb);
}
public class OrderIdSpec implements Specification<OrderSummary> {

    private String orderId;
    
    public OrderIdSpec(String orderId) {
      this.orderId = orderId
    }
    
    @Override
    public Predicate toPredicate(Root<OrderSummary> root,
                                CriteriaQuery<?> query,
                                CriteriaBuilder cb) {
        return cb.equal(root.get(OrderSummary.orderId), orderId);
    }                                
}

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

public interface OrderSummaryDao extends Repository<OrderSummary, String> {
  List<OrderSummary> findAll(Specification<OrderSummary> spec);
}

5.5 스펙 조합

public interface Specification<T> extends Serializable {

  default Specification<T> and(@Nullable Specification<T> other) { ... }
  default Specification<T> or(@Nullable Specification<T> other) { ... }

  @Nullable
  Predicate toPredicate(Root<T> root, 
                       CriteriaQuery<?> query,
                       CriteriaBuilder cb);
}

5.6 정렬 지정하기

Sort sort = Sort.by("number").ascending()
List<OrderSummary> results = orderSummaryDao.findByOrderId(String orderId, Sort sort)

5.7 페이징 처리하기

Sort sort = Sort.by("name").descending()
PageRequest req = PageRequest.of(1, 10, sort);
List<MemberData> user = memberDataDao.findByNameLike("...%", req)

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

Specification<MemberData> spec = SpecBuilder.builder(MemberData.class)
    .ifTrue(searchRequest.isOnlyNotBlocked(), 
        () -> MemberDataSpecs.nonBlocked())
    .ifHasText(searchRequest.getName(), 
        name -> MemberDataSpecs.nameLike(searchRequest.getName()))
    .toSpec()
List<MemberData> result = memberDataDao.findAll(spec, PageRequest.of(0, 5));
public class SpecBuilder {
    public static <T> Builder<T> builder(Class<T> type) {
        return new Builder<T>();
    }

    public static class Builder<T> {
        private List<Specification<T>> specs = new ArrayList<>();

        public Builder<T> and(Specification<T> spec) {
            addSpec(spec);
            return this;
        }

        public Builder<T> ifHasText(String str,
                                    Function<String, Specification<T>> specSupplier) {
            if (StringUtils.hasText(str)) {
                addSpec(specSupplier.apply(str));
            }
            return this;
        }

        public Builder<T> ifTrue(Boolean cond,
                                 Supplier<Specification<T>> specSupplier) {
            if (cond != null && cond.booleanValue()) {
                addSpec(specSupplier.get());
            }
            return this;
        }

        public Specification<T> toSpec() {
            Specification<T> spec = Specification.where(null);
            for (Specification<T> s : specs) {
                spec = spec.and(s);
            }
            return spec;
        }
    }
}

5.9 동적 인스턴스 생성

@Query("""
        select new com.myshop.order.query.dto.OrderView(...)
        ...
    """)
List<OrderView> findOrderView(String orderId);

5.10 하이버네이트 @Subselect 사용

@Entity
@Immutable
@Subselect(
  """
  select o.order_number as number,
         o.version,
         o.orderer_id,
         o.order_name,
         o.total_amounts,
         o.receiver_name,
         o.state,
         o.order_date,
         p.pruduct_id,
         p.name as product_name
  from purchase_order o inner join order_line ol
    on o.order_number = ol.ourder_number
    cross join product p
  where ol.line_idx = 0
    and ol.product_id = p.product_id   
"""
)
@Synchronize({"purchase_order", "order_line", "product"})
public class OrderSummary {
  @Id
  private String number;
  private long version;
  @Column(name = "orderer_id")
  private String ordererId;
  @Column(name = "orderer_name")
  private String ordererName;
  //...
  
  protected OrderSummary() {
  }
}
Order orer = findOrder(orderNumber);
order.changeShippingInfo(newInfo);

// 위 order의 변경 사항이 flush 된 후의 상태를 가져올 수 있음
List<OrderSummary> summaries = orderSummarRepository.findByrdererId(userId);