原始碼
在前面關於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方法
Specification
從本文前面的描述得知,在呼叫JpaSpecificationExecutor介面提供的幾個方法時需要構造Specification型別的引數。
在前面關於SimpleJpaRepository的文章[地址]中提到了構造Specification型別引數的方式:匿名內部類或者ExampleSpecification的例項,本小節來剖析一下Specification的細節。
類圖
解讀:
從類圖可知,ByIdsSpecification、ExampleSpecification實現了Specification介面
進一步發掘,可以發現ByIdsSpecification、ExampleSpecification都是SimpleJpaRepository的內部類,如下圖所示:
原始碼
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方法即可。
Root、Path
Specification中toPredicate方法的的第一個引數為Root<T> root,前面示例的Service層在實現toPredicate方法時通過呼叫如下語句獲得Path型別的變數
- Path<Integer> path = root.get("id");
下圖展示了Root、Path之間的關係:
解讀:
從上圖可知,Root介面間接繼承了Path介面,前述呼叫語句中的get方法由Path介面定義
相關程式碼如下:
- <Y> Path<Y> get(String attributeName);
參加[官方Doc]