假设我已经用这种方式编写了一个可重用JPA规范的小型库,作为更多的语法糖
public static Specification<Person> lastNameEquas(String lastName) {
return (r,q,cb)->cb.equals(r.get(Person_.lastName),lastName);
}
想象更多的谓词,我使用它们,例如:
Specification<Person> s = where(firstNameEquals("John"))
.and(lastNameEquals("Smith"))
.and(ageGreaterThan(18));
在T
是U
的联接实体的情况下,我也面临着为T
实体重用定义的Specification<T>
的问题
假设类Person
是@OneToMany
-连接到Pet
,并且我有Person
和Pet
的规范,我希望在规范的相同构造中重用Person
和Pet
的助手方法
@Entity
public class Person{
......
}
@Entity
public class Pet{
private String name;
private int age;
@ManyToOne
Person owner
}
我想要一个可以与可重复使用的Specification<Person>
实例组合的Specification<Pet>
Specification<Pet> spec = where(PetSpecs.petNameEqual("Lucky"))
.and(PetSpecs.petAgeGreaterThan(1))
.and(PetSpecs.owner(
personNameEquals("John")
.and(personAgeGreaterThan(18))
))
select from Pet p where
p.name = 'Lucky' and p.age > 1 and p.owner.name = 'John' and p.owner.age > 18;
到目前为止我尝试了什么
我想写一个方法public static Specification<Pet> owner(Specification<Person>)
,它接受任何Specification-of-Person
的输入,并将其应用于联接的属性,从而生成一个可以提供查询的Specification-of-Pet
更一般地说,我可以尝试写
public static <T, U> Specification<T> joinedSpecification(@NonNull SingularAttribute<T, U> joinAttribute, JoinType joinType, Specification<U> joinSpecification) {
if (joinSpecification == null) return (root, query, criteriaBuilder) -> null;
return (root, query, criteriaBuilder) -> {
Join<T, U> join = root.join(joinAttribute, joinType);
return joinSpecification.toPredicate(join, query, criteriaBuilder);
};
}
其思想是,Specification是一个返回谓词的函数,因此我的谓词将递归地将输入规范转换为更多的谓词,应用于连接的实体。
现在是我的问题。JPA将Specification<T>
定义为lambda接口
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
注意,Root<X>
扩展From<X,X>
扩展Path<X>
,Join<T,U>
扩展Path<T,U>
。
上面的代码不编译,因为root.join(...)
返回一个Join
,是Path
(获取实体属性),但不是Root
。
我的问题是:在JPA中,是否有可能重用规范来在联接路径中重新应用?
定义自己的接口似乎没什么大不了的:
@FunctionalInterface
public interface Specification<X> extends Serializable {
Predicate toPredicate(From<?, ? extends X> from, CriteriaQuery<?> cq, CriteriaBuilder cb);
default Specification<X> not() {
return (from, cq, cb) -> cb.not(toPredicate(from, cq, cb));
}
default Specification<X> and(@Nullable Specification<? super X> other) {
if (other == null) {
return this;
}
return (path, cq, cb) -> cb.and(toPredicate(path, cq, cb), other.toPredicate(path, cq, cb));
}
default Specification<X> or(@Nullable Specification<? super X> other) {
if (other == null) {
return this;
}
return (path, cq, cb) -> cb.or(toPredicate(path, cq, cb), other.toPredicate(path, cq, cb));
}
default org.springframework.data.jpa.domain.Specification<X> asSpring() {
return this::toPredicate;
}
}
然而,我们发现重用标准谓词比spring规范更方便(至少大多数开发人员更喜欢这个选项),smth。类似:
@Entity
public class Pet {
private String name;
private int age;
public static Predicate hasName(From<?, ? extends Pet> from, CriteriaBuilder cb, String name) {
return cb.equal(from.get(Pet_.name), cb.literal(name));
}
@ManyToOne
Person owner;
}