原始碼

在前面關於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]