Spring @Transactional 声明式事务揭秘

本章,我们将深入探讨Spring事务管理,为你揭秘@Transactional注解底层工作原理。

JPA和事务管理

首先,我们要知道,JPA本身不提供任何声明式事务管理。当我们在任意依赖注入容器外使用JPA时,需要由开发人员以编程方式来处理事务。

UserTransaction utx = entityManager.getTransaction(); 

try { 
    utx.begin(); 
    businessLogic();
    utx.commit(); 
} catch(Exception ex) { 
    utx.rollback(); 
    throw ex; 
} 

这种管理事务的方式使得代码中的事务范围非常清晰,但它有几个明显不足:

  • 代码重复且容易出错
  • 任意一个微小的错误都都可能会产生很大的影响
  • 错误很难调试和重现
  • 降低了代码的可读性
  • 如果此方法调用另一个事务方法如何处理事务?

使用Spring @Transactional注解

如果我们使用@Transactional注解,那么上面的代码就可以简化为:

@Transactional
public void businessLogic() {
... use entity manager inside a transaction ...
}

这样以来,通过@Transactional注解使得代码更方便和可读,这也是目前Spring中事务处理的推荐做法。

通过使用 @Transactional,许多事务相关的都可以被自动处理,例如:事务传播等等。例如,如果businessLogic()方法调用另一个事务方法 ,该方法将根据事务传播配置确定是否加入当前正在进行的事务。

然而,万事皆有两面性,@Transactional的强大机制为我们的事务管理提供了便利,然而正是这种强大机制隐藏了内部实现细节,进而使得当问题发生时,我们无法很好的去跟踪调试。

所以,我们要探究@Transactional背后的机制。

要正确理解@Transactional,我们首先要理解掌握以下两个不同的概念,每一个概念都有自己的作用范围和生命周期:

  • Persistence Context:持久化上下文
  • Database Transaction:数据库事务

@Transactional注解本身定义的是一个数据库事务的作用范围,而数据库事务则是作用在一个特定的持久化上下文(Persistence Context)中。

当我们使用Hibernate作为持久化方案时,JPA中的持久化上下文(Persistence Context)是Entity Manager(实体管理器),其由Hibernate Session内部实现。

持久化上下文(Persistence Context)本身只是一个同步器对象,其用以跟踪记录一组有限的Java对象的状态,并确保对这些对象的更改最终被保留回数据库。

这与一个数据库事务是完全不同的概念,一个EntityManager(实体管理器)通常被用于多个数据库事务。

EntityManager何时跨越多个数据库事务?

最常见的情况是,当应用程序使用Open Session In View模式来处理延迟初始化异常时。在这种情况下,在视图层中的多个查询通常包含各自单独的数据库事务中,但这些数据库事务都是由同一个EntityManager(实体管理器)所创建。

另一种情况则是开发人员将持久化上下文标记为PersistenceContextType.EXTENDED,这也就意味着它可以在多个请求中存在作用。

如何定义Entity Manager与Transaction关系?

这实际上是每个应用开发人员的选择,但使用JPA Entity Manager 最常见的方式是
“Entity Manager per application transaction”模式(一个应用事务对应一个实体管理器EntityManager)。这是最常见的注入Entity Manager方法:

@PersistenceContext
private EntityManager em;

这里,我们默认使用“Entity Manager per transaction”模式,即一个事务对应一个EntityManager模式。在此模式下,如果我们在@Transactional 方法中使用此EntityManager,则该方法将在单个数据库事务中运行。

@PersistenceContext如何工作?

一个明显的问题,考虑到EntityManager生命周期如此短暂,并且在每一个应用请求中又包含诸多EntityManager实例,那么@PersistenceContext是如何在应用容器启动时只注入到EntityManager一次呢?

答案是它不能:EntityManager只是一个接口,实际注入的是一个持久化上下文代理(context aware proxy:具体实现类SharedEntityManagerInvocationHandler),由其在运行时负责关联到具体的实体管理器实例。

@Transactional如何工作

要想确保声明式事务正确工作,仅仅靠实现了EntityManager接口的持久化上下文(persistence context proxy )代理并不行,这里还需要介绍另外三个单独的组件:

  • EntityManager Proxy (实体管理器代理本身)
  • Transactional Aspect(事务切面)
  • Transaction Manager(事务管理器)

接下来,让我们看下它们之间如何交互。

Transactional Aspect:事务切面

Transactional Aspect是一个”环绕”切面,具体实现类TransactionInterceptor,Transactional Aspect主要有两个职责:

  • “方法调用前”,切面提供一个hook point,用于确定要调用的业务方法加入当前正在进行的事务,还是新启事务。
  • “方法调用后”,由切面决定事务动作:提交,回滚或继续运行。

“方法调用前”,Transactional Aspect本身不包含任何决策逻辑,新启事务的动作将委托给Transaction Manager。

Transaction Manager

Transaction Manager需要负责以下两个逻辑:

  • 是否需要创建新的Entity Manager实例
  • 是否需要新启新的数据库事务

所有这些都会在Transactional Aspect ‘执行前’被确定,Transaction Manager将基于以下情况确认接下来的动作:

  • 是否已有正在进行的事务
  • 事务方法的传播属性

一旦确认需要创建新的数据库事务,那么Transaction Manager将执行以下动作:

  • 创建新的Entity Manager实例
  • 将创建的Entity Manager实例与当前线程绑定
  • 从数据库连接池中获取数据库连接
  • 将获取的数据库连接与当前线程绑定

EntityManager实例和数据库连接都会以ThreadLocal变量的方式与当前线程绑定。当事务执行时,他们会被保存在当前线程中,并且由Transaction Manager负责清理。当然,如果需要获取当前的线程的Entity Manager和数据库连接,是需要通过EntityManager proxy来获取。

EntityManager Proxy(Entity Manager 代理)

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声明式事务管理机制非常强大,但它容易误用或错误配置。深入了解其工作机制对于后续我们解决问题时非常重要,但总结起来需要我们注意两个概念:数据库事务和持久化上下文:

  • Persistence Context:持久化上下文
  • Database Transaction:数据库事务

https://dzone.com/articles/how-does-spring-transactional

你可能感兴趣的:(Java,架构,Spring,中间件)