find
메서드가 너무 많이 증가하게 될 것이다.public interface Specification<T> {
public boolean isSatisfiedBy(T agg);
}
isSatisfiedBy()
의 agg
는 검사 대상 객체다.
Specification
인터페이스를 제공한다.public interface Specification<T> extends Serializable {
@Nullable
Predicate toPredicate(Root<T> root,
CriteriaQuery<?> query,
CriteriaBuilder cb);
}
toPredicate
메서드는 JPA 크리테리아 API에서 조건을 표현하는 Predicate
를 생성한다.
orderId
가 동일한지 비교하는 Predicate
를 생성한다.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);
}
}
public interface OrderSummaryDao extends Repository<OrderSummary, String> {
List<OrderSummary> findAll(Specification<OrderSummary> spec);
}
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);
}
spec1.and(spec2)
와 같이 작성하면 두 스펙 모두 충족하는 표현을 생성한다.not()
메서드도 존재한다.where()
메서드를 사용하면 null
사용 시 아무 조건도 생성하지 않는 스펙을 생성한다.findBy…OrderByNumberDesc()
Sort
객체를 사용할 수 있다.Sort sort = Sort.by("number").ascending()
List<OrderSummary> results = orderSummaryDao.findByOrderId(String orderId, Sort sort)
Sort
도 and
나 or
로 조건을 연결할 수 있다.
sort1.and(sort2)
Pageable
인터페이스로 페이징 처리를 할 수 있다.
PageRequest
클래스로 Pageable
타입을 생성할 수 있다.PageRequest
와 Sort
를 함께 사용할 수도 있다.Sort sort = Sort.by("name").descending()
PageRequest req = PageRequest.of(1, 10, sort);
List<MemberData> user = memberDataDao.findByNameLike("...%", req)
if
와 각 스펙을 조합해서 여러 스펙을 조합할 수도 있다.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;
}
}
}
new
키워드로 생성한다.@Query("""
select new com.myshop.order.query.dto.OrderView(...)
...
""")
List<OrderView> findOrderView(String orderId);
@Subselect
는 쿼리 결과를 @Entity
로 매핑할 수 있는 유용한 기능이다.@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() {
}
}
@Subselect
- 조회 쿼리를 값으로 갖는다.
@Immutable
@Subselect
로 매핑된 엔티티는 실제 테이블이 아니기 때문에 수정되면 변경 감지가 동작하여 에러가 발생할 것이다.@Immutable
애너테이션으로 변경을 무시하도록 한다.@Synchronize
Order orer = findOrder(orderNumber);
order.changeShippingInfo(newInfo);
// 위 order의 변경 사항이 flush 된 후의 상태를 가져올 수 있음
List<OrderSummary> summaries = orderSummarRepository.findByrdererId(userId);
@Subselect
는 일반 엔티티와 같기 때문에 EntityManager
, JPQL
, Criteria
를 사용해서 평범하게 조회 가능하다.
from
절의 서브 쿼리로 사용되어 조회된다.