本章,我们将深入探讨Spring事务管理,为你揭秘@Transactional注解底层工作原理。
首先,我们要知道,JPA本身不提供任何声明式事务管理。当我们在任意依赖注入容器外使用JPA时,需要由开发人员以编程方式来处理事务。
UserTransaction utx = entityManager.getTransaction();
try {
utx.begin();
businessLogic();
utx.commit();
} catch(Exception ex) {
utx.rollback();
throw ex;
}
这种管理事务的方式使得代码中的事务范围非常清晰,但它有几个明显不足:
如果我们使用@Transactional注解,那么上面的代码就可以简化为:
@Transactional
public void businessLogic() {
... use entity manager inside a transaction ...
}
这样以来,通过@Transactional注解使得代码更方便和可读,这也是目前Spring中事务处理的推荐做法。
通过使用 @Transactional,许多事务相关的都可以被自动处理,例如:事务传播等等。例如,如果businessLogic()方法调用另一个事务方法 ,该方法将根据事务传播配置确定是否加入当前正在进行的事务。
然而,万事皆有两面性,@Transactional的强大机制为我们的事务管理提供了便利,然而正是这种强大机制隐藏了内部实现细节,进而使得当问题发生时,我们无法很好的去跟踪调试。
要正确理解@Transactional,我们首先要理解掌握以下两个不同的概念,每一个概念都有自己的作用范围和生命周期:
@Transactional注解本身定义的是一个数据库事务的作用范围,而数据库事务则是作用在一个特定的持久化上下文(Persistence Context)中。
当我们使用Hibernate作为持久化方案时,JPA中的持久化上下文(Persistence Context)是Entity Manager(实体管理器),其由Hibernate Session内部实现。
持久化上下文(Persistence Context)本身只是一个同步器对象,其用以跟踪记录一组有限的Java对象的状态,并确保对这些对象的更改最终被保留回数据库。
这与一个数据库事务是完全不同的概念,一个EntityManager(实体管理器)通常被用于多个数据库事务。
最常见的情况是,当应用程序使用Open Session In View模式来处理延迟初始化异常时。在这种情况下,在视图层中的多个查询通常包含各自单独的数据库事务中,但这些数据库事务都是由同一个EntityManager(实体管理器)所创建。
另一种情况则是开发人员将持久化上下文标记为PersistenceContextType.EXTENDED,这也就意味着它可以在多个请求中存在作用。
这实际上是每个应用开发人员的选择,但使用JPA Entity Manager 最常见的方式是
“Entity Manager per application transaction”模式(一个应用事务对应一个实体管理器EntityManager)。这是最常见的注入Entity Manager方法:
@PersistenceContext
private EntityManager em;
这里,我们默认使用“Entity Manager per transaction”模式,即一个事务对应一个EntityManager模式。在此模式下,如果我们在@Transactional 方法中使用此EntityManager,则该方法将在单个数据库事务中运行。
一个明显的问题,考虑到EntityManager生命周期如此短暂,并且在每一个应用请求中又包含诸多EntityManager实例,那么@PersistenceContext是如何在应用容器启动时只注入到EntityManager一次呢?
答案是它不能:EntityManager只是一个接口,实际注入的是一个持久化上下文代理(context aware proxy:具体实现类SharedEntityManagerInvocationHandler),由其在运行时负责关联到具体的实体管理器实例。
要想确保声明式事务正确工作,仅仅靠实现了EntityManager接口的持久化上下文(persistence context proxy )代理并不行,这里还需要介绍另外三个单独的组件:
接下来,让我们看下它们之间如何交互。
Transactional Aspect是一个”环绕”切面,具体实现类TransactionInterceptor,Transactional Aspect主要有两个职责:
在“方法调用前”,Transactional Aspect本身不包含任何决策逻辑,新启事务的动作将委托给Transaction Manager。
Transaction Manager需要负责以下两个逻辑:
所有这些都会在Transactional Aspect ‘执行前’被确定,Transaction Manager将基于以下情况确认接下来的动作:
一旦确认需要创建新的数据库事务,那么Transaction Manager将执行以下动作:
EntityManager实例和数据库连接都会以ThreadLocal变量的方式与当前线程绑定。当事务执行时,他们会被保存在当前线程中,并且由Transaction Manager负责清理。当然,如果需要获取当前的线程的Entity Manager和数据库连接,是需要通过EntityManager proxy来获取。
EntityManager代理是确保整个声明事务机制正确运行的最后一个组件。假如,当业务方法调用entityManager.persist(),当前调用并不会直接调用EntityManager本身,而是调用EntityManager Proxy,由EntityManager Proxy负责从当前线程中获取当前EntityManager实例对象(之前我们说的,EntityManager 会负责放置管理EntityManager的实例对象)。
接下来,我们来看下如何配置这三个组件,以使声明式事务如何正确工作。
首先定义EntityManagerFactory,这样EntityManager Proxy就可以通过@PersistenceContext实现注入。
@Configuration
public class EntityManagerFactoriesConfiguration {
@Autowired
private DataSource dataSource;
@Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean emf() {
LocalContainerEntityManagerFactoryBean emf = ...
emf.setDataSource(dataSource);
emf.setPackagesToScan(new String[] {"your.package"});
emf.setJpaVendorAdapter(
new HibernateJpaVendorAdapter());
return emf;
}
}
接下来,在@Transactional注解类中配置Transaction Manager以及应用Transactional Aspect
@Configuration
@EnableTransactionManagement
public class TransactionManagersConfig {
@Autowired
EntityManagerFactory emf;
@Autowired
private DataSource dataSource;
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager() {
JpaTransactionManager tm = new JpaTransactionManager();
tm.setEntityManagerFactory(emf);
tm.setDataSource(dataSource);
return tm;
}
}
添加@EnableTransactionManagement注解的目的是告诉Spring,凡是带有@Transactional注解的类都要被Transactional Aspect切面环绕。有了这个,@Transactional就可以正常工作了。
Spring声明式事务管理机制非常强大,但它容易误用或错误配置。深入了解其工作机制对于后续我们解决问题时非常重要,但总结起来需要我们注意两个概念:数据库事务和持久化上下文:
https://dzone.com/articles/how-does-spring-transactional