原始碼
SimpleJpaRepository的定義如下:
/** * Default implementation of the {@link org.springframework.data.repository.CrudRepository} interface. This will offer * you a more sophisticated interface than the plain {@link EntityManager} . * * @author Oliver Gierke * @author Eberhard Wolff * @author Thomas Darimont * @author Mark Paluch * @author Christoph Strobl * @author Stefan Fussenegger * @author Jens Schauder * @author David Madden * @author Moritz Becker * @param <T> the type of the entity to handle * @param <ID> the type of the entity's identifier */ @Repository @Transactional(readOnly = true) public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
解讀:
SimpleJpaRepository實現了JpaRepositoryImplementation介面。
JpaRepositoryImplementation的定義如下:
/** * SPI interface to be implemented by {@link JpaRepository} implementations. * * @author Oliver Gierke * @author Stefan Fussenegger * @author Jens Schauder */ @NoRepositoryBean public interface JpaRepositoryImplementation<T, ID> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> { /** * Configures the {@link CrudMethodMetadata} to be used with the repository. * * @param crudMethodMetadata must not be {@literal null}. */ void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata); /** * Configures the {@link EscapeCharacter} to be used with the repository. * * @param escapeCharacter Must not be {@literal null}. */ default void setEscapeCharacter(EscapeCharacter escapeCharacter) { } }
解讀:
JpaRepositoryImplementation介面繼承了JpaSpecificationExecutor。
類圖
呼叫鏈路
觀察SimpleJpaRepository中findOne(Example<S> example)方法的實現,程式碼如下:
/* * (non-Javadoc) * @see org.springframework.data.repository.query.QueryByExampleExecutor#findOne(org.springframework.data.domain.Example) */ @Override public <S extends T> Optional<S> findOne(Example<S> example) { try { return Optional .of(getQuery(new ExampleSpecification<S>(example, escapeCharacter), example.getProbeType(), Sort.unsorted()) .getSingleResult()); } catch (NoResultException e) { return Optional.empty(); } }
解讀:
此方法由QueryByExampleExecutor介面定義。
觀察SimpleJpaRepository中findOne(@Nullable Specification<T> spec)方法的實現,程式碼如下:
/* * (non-Javadoc) * @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#findOne(org.springframework.data.jpa.domain.Specification) */ @Override public Optional<T> findOne(@Nullable Specification<T> spec) { try { return Optional.of(getQuery(spec, Sort.unsorted()).getSingleResult()); } catch (NoResultException e) { return Optional.empty(); } }
解讀:
此方法由JpaSpecificationExecutor介面定義。
小結:
上述兩個findOne方法最終都是呼叫瞭如下getQuery方法:
/** * Creates a {@link TypedQuery} for the given {@link Specification} and {@link Sort}. * * @param spec can be {@literal null}. * @param domainClass must not be {@literal null}. * @param sort must not be {@literal null}. * @return */ protected <S extends T> TypedQuery<S> getQuery(@Nullable Specification<S> spec, Class<S> domainClass, Sort sort) { CriteriaBuilder builder = em.getCriteriaBuilder(); CriteriaQuery<S> query = builder.createQuery(domainClass); Root<S> root = applySpecificationToCriteria(spec, domainClass, query); query.select(root); if (sort.isSorted()) { query.orderBy(toOrders(sort, root, builder)); } return applyRepositoryMethodMetadata(em.createQuery(query)); }
解讀:
getQuery方法呼叫了applySpecificationToCriteria方法,該方法的實現如下:
/** * Applies the given {@link Specification} to the given {@link CriteriaQuery}. * * @param spec can be {@literal null}. * @param domainClass must not be {@literal null}. * @param query must not be {@literal null}. * @return */ private <S, U extends T> Root<U> applySpecificationToCriteria(@Nullable Specification<U> spec, Class<U> domainClass, CriteriaQuery<S> query) { Assert.notNull(domainClass, "Domain class must not be null!"); Assert.notNull(query, "CriteriaQuery must not be null!"); Root<U> root = query.from(domainClass); if (spec == null) { return root; } CriteriaBuilder builder = em.getCriteriaBuilder(); Predicate predicate = spec.toPredicate(root, query, builder); if (predicate != null) { query.where(predicate); } return root; }
解讀:
applySpecificationToCriteria方法呼叫了Specification的toPredicate方法,該方法是一個抽象方法,其定義如下:
/** * 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);
解讀:
縱觀前述呼叫過程可知,此處Specification的toPredicate方法由findOne方法傳遞給getQuery方法的引數對應的類實現。
從findOne(Example<S> example)的實現可知,toPredicate方法由引數new ExampleSpecification<S>(example, escapeCharacter)對應ExampleSpecification類實現。
ExampleSpecification
由前面的類圖可知,ExampleSpecification是SimpleJpaRepository的內部類,其定義如下:
/** * {@link Specification} that gives access to the {@link Predicate} instance representing the values contained in the * {@link Example}. * * @author Christoph Strobl * @since 1.10 * @param <T> */ private static class ExampleSpecification<T> implements Specification<T> { private static final long serialVersionUID = 1L; private final Example<T> example; private final EscapeCharacter escapeCharacter; /** * Creates new {@link ExampleSpecification}. * * @param example * @param escapeCharacter */ ExampleSpecification(Example<T> example, EscapeCharacter escapeCharacter) { Assert.notNull(example, "Example must not be null!"); Assert.notNull(escapeCharacter, "EscapeCharacter must not be null!"); this.example = example; this.escapeCharacter = escapeCharacter; } /* * (non-Javadoc) * @see org.springframework.data.jpa.domain.Specification#toPredicate(javax.persistence.criteria.Root, javax.persistence.criteria.CriteriaQuery, javax.persistence.criteria.CriteriaBuilder) */ @Override public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) { return QueryByExamplePredicateBuilder.getPredicate(root, cb, example, escapeCharacter); } }
解讀:
ExampleSpecification實現了Specification介面中的toPredicate方法。
小結:
如果需要呼叫SimpleJpaRepository的findOne方法,需要構造Specification或者Example對應的例項。
示例:
以匿名內部類的形式構造Specification對應的例項
Specification<User> specification = new Specification<>() { @Override public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { Path<Integer> path = root.get("id"); return cb.lt(path, id); } };
從前面的描述可知,getQuery方法是findOne方法的核心,而getQuery中query的構造主要由成員變數em來完成,所以此處觀察成員變數是在何處賦值的。
建構函式
對應的定義如下:
/** * Creates a new {@link SimpleJpaRepository} to manage objects of the given {@link JpaEntityInformation}. * * @param entityInformation must not be {@literal null}. * @param entityManager must not be {@literal null}. */ public SimpleJpaRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) { Assert.notNull(entityInformation, "JpaEntityInformation must not be null!"); Assert.notNull(entityManager, "EntityManager must not be null!"); this.entityInformation = entityInformation; this.em = entityManager; this.provider = PersistenceProvider.fromEntityManager(entityManager); } /** * Creates a new {@link SimpleJpaRepository} to manage objects of the given domain type. * * @param domainClass must not be {@literal null}. * @param em must not be {@literal null}. */ public SimpleJpaRepository(Class<T> domainClass, EntityManager em) { this(JpaEntityInformationSupport.getEntityInformation(domainClass, em), em); }
解讀:
SimpleJpaRepository中成員變數是在構造SimpleJpaRepository物件時傳遞進來的。
Note:
有如下一些方法呼叫了getQuery方法