Spring Data JPA:解析JpaSpecificationExecutor & Specification
源码
在前面关于SimpleJpaRepository的文章[地址]中可以得知,SimpleJpaRepository间接实现了JpaSpecificationExecutor接口,本文就详细探究一下该接口。
JpaSpecificationExecutor的定义如下:
/** * Interface to allow execution of {@link Specification}s based on the JPA criteria API. * * @author Oliver Gierke * @author Christoph Strobl */ public interface JpaSpecificationExecutor<T> { /** * Returns a single entity matching the given {@link Specification} or {@link Optional#empty()} if none found. * * @param spec can be {@literal null}. * @return never {@literal null}. * @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one entity found. */ Optional<T> findOne(@Nullable Specification<T> spec); /** * Returns all entities matching the given {@link Specification}. * * @param spec can be {@literal null}. * @return never {@literal null}. */ List<T> findAll(@Nullable Specification<T> spec); /** * Returns a {@link Page} of entities matching the given {@link Specification}. * * @param spec can be {@literal null}. * @param pageable must not be {@literal null}. * @return never {@literal null}. */ Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable); /** * Returns all entities matching the given {@link Specification} and {@link Sort}. * * @param spec can be {@literal null}. * @param sort must not be {@literal null}. * @return never {@literal null}. */ List<T> findAll(@Nullable Specification<T> spec, Sort sort); /** * Returns the number of instances that the given {@link Specification} will return. * * @param spec the {@link Specification} to count instances for. Can be {@literal null}. * @return the number of instances. */ long count(@Nullable Specification<T> spec); }
解读:
上述接口提供了一个findOne方法以及三个接受不同参数的findAll方法,这几个方法接受Specification类型的参数
示例
在实际开发中,通常按如下示例中展示的方式使用JpaSpecificationExecutor接口
Repository层:
@Repository public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> { }
Service层:
public Page<User> getUsers(Integer id, Integer pageNum, Integer pageSize) { Sort sort = Sort.by(Sort.Direction.DESC, "id"); Pageable pageable = PageRequest.of(pageNum, pageSize, sort); Specification<User> specification = (Specification<User>) (root, query, cb) -> { Path<Integer> path = root.get("id"); return cb.lt(path, id); }; return userRepository.findAll(specification, pageable); }
解读:
此处Service调用了userRepository的findAll方法,参数为Specification的实例以及Pageable的实例,该findAll方法实质上是JpaSpecificationExecutor提供的findAll方法:
Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
Specification
从本文前面的描述得知,在调用JpaSpecificationExecutor接口提供的几个方法时需要构造Specification类型的实例作为参数。
由此可知,Specification是JpaSpecificationExecutor接口的核心。
在前面关于SimpleJpaRepository的文章[地址]中提到了构造Specification类型参数的方式:匿名内部类或者ExampleSpecification的实例,本小节来剖析一下Specification的细节。
源码
Specification定义在包路径org.springframework.data.jpa.domain下,其定义如下:
/** * Specification in the sense of Domain Driven Design. * * @author Oliver Gierke * @author Thomas Darimont * @author Krzysztof Rzymkowski * @author Sebastian Staudt * @author Mark Paluch * @author Jens Schauder */ public interface Specification<T> extends Serializable { long serialVersionUID = 1L; /** * Negates the given {@link Specification}. * * @param <T> the type of the {@link Root} the resulting {@literal Specification} operates on. * @param spec can be {@literal null}. * @return guaranteed to be not {@literal null}. * @since 2.0 */ static <T> Specification<T> not(@Nullable Specification<T> spec) { return spec == null // ? (root, query, builder) -> null // : (root, query, builder) -> builder.not(spec.toPredicate(root, query, builder)); } /** * Simple static factory method to add some syntactic sugar around a {@link Specification}. * * @param <T> the type of the {@link Root} the resulting {@literal Specification} operates on. * @param spec can be {@literal null}. * @return guaranteed to be not {@literal null}. * @since 2.0 */ static <T> Specification<T> where(@Nullable Specification<T> spec) { return spec == null ? (root, query, builder) -> null : spec; } /** * ANDs the given {@link Specification} to the current one. * * @param other can be {@literal null}. * @return The conjunction of the specifications * @since 2.0 */ default Specification<T> and(@Nullable Specification<T> other) { return SpecificationComposition.composed(this, other, CriteriaBuilder::and); } /** * ORs the given specification to the current one. * * @param other can be {@literal null}. * @return The disjunction of the specifications * @since 2.0 */ default Specification<T> or(@Nullable Specification<T> other) { return SpecificationComposition.composed(this, other, CriteriaBuilder::or); } /** * Creates a WHERE clause for a query of the referenced entity in form of a {@link Predicate} for the given * {@link Root} and {@link CriteriaQuery}. * * @param root must not be {@literal null}. * @param query must not be {@literal null}. * @param criteriaBuilder must not be {@literal null}. * @return a {@link Predicate}, may be {@literal null}. */ @Nullable Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder); }
解读:
其中只有toPredicate方法抽象方法,所以通过匿名内部类的形式构造Specification的实例时只需实现toPredicate方法即可。
类图
解读:
从类图可知,ByIdsSpecification、ExampleSpecification实现了Specification接口
进一步发掘,可以发现ByIdsSpecification、ExampleSpecification都是SimpleJpaRepository的内部类,如下图所示:
解读:
ByIdsSpecification、ExampleSpecification按照各自的应用场景实现了toPredicate方法方法。
扩展阅读
Use Criteria Queries in a Spring Data Application[地址]
Root、Path
Specification中toPredicate方法的的第一个参数为Root<T> root,前面示例的Service层在实现toPredicate方法时通过调用如下语句获得Path类型的变量
Path<Integer> path = root.get("id");
解读:
此处的Path表示来自绑定类型或集合的简单属性(或复合属性)的路径,即user.id。
Root、Path均定义在包路径javax.persistence.criteria下。
下图展示了Root、Path之间的关系:
解读:
从上图可知,Root接口间接继承了Path接口,前述调用语句中的get方法由Path接口定义
相关代码如下:
<Y> Path<Y> get(String attributeName);
参加[官方Doc]
Note:
如果需要获取类似user.address.city的属性路径,则相关实现代码如下:
Path<Object> address = root.get("address"); Path<Object> city = address.get("city"); Predicate predicate = cb.equal(city, "WH");