Spring Data JPA:解析SimpleJpaRepository

時空穿越者發表於2021-08-26

原始碼

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方法

相關文章