序
本文主要研究一下 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