乐趣区

聊聊spring data jpa的SimpleJpaRepository


本文主要研究一下 spring data jpa 的 SimpleJpaRepository
JpaRepositoryImplementation
spring-data-jpa-2.1.6.RELEASE-sources.jar!/org/springframework/data/jpa/repository/support/JpaRepositoryImplementation.java
/**
* SPI interface to be implemented by {@link JpaRepository} implementations.
*
* @author Oliver Gierke
* @author Stefan Fussenegger
*/
@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);
}
JpaRepositoryImplementation 接口继承了 JpaRepository 及 JpaSpecificationExecutor,它是 JpaRepository 接口实现类的 SPI interface;它定义了 setRepositoryMethodMetadata 方法
SimpleJpaRepository
spring-data-jpa-2.1.6.RELEASE-sources.jar!/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {

private static final String ID_MUST_NOT_BE_NULL = “The given id must not be null!”;

private final JpaEntityInformation<T, ?> entityInformation;
private final EntityManager em;
private final PersistenceProvider provider;

private @Nullable CrudMethodMetadata metadata;

/**
* 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);
}

/**
* Configures a custom {@link CrudMethodMetadata} to be used to detect {@link LockModeType}s and query hints to be
* applied to queries.
*
* @param crudMethodMetadata
*/
public void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata) {
this.metadata = crudMethodMetadata;
}

@Nullable
protected CrudMethodMetadata getRepositoryMethodMetadata() {
return metadata;
}

//……

/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable)
*/
@Transactional
public void deleteById(ID id) {

Assert.notNull(id, ID_MUST_NOT_BE_NULL);

delete(findById(id).orElseThrow(() -> new EmptyResultDataAccessException(
String.format(“No %s entity with id %s exists!”, entityInformation.getJavaType(), id), 1)));
}

/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.lang.Object)
*/
@Transactional
public void delete(T entity) {

Assert.notNull(entity, “The entity must not be null!”);
em.remove(em.contains(entity) ? entity : em.merge(entity));
}

/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.lang.Iterable)
*/
@Transactional
public void deleteAll(Iterable<? extends T> entities) {

Assert.notNull(entities, “The given Iterable of entities not be null!”);

for (T entity : entities) {
delete(entity);
}
}

/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.JpaRepository#deleteInBatch(java.lang.Iterable)
*/
@Transactional
public void deleteInBatch(Iterable<T> entities) {

Assert.notNull(entities, “The given Iterable of entities not be null!”);

if (!entities.iterator().hasNext()) {
return;
}

applyAndBind(getQueryString(DELETE_ALL_QUERY_STRING, entityInformation.getEntityName()), entities, em)
.executeUpdate();
}

/*
* (non-Javadoc)
* @see org.springframework.data.repository.Repository#deleteAll()
*/
@Transactional
public void deleteAll() {

for (T element : findAll()) {
delete(element);
}
}

/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.JpaRepository#deleteAllInBatch()
*/
@Transactional
public void deleteAllInBatch() {
em.createQuery(getDeleteAllQueryString()).executeUpdate();
}

//……

/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#save(java.lang.Object)
*/
@Transactional
public <S extends T> S save(S entity) {

if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}

/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.JpaRepository#saveAndFlush(java.lang.Object)
*/
@Transactional
public <S extends T> S saveAndFlush(S entity) {

S result = save(entity);
flush();

return result;
}

/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.JpaRepository#save(java.lang.Iterable)
*/
@Transactional
public <S extends T> List<S> saveAll(Iterable<S> entities) {

Assert.notNull(entities, “The given Iterable of entities not be null!”);

List<S> result = new ArrayList<S>();

for (S entity : entities) {
result.add(save(entity));
}

return result;
}

/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.JpaRepository#flush()
*/
@Transactional
public void flush() {
em.flush();
}

//……

/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findById(java.io.Serializable)
*/
public Optional<T> findById(ID id) {

Assert.notNull(id, ID_MUST_NOT_BE_NULL);

Class<T> domainType = getDomainClass();

if (metadata == null) {
return Optional.ofNullable(em.find(domainType, id));
}

LockModeType type = metadata.getLockModeType();

Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap();

return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
}

/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.JpaRepository#getOne(java.io.Serializable)
*/
@Override
public T getOne(ID id) {

Assert.notNull(id, ID_MUST_NOT_BE_NULL);
return em.getReference(getDomainClass(), id);
}

/**
* 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;
}
//……
}

SimpleJpaRepository 实现了 JpaRepositoryImplementation 接口,它是 CrudRepository 的默认实现;它的构造器都要求传入 EntityManager
它的类上注解了 @Transactional(readOnly = true);而对 deleteById、delete、deleteAll、deleteInBatch、deleteAllInBatch、save、saveAndFlush、saveAll、flush 都添加了 @Transactional 注解
从各个方法的实现可以看到 SimpleJpaRepository 是使用 EntityManager 来完成具体的方法功能,对于查询功能很多都借助了 applySpecificationToCriteria 方法,将 spring data 的 Specification 转换为 javax.persistence 的 CriteriaQuery

JpaRepositoryFactory
spring-data-jpa-2.1.6.RELEASE-sources.jar!/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java
public class JpaRepositoryFactory extends RepositoryFactorySupport {

private final EntityManager entityManager;
private final QueryExtractor extractor;
private final CrudMethodMetadataPostProcessor crudMethodMetadataPostProcessor;

private EntityPathResolver entityPathResolver;
private EscapeCharacter escapeCharacter = EscapeCharacter.of(‘\\’);

/**
* Creates a new {@link JpaRepositoryFactory}.
*
* @param entityManager must not be {@literal null}
*/
public JpaRepositoryFactory(EntityManager entityManager) {

Assert.notNull(entityManager, “EntityManager must not be null!”);

this.entityManager = entityManager;
this.extractor = PersistenceProvider.fromEntityManager(entityManager);
this.crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor();
this.entityPathResolver = SimpleEntityPathResolver.INSTANCE;

addRepositoryProxyPostProcessor(crudMethodMetadataPostProcessor);

if (extractor.equals(PersistenceProvider.ECLIPSELINK)) {
addQueryCreationListener(new EclipseLinkProjectionQueryCreationListener(entityManager));
}
}

//……

/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getTargetRepository(org.springframework.data.repository.core.RepositoryMetadata)
*/
@Override
protected final JpaRepositoryImplementation<?, ?> getTargetRepository(RepositoryInformation information) {

JpaRepositoryImplementation<?, ?> repository = getTargetRepository(information, entityManager);
repository.setRepositoryMethodMetadata(crudMethodMetadataPostProcessor.getCrudMethodMetadata());

return repository;
}

/**
* Callback to create a {@link JpaRepository} instance with the given {@link EntityManager}
*
* @param information will never be {@literal null}.
* @param entityManager will never be {@literal null}.
* @return
*/
protected JpaRepositoryImplementation<?, ?> getTargetRepository(RepositoryInformation information,
EntityManager entityManager) {

JpaEntityInformation<?, Serializable> entityInformation = getEntityInformation(information.getDomainType());
Object repository = getTargetRepositoryViaReflection(information, entityInformation, entityManager);

Assert.isInstanceOf(JpaRepositoryImplementation.class, repository);

return (JpaRepositoryImplementation<?, ?>) repository;
}

//……
}
JpaRepositoryFactory 的 getTargetRepository 会根据 RepositoryInformation 创建 JpaRepositoryImplementation,这里默认创建的是 SimpleJpaRepository 实例
RepositoryFactorySupport
spring-data-commons-2.1.6.RELEASE-sources.jar!/org/springframework/data/repository/core/support/RepositoryFactorySupport.java
public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, BeanFactoryAware {

//……

/**
* Returns a repository instance for the given interface backed by an instance providing implementation logic for
* custom logic.
*
* @param repositoryInterface must not be {@literal null}.
* @param fragments must not be {@literal null}.
* @return
* @since 2.0
*/
@SuppressWarnings({“unchecked”})
public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fragments) {

if (LOG.isDebugEnabled()) {
LOG.debug(“Initializing repository instance for {}…”, repositoryInterface.getName());
}

Assert.notNull(repositoryInterface, “Repository interface must not be null!”);
Assert.notNull(fragments, “RepositoryFragments must not be null!”);

RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface);
RepositoryComposition composition = getRepositoryComposition(metadata, fragments);
RepositoryInformation information = getRepositoryInformation(metadata, composition);

validate(information, composition);

Object target = getTargetRepository(information);

// Create proxy
ProxyFactory result = new ProxyFactory();
result.setTarget(target);
result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class);

if (MethodInvocationValidator.supports(repositoryInterface)) {
result.addAdvice(new MethodInvocationValidator());
}

result.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE);
result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);

postProcessors.forEach(processor -> processor.postProcess(result, information));

result.addAdvice(new DefaultMethodInvokingMethodInterceptor());

ProjectionFactory projectionFactory = getProjectionFactory(classLoader, beanFactory);
result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory));

composition = composition.append(RepositoryFragment.implemented(target));
result.addAdvice(new ImplementationMethodExecutionInterceptor(composition));

T repository = (T) result.getProxy(classLoader);

if (LOG.isDebugEnabled()) {
LOG.debug(“Finished creation of repository instance for {}.”, repositoryInterface.getName());
}

return repository;
}

//……
}
RepositoryFactorySupport 的 getRepository 方法在调用子类的 getTargetRepository 创建 SimpleJpaRepository 实例之后,会对其进行 proxy,设置其接口为用户定义的 dao 接口、Repository、TransactionalProxy,并添加了 SurroundingTransactionDetectorMethodInterceptor、DefaultMethodInvokingMethodInterceptor、QueryExecutorMethodInterceptor、ImplementationMethodExecutionInterceptor 等 MethodInterceptor,最后生成最终的实现类
小结

JpaRepositoryImplementation 接口继承了 JpaRepository 及 JpaSpecificationExecutor,它是 JpaRepository 接口实现类的 SPI interface;它定义了 setRepositoryMethodMetadata 方法
SimpleJpaRepository 实现了 JpaRepositoryImplementation 接口,它是 CrudRepository 的默认实现;它的构造器都要求传入 EntityManager;从各个方法的实现可以看到 SimpleJpaRepository 是使用 EntityManager 来完成具体的方法功能,对于查询功能很多都借助了 applySpecificationToCriteria 方法,将 spring data 的 Specification 转换为 javax.persistence 的 CriteriaQuery
JpaRepositoryFactory 的 getTargetRepository 会根据 RepositoryInformation 创建 JpaRepositoryImplementation,这里默认创建的是 SimpleJpaRepository 实例;RepositoryFactorySupport 的 getRepository 方法在调用子类的 getTargetRepository 创建 SimpleJpaRepository 实例之后,会对其进行 proxy,设置其接口为用户定义的 dao 接口、Repository、TransactionalProxy,并添加了 SurroundingTransactionDetectorMethodInterceptor、DefaultMethodInvokingMethodInterceptor、QueryExecutorMethodInterceptor、ImplementationMethodExecutionInterceptor 等 MethodInterceptor,最后生成最终的实现类

doc

SimpleJpaRepository
SimpleJpaRepository.java
Customizing Spring Data JPA Repository
Spring Data JPA – Adding a Method in All Repositories
Spring Data JPA Tutorial: Adding Custom Methods to All Repositories

退出移动版