JPA에서 동적 쿼리를 위한 스펙 구현

public interface Specification<T> {
	Predicagte toPredicate(Root<T> root, CriteriaBuilder cb);
}
public class OrdererSpec implements Specification<Order> {
	
	private String ordererId;

	public OrdererSpec(String ordererId) {
		this.ordererId = ordererId;
	}

	@Overridee
	public Predicate toPredicate(Root<Order> root, CriteriaBuilder cb) {
		return cb.equeal(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 ordereId) {
		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);
	}

}
Specification<Order> betweenSpec = OrderSpecs.between(formTime, toTime);

AND와 OR를 위한 JPA 스펙

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, db))
			.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, db))
			.toArray(Predicate[]::new);
		return cb.or(predicates);
	}

}

AND/OR 스펙을 생성해주는 팩토리 클래스

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 enw OrSpecification<>(specs);
	}

}
Specification<Order> specs = Specs.and(
	OrderSpecs.orderer("madvirus"), OrderSpecs.between(fromTime, toTime)
);

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