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[地址]