Spring Data Jpa의 Speicfication을 애용하는 편인데, 이 Specification을 사용해서 조건을 조합하다보면 다음과 같은 코드를 종종 작성하게 된다. (관련 내용은 http://javacan.tistory.com/entry/SpringDataJPA-Specifcation-Usage 참고)
Specifications<Check> specs = Specifications.where(
CheckSpecs.yearQuarter(searchRequest.getYear(), searchRequest.getQuarter()));
if (searchRequest.hasTeamCd())
specs = specs.and(CheckSpecs.teamCd(searchRequest.getTeamCd()));
if (searchRequest.hasPlanDate())
specs = specs.and(CheckSpecs.planDate(searchRequest.getPlanDate()));
List<Check> checks = checkRepository.findAll(specs);
if 절과 각 Spec을 and로 엮는 코드가 실수하기 좋게 되어 있다. 이를 보완하고자 SpecBuilder라는 보조 클래스를 하나 만들었다. 이 클래스를 사용하면 위 코드를 다음과 같이 변경할 수 있다.
Specification<Check> spec = SpecBuilder.builder(Check.class)
.and(CheckSpecs.yearQuarter(searchRequest.getYear(), searchRequest.getQuarter()))
.whenHasText(searchRequest.getTeamCd(), str -> CheckSpec.teamCd(str))
.whenHasText(searchRequest.getPlanDate(), CheckSpec::planDate)
.toSpec();
.List<Check> checks = checkRepository.findAll(specs);
단순히 and로 조합하는 경우, if를 사용할 때보다 코드를 보기가 더 좋아졌다.
SpecBuilder의 완전한 코드는 다음과 같다.
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) {
specs.add(spec);
return this;
}
public Builder<T> whenHasText(String str,
Function<String, Specification<T>> specSupplier) {
if (StringUtils.hasText(str)) {
specs.add(specSupplier.apply(str));
}
return this;
}
public Builder<T> when(String str,
Function<String, Specification<T>> specSupplier) {
specs.add(specSupplier.apply(str));
return this;
}
public Builder<T> whenHasTextThenBetween(String from, String to,
BiFunction<String, String, Specification<T>> specSupplier) {
if (StringUtils.hasText(from) && StringUtils.hasText(to)) {
specs.add(specSupplier.apply(from, to));
}
return this;
}
public Builder<T> whenIsTrue(Boolean cond,
Supplier<Specification<T>> specSupplier) {
if (cond != null && cond.booleanValue()) {
specs.add(specSupplier.get());
}
return this;
}
public Specification<T> toSpec() {
if (specs.isEmpty())
return Specifications.where(null);
else if (specs.size() == 1)
return specs.get(0);
else {
return specs.stream().reduce(
Specifications.where(null),
(specs, spec) -> specs.and(spec),
(specs1, specs2) -> specs1.and(specs2));
}
}
}
}