Spring Data JPA:解析SimpleJpaRepository

源码

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

 

扩展阅读

Jakarta Persistence[地址]

posted @ 2021-08-26 17:17  时空穿越者  阅读(2220)  评论(0编辑  收藏  举报