原始碼

在前面關於SimpleJpaRepository的文章[地址]中可以得知,SimpleJpaRepository間接實現了JpaSpecificationExecutor介面,本文就詳細探究一下該介面。

JpaSpecificationExecutor的定義如下:

  1. /**
  2. * Interface to allow execution of {@link Specification}s based on the JPA criteria API.
  3. *
  4. * @author Oliver Gierke
  5. * @author Christoph Strobl
  6. */
  7. public interface JpaSpecificationExecutor<T> {
  8.  
  9. /**
  10. * Returns a single entity matching the given {@link Specification} or {@link Optional#empty()} if none found.
  11. *
  12. * @param spec can be {@literal null}.
  13. * @return never {@literal null}.
  14. * @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one entity found.
  15. */
  16. Optional<T> findOne(@Nullable Specification<T> spec);
  17.  
  18. /**
  19. * Returns all entities matching the given {@link Specification}.
  20. *
  21. * @param spec can be {@literal null}.
  22. * @return never {@literal null}.
  23. */
  24. List<T> findAll(@Nullable Specification<T> spec);
  25.  
  26. /**
  27. * Returns a {@link Page} of entities matching the given {@link Specification}.
  28. *
  29. * @param spec can be {@literal null}.
  30. * @param pageable must not be {@literal null}.
  31. * @return never {@literal null}.
  32. */
  33. Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
  34.  
  35. /**
  36. * Returns all entities matching the given {@link Specification} and {@link Sort}.
  37. *
  38. * @param spec can be {@literal null}.
  39. * @param sort must not be {@literal null}.
  40. * @return never {@literal null}.
  41. */
  42. List<T> findAll(@Nullable Specification<T> spec, Sort sort);
  43.  
  44. /**
  45. * Returns the number of instances that the given {@link Specification} will return.
  46. *
  47. * @param spec the {@link Specification} to count instances for. Can be {@literal null}.
  48. * @return the number of instances.
  49. */
  50. long count(@Nullable Specification<T> spec);
  51. }

解讀:

上述介面提供了一個findOne方法以及三個接受不同引數的findAll方法,這幾個方法接受Specification型別的引數

示例

在實際開發中,通常按如下示例中展示的方式使用JpaSpecificationExecutor介面

Repository層:

  1. @Repository
  2. public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {
  3.  
  4. }

Service層:

  1. public Page<User> getUsers(Integer id, Integer pageNum, Integer pageSize) {
  2. Sort sort = Sort.by(Sort.Direction.DESC, "id");
  3. Pageable pageable = PageRequest.of(pageNum, pageSize, sort);
  4.  
  5. Specification<User> specification = (Specification<User>) (root, query, cb) -> {
  6. Path<Integer> path = root.get("id");
  7. return cb.lt(path, id);
  8. };
  9. return userRepository.findAll(specification, pageable);
  10. }

解讀:

此處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下,其定義如下:

  1. /**
  2. * Specification in the sense of Domain Driven Design.
  3. *
  4. * @author Oliver Gierke
  5. * @author Thomas Darimont
  6. * @author Krzysztof Rzymkowski
  7. * @author Sebastian Staudt
  8. * @author Mark Paluch
  9. * @author Jens Schauder
  10. */
  11. public interface Specification<T> extends Serializable {
  12.  
  13. long serialVersionUID = 1L;
  14.  
  15. /**
  16. * Negates the given {@link Specification}.
  17. *
  18. * @param <T> the type of the {@link Root} the resulting {@literal Specification} operates on.
  19. * @param spec can be {@literal null}.
  20. * @return guaranteed to be not {@literal null}.
  21. * @since 2.0
  22. */
  23. static <T> Specification<T> not(@Nullable Specification<T> spec) {
  24.  
  25. return spec == null //
  26. ? (root, query, builder) -> null //
  27. : (root, query, builder) -> builder.not(spec.toPredicate(root, query, builder));
  28. }
  29.  
  30. /**
  31. * Simple static factory method to add some syntactic sugar around a {@link Specification}.
  32. *
  33. * @param <T> the type of the {@link Root} the resulting {@literal Specification} operates on.
  34. * @param spec can be {@literal null}.
  35. * @return guaranteed to be not {@literal null}.
  36. * @since 2.0
  37. */
  38. static <T> Specification<T> where(@Nullable Specification<T> spec) {
  39. return spec == null ? (root, query, builder) -> null : spec;
  40. }
  41.  
  42. /**
  43. * ANDs the given {@link Specification} to the current one.
  44. *
  45. * @param other can be {@literal null}.
  46. * @return The conjunction of the specifications
  47. * @since 2.0
  48. */
  49. default Specification<T> and(@Nullable Specification<T> other) {
  50. return SpecificationComposition.composed(this, other, CriteriaBuilder::and);
  51. }
  52.  
  53. /**
  54. * ORs the given specification to the current one.
  55. *
  56. * @param other can be {@literal null}.
  57. * @return The disjunction of the specifications
  58. * @since 2.0
  59. */
  60. default Specification<T> or(@Nullable Specification<T> other) {
  61. return SpecificationComposition.composed(this, other, CriteriaBuilder::or);
  62. }
  63.  
  64. /**
  65. * Creates a WHERE clause for a query of the referenced entity in form of a {@link Predicate} for the given
  66. * {@link Root} and {@link CriteriaQuery}.
  67. *
  68. * @param root must not be {@literal null}.
  69. * @param query must not be {@literal null}.
  70. * @param criteriaBuilder must not be {@literal null}.
  71. * @return a {@link Predicate}, may be {@literal null}.
  72. */
  73. @Nullable
  74. Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
  75. }

解讀:

其中只有toPredicate方法抽象方法,所以通過匿名內部類的形式構造Specification的例項時只需實現toPredicate方法即可。

Root、Path

Specification中toPredicate方法的的第一個引數為Root<T> root,前面示例的Service層在實現toPredicate方法時通過呼叫如下語句獲得Path型別的變數

  1. Path<Integer> path = root.get("id");

下圖展示了Root、Path之間的關係:

解讀:

從上圖可知,Root介面間接繼承了Path介面,前述呼叫語句中的get方法由Path介面定義

相關程式碼如下:

  1. <Y> Path<Y> get(String attributeName);

參加[官方Doc]