Spring JPA 事务实现笔记

Spring 的事务实现主要有2种方式(1) 编程方式 也就是TransactionTemplate方式;(2)使用声明式的方法,也就是加@Transactional标注的方法。本篇主要记录声明式@Transactional注解在JPA下面的大概实现方式。

启动AOP事物需要添加以下标注 :

@EnableTransactionManagement

实现的核心为代理模式及TransactionInterceptor,以下是来自官网的一张图

https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html

事物拦截.jpg

JPA是Java定义的ORM框架访问Database的规范,其中EntityManager定义了一系列与Database交互的方法 包括如何获取数据 保持数据 事物交互等。

如何定义EntityManager和Transaction之间的关系?

这由应用开发者来选择,但是JPA Entity Manager最常用的方式是“Entity Manager per application transaction”(每个事务都有自己的实体管理器)模式。

Spring的Transactional注解会被TransactionInterceptor实现,该切面方法主要实现了

(1) 如何创建Transaction/EntityManager(默认绑定Hibernate SessionImpl),Transaction的提交等

(2) 如何绑定EntityManager?

下面是TransactionInterceptor核心代码,第25行决定了时候应该创建一个新的事务,主要逻辑由事务管理器完成(PlatformTransactionManager.getTransaction), 在Jpa的环境下,Spring的事务管理器为JpaTransactionManger。

    @Override
    @Nullable
    public Object invoke(final MethodInvocation invocation) throws Throwable {
        // Work out the target class: may be {@code null}.
        // The TransactionAttributeSource should be passed the target class
        // as well as the method, which may be from an interface.
        Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

        // Adapt to TransactionAspectSupport's invokeWithinTransaction...
        return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
    }
    
    @Nullable
    protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
            final InvocationCallback invocation) throws Throwable {

        // If the transaction attribute is null, the method is non-transactional.
        TransactionAttributeSource tas = getTransactionAttributeSource();
        final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
        final PlatformTransactionManager tm = determineTransactionManager(txAttr);
        final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

        if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
            // Standard transaction demarcation with getTransaction and commit/rollback calls.
            TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
            Object retVal = null;
            try {
                // This is an around advice: Invoke the next interceptor in the chain.
                // This will normally result in a target object being invoked.
                retVal = invocation.proceedWithInvocation();
            }
            catch (Throwable ex) {
                // target invocation exception
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            }
            finally {
                cleanupTransactionInfo(txInfo);
            }
            commitTransactionAfterReturning(txInfo);
            return retVal;
        }

        else {
          ...
        }
    }
    

那么在TransactionInterceptor中生成的transaction和EntityManager是如何被目标代码也就是我们写的数据库交互部分代码所获取并应用的呢?

答案就是TransactionSynchronizationManager和代理。 TransactionSynchronizationManager核心功能就是利用ThreadLocal绑定EntityManager和其他信息到当前线程,要用的时候直接从中获取,从而达到EntityManager的注入和管理。doGetTransaction中我们直接从当前线程中获取了存有EntityManger的EntityMangerHolder, 如果不是第一个事务的话,拿到的是上一个事务的值。

@Override
protected Object doGetTransaction() {
   JpaTransactionObject txObject = new JpaTransactionObject();
   txObject.setSavepointAllowed(isNestedTransactionAllowed());

   EntityManagerHolder emHolder = (EntityManagerHolder)
         TransactionSynchronizationManager.getResource(obtainEntityManagerFactory());
   if (emHolder != null) {
      if (logger.isDebugEnabled()) {
         logger.debug("Found thread-bound EntityManager [" + emHolder.getEntityManager() +
               "] for JPA transaction");
      }
      txObject.setEntityManagerHolder(emHolder, false);
   }

   if (getDataSource() != null) {
      ConnectionHolder conHolder = (ConnectionHolder)
            TransactionSynchronizationManager.getResource(getDataSource());
      txObject.setConnectionHolder(conHolder);
   }

   return txObject;
}

在doBegin中,第6行如果发现在上一步骤中获取的到EntityManager为空,也就是说当前事务的第一个,则创建一个EntityManager(即Hibernate SessionImpl),17行调用底层Hibernate实现真正地开启一个事务,之后将EntityManager绑定到当前线程中(31行)供后续使用。

@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
   JpaTransactionObject txObject = (JpaTransactionObject) transaction;
      if (!txObject.hasEntityManagerHolder() ||
            txObject.getEntityManagerHolder().isSynchronizedWithTransaction()) {
         EntityManager newEm = createEntityManagerForTransaction();
         if (logger.isDebugEnabled()) {
            logger.debug("Opened new EntityManager [" + newEm + "] for JPA transaction");
         }
         txObject.setEntityManagerHolder(new EntityManagerHolder(newEm), true);
      }

      EntityManager em = txObject.getEntityManagerHolder().getEntityManager();

      // Delegate to JpaDialect for actual transaction begin.
      final int timeoutToUse = determineTimeout(definition);
      Object transactionData = getJpaDialect().beginTransaction(em,
            new DelegatingTransactionDefinition(definition) {
               @Override
               public int getTimeout() {
                  return timeoutToUse;
               }
            });
      txObject.setTransactionData(transactionData);

     ...

      // Bind the entity manager holder to the thread.
      if (txObject.isNewEntityManagerHolder()) {
         TransactionSynchronizationManager.bindResource(
               obtainEntityManagerFactory(), txObject.getEntityManagerHolder());
      }
      txObject.getEntityManagerHolder().setSynchronizedWithTransaction(true);
   }
}

底层调用下面的方法开启事物

spring-orm-5.0.11.RELEASE-sources.jar!/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java
beginTransaction
entityManager.getTransaction().begin(); // 开启事务

至此,事务切面的工作已经完成,工作完成了一半。下面讲述这些工作是如何应用到我们的代码里面的,我们通常使用JpaRepository来进行数据库操作,比如下面, 只是接口的声明,Spring容器在注入依赖时会生成代理,最终调用的方法为SimpleJpaRepository里面的方法。

@Transactional(propagation = Propagation.SUPPORTS)
public interface DemoPoRepository extends JpaRepository<DemoPo, Long>, JpaSpecificationExecutor<DemoPo> {
      Optional<DemoPo> findByUid(Long uid);
}
    /*
     * (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);
        }
    }

在save操作,如果为entity的新生成的,则调用EntityManager.persist否则为merge。

魔法在于第8行的em是被注入了什么对象?通常我们可以通过@PersistenceContext来获取容器管理em,这些em实际上是容器生成的em代理,Spring中通常用于代理的具体类为SharedEntityManagerInvocationHandler, 下面是代理类的方法调用的逻辑

@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   // Invocation on EntityManager interface coming in...

   //省略特殊方法调用处理

   // Determine current EntityManager: either the transactional one
   // managed by the factory or a temporary one for the given invocation.
   EntityManager target = EntityManagerFactoryUtils.doGetTransactionalEntityManager(
         this.targetFactory, this.properties, this.synchronizedWithTransaction);

   // Regular EntityManager operations.
   boolean isNewEm = false;
   if (target == null) {
      logger.debug("Creating new EntityManager for shared EntityManager invocation");
      target = (!CollectionUtils.isEmpty(this.properties) ?
            this.targetFactory.createEntityManager(this.properties) :
            this.targetFactory.createEntityManager());
      isNewEm = true;
   }

   // Invoke method on current EntityManager.
   try {
      Object result = method.invoke(target, args);
      if (result instanceof Query) {
         Query query = (Query) result;
         if (isNewEm) {
            Class<?>[] ifcs = ClassUtils.getAllInterfacesForClass(query.getClass(), this.proxyClassLoader);
            result = Proxy.newProxyInstance(this.proxyClassLoader, ifcs,
                  new DeferredQueryInvocationHandler(query, target));
            isNewEm = false;
         }
         else {
            EntityManagerFactoryUtils.applyTransactionTimeout(query, this.targetFactory);
         }
      }
      return result;
   }
   catch (InvocationTargetException ex) {
      throw ex.getTargetException();
   }
   finally {
      if (isNewEm) {
         EntityManagerFactoryUtils.closeEntityManager(target);
      }
   }
}

大致的逻辑就是拿到一个EntityManager并且调用它对应的方法执行, 魔法就在于第10行,其实现如下(缩略版)。从TransactionSynchronizationManager中获取之前绑定到线程的EntityManager (如果没有被绑定,如直接使用@PersistenceContext来获取EntityManager的方法来操纵数据库,则会生成一个EntityManager并绑定到当前线程),至此真相大白。

@Nullable
public static EntityManager doGetTransactionalEntityManager(
      EntityManagerFactory emf, @Nullable Map<?, ?> properties, boolean synchronizedWithTransaction)
      throws PersistenceException {

   Assert.notNull(emf, "No EntityManagerFactory specified");

   EntityManagerHolder emHolder =
         (EntityManagerHolder) TransactionSynchronizationManager.getResource(emf);
   if (emHolder != null) {
      if (synchronizedWithTransaction) {
         if (!emHolder.isSynchronizedWithTransaction()) {
            if (TransactionSynchronizationManager.isActualTransactionActive()) {
               // Try to explicitly synchronize the EntityManager itself
               // with an ongoing JTA transaction, if any.
               try {
                  emHolder.getEntityManager().joinTransaction();
               }
               catch (TransactionRequiredException ex) {
                  logger.debug("Could not join transaction because none was actually active", ex);
               }
            }
            if (TransactionSynchronizationManager.isSynchronizationActive()) {
               Object transactionData = prepareTransaction(emHolder.getEntityManager(), emf);
               TransactionSynchronizationManager.registerSynchronization(
                     new TransactionalEntityManagerSynchronization(emHolder, emf, transactionData, false));
               emHolder.setSynchronizedWithTransaction(true);
            }
         }
         // Use holder's reference count to track synchronizedWithTransaction access.
         // isOpen() check used below to find out about it.
         emHolder.requested();
         return emHolder.getEntityManager();
      }
      else {
         // unsynchronized EntityManager demanded
         if (emHolder.isTransactionActive() && !emHolder.isOpen()) {
            if (!TransactionSynchronizationManager.isSynchronizationActive()) {
               return null;
            }
            // EntityManagerHolder with an active transaction coming from JpaTransactionManager,
            // with no synchronized EntityManager having been requested by application code before.
            // Unbind in order to register a new unsynchronized EntityManager instead.
            TransactionSynchronizationManager.unbindResource(emf);
         }
         else {
            // Either a previously bound unsynchronized EntityManager, or the application
            // has requested a synchronized EntityManager before and therefore upgraded
            // this transaction's EntityManager to synchronized before.
            return emHolder.getEntityManager();
         }
      }
   }
   else if (!TransactionSynchronizationManager.isSynchronizationActive()) {
      // Indicate that we can't obtain a transactional EntityManager.
      return null;
   }

   // Create a new EntityManager for use within the current transaction.
   logger.debug("Opening JPA EntityManager");
   EntityManager em = null;
   if (!synchronizedWithTransaction) {
      try {
         em = emf.createEntityManager(SynchronizationType.UNSYNCHRONIZED, properties);
      }
      catch (AbstractMethodError err) {
         // JPA 2.1 API available but method not actually implemented in persistence provider:
         // falling back to regular createEntityManager method.
      }
   }
   if (em == null) {
      em = (!CollectionUtils.isEmpty(properties) ? emf.createEntityManager(properties) : emf.createEntityManager());
   }

   // Use same EntityManager for further JPA operations within the transaction.
   // Thread-bound object will get removed by synchronization at transaction completion.
   logger.debug("Registering transaction synchronization for JPA EntityManager");
   emHolder = new EntityManagerHolder(em);
   if (synchronizedWithTransaction) {
      Object transactionData = prepareTransaction(em, emf);
      TransactionSynchronizationManager.registerSynchronization(
            new TransactionalEntityManagerSynchronization(emHolder, emf, transactionData, true));
      emHolder.setSynchronizedWithTransaction(true);
   }
   else {
      // Unsynchronized - just scope it for the transaction, as demanded by the JPA 2.1 spec...
      TransactionSynchronizationManager.registerSynchronization(
            new TransactionScopedEntityManagerSynchronization(emHolder, emf));
   }
   TransactionSynchronizationManager.bindResource(emf, emHolder);

   return em;
}

需要注意,SimpleJpaRepository很多方法都标注了@Transactional,也就是每次都会生成一个新的EntityManager

总结Spring Jpa的事务实现的方式

1. 使用@Transactional注解标注事务,由TransactionInspector负责实现事务的提交及回滚,调用JpaTransactionManger来创建事务,并绑定到当前线程

2. 用户代码中注入的EntityManager为EntityManager的代理,其会获取当前线程绑定的真正的EntityManager (保证垮方法之间构成同一个事物)去执行。

下面是一个调用栈

Exception in thread "main" org.springframework.dao.EmptyResultDataAccessException: No class com.xx.repo.po.DemoPo entity with id 121 exists!
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.lambda$deleteById$0(SimpleJpaRepository.java:151)
    at java.util.Optional.orElseThrow(Optional.java:290)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.deleteById(SimpleJpaRepository.java:150)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:377)
    at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:200)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:629)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:593)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:578)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at com.xxx.metric.MeasurePostProcessor$MeasureMethodInterceptor.invoke(MeasurePostProcessor.java:36)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:135)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.sun.proxy.$Proxy86.deleteById(Unknown Source)
    at com.xxx.Main.main(Main.java:27)

其他一些释义
https://stackoverflow.com/questions/10762974/should-jpa-entity-manager-be-closed

as you can see the entity manager is the public interface through which you access your entities, however, your entities reside in a context, attached to your entity manager. Understanding the life cycle of the different types of contexts will answer your question.

Persistence contexts can be of different types. In Java EE applications, you can either have a transaction-scoped persistence context or a extended-persistence context. In the JSE application, the nature of the context is controlled by the developer.

When you ask for an entity to your entity manager, it looks for the entity in its attached context, if it finds the entity there, then it returns it, otherwise, it retrieves the entity from the database. Subsequent calls for this entity in context will return the same entity.

https://www.cnblogs.com/wangyonglong/p/5178450.html

基本概念 https://blog.csdn.net/hy6688_/article/details/46701111

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。