검색을 위한 스펙

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

################################################################################################

public class OrdererSpec implements Specification<Order> {
	private String ordererId;

	public boolean isSatisfiedBy(Order agg) {
		return agg.getOrdererId().getMemberId().getId().equals(ordererId);
	}
}

################################################################################################

Specification<Order> ordererSpec = new OrdererSpec("madvirus");
List<Order> orders = orderRepository.findAll(ordererSpec);

스펙 조합

Specification<Order> ordererSpec = new OrdererSpec("madvirus");
Specification<Order> ordererDateSpec = new OrdererDateSpec(fromDate, toDate);
AndSpec<T> spec = new AndSpec(ordererSpec, orderDateSpec);

################################################################################################

List<Order> orders = orderRepository.findAll(spec);

JPA를 위한 스펙 구현

JPA 스펙 구현

public interface Specification<T> {
	Predicate toPredicate(Root<T> root, CriteriaBuilder db);
}

################################################################################################

public class OrdererSpec implements Specification<Order> {
	private String ordererId;

	public Predicate toPredicate(Root<Order> root, CriteriaBuilder cb) {
		return cb.equal(root.get(Order_.orderer)
											.get(Orderer_.memberId).get(MemberId_.id),
										ordererId);
	}
}

################################################################################################

Specification<Order> ordererSpec = new OrdererSpec("madvirus");
List<Order> orders = orderRepository.findAll(ordererSpec);
public class OrderSpecs {
	public static Specification<Order> orderer(String ordererId) {
		return (root, cb) -> cb.equal(
			root.get(Order_.orderer).get(Orderer_.memberId).get(MemberId_.id),
			ordererId);
		}

	public static Specification<Order> between(Date from, Date to) {
		return (root, cb) -> cb.between(root.get(Order_.orderDate), from, to);
	}
}

AND/OR 스펙 조합을 위한 구현


public class AndSpecification<T> implements Specification<T> {
    private List<Specification<T>> specs;

    public AndSpecification(Specification<T> ... specs) {
        this.specs = Arrays.asList(specs);
    }

    @Override
    public Predicate toPredicate(Root<T> root, CriteriaBuilder cb) {
        Predicate[] predicates = specs.stream()
                .map(spec -> spec.toPredicate(root, cb))
                .toArray(size -> new Predicate[size]);
        return cb.and(predicates);
    }
}
public class OrSpecification<T> implements Specification<T> {
    private List<Specification<T>> specs;

    public OrSpecification(Specification<T>... specs) {
        this.specs = Arrays.asList(specs);
    }

    @Override
    public Predicate toPredicate(Root<T> root, CriteriaBuilder cb) {
        Predicate[] predicates = specs.stream()
                .map(spec -> spec.toPredicate(root, cb))
                .toArray(Predicate[]::new);
        return cb.or(predicates);
    }
}
public class Specs {
    public static <T> Specification<T> and(Specification<T> ... specs) {
        return new AndSpecification<>(specs);
    }

    public static <T> Specification<T> or(Specification<T> ... specs) {
        return new OrSpecification<>(specs);
    }
}
public interface Specification<T> {
    Predicate toPredicate(Root<T> root, CriteriaBuilder cb);
}

스펙을 사용하는 JPA 리포지터리 구현

		public interface OrderRepository {
		    List<Order> findAll(Specification<Order> spec, String ... orders);
		}

		@Override
    public List<Order> findAll(Specification<Order> spec, String ... orders) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Order> criteriaQuery = cb.createQuery(Order.class);
        Root<Order> root = criteriaQuery.from(Order.class);
        Predicate predicate = spec.toPredicate(root, cb);
        criteriaQuery.where(predicate);
        if (orders.length > 0) {
            criteriaQuery.orderBy(JpaQueryUtils.toJpaOrders(root, cb, orders));
        }
        TypedQuery<Order> query = entityManager.createQuery(criteriaQuery);
        return query.getResultList();
    }

정렬 구현