每日必读DZone Spring:Spring @Transactional 是如何真正工作的?

Posted boonya

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了每日必读DZone Spring:Spring @Transactional 是如何真正工作的?相关的知识,希望对你有一定的参考价值。

目录

JPA 和事务管理

使用 Spring @Transactional

 @Transactional是什么意思?

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

什么定义了 EntityManager 与 Transaction 关系?

@PersistenceContext 是如何工作的?

那么@Transactional 是如何工作的呢?

交易方面

事务管理器

EntityManager 代理

把它们放在一起

结论

示例


想知道 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

使用 Spring  @Transactional,上面的代码被简化为:

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

这更加方便和可读,并且是目前在 Spring 中处理事务的推荐方式。

通过使用 @Transactional,事务传播等许多重要方面都可以自动处理。在这种情况下,如果另一个事务方法由 调用 businessLogic(),该方法将可以选择加入正在进行的事务。

一个潜在的缺点是这种强大的机制隐藏了幕后发生的事情,使得当事情不工作时很难调试。

 @Transactional是什么意思?

其中一个关键点 @Transactional是需要考虑两个不同的概念,每个概念都有自己的范围和生命周期:

  • 持久性上下文
  • 数据库事务

事务注释本身定义了单个数据库事务的范围。数据库事务发生在持久性上下文的范围内。

持久性上下文在 JPA 中 EntityManager,使用 Hibernate 在内部实现 Session(当使用 Hibernate 作为持久性提供者时)。

持久化上下文只是一个同步器对象,它跟踪一组有限的 Java 对象的状态,并确保这些对象上的更改最终被持久化回数据库。

这是一个与数据库事务非常不同的概念。一个实体管理器 可以跨多个数据库事务使用,而且实际上经常如此。

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

最常见的情况是应用程序使用 Open Session In View 模式来处理延迟初始化异常,请参阅上一篇博客文章了解它的优缺点。

在这种情况下,在视图层中运行的查询与用于业务逻辑的查询在不同的数据库事务中,但它们是通过相同的实体管理器进行的。

另一种情况是开发人员将持久性上下文标记为 PersistenceContextType.EXTENDED,这意味着它可以承受多个请求。

什么定义了 EntityManager 与 Transaction 关系?

这实际上是应用程序开发人员的选择,但使用 JPA 实体管理器最常见的方式是使用 
“每个应用程序事务的实体管理器”模式。这是注入实体管理器的最常见方式:

@PersistenceContext
private EntityManager em;

默认情况下,我们处于“每个事务的实体管理器”模式。在这种模式下,如果我们在一个 @Transactional方法中使用这个实体管理器,那么该方法将在单个数据库事务中运行。

@PersistenceContext 是如何工作的?

想到的一个问题是,如何 @PersistenceContext在容器启动时只注入一次实体管理器,因为实体管理器的寿命很短,而且每个请求通常有多个。

答案是它不能: EntityManager是一个接口,注入到 spring bean 中的不是实体管理器本身,而是 一个上下文感知代理,它将在运行时委托给具体的实体管理器。

通常用于代理的具体类是 
SharedEntityManagerInvocationHandler,这可以在调试器的帮助下确认。

那么@Transactional 是如何工作的呢?


实现的持久性上下文代理 EntityManager并不是使声明式事务管理工作所需的唯一组件。实际上需要三个单独的组件:

  • EntityManager 代理本身
  • 交易方面
  • 事务管理器

让我们回顾一下每一个,看看它们是如何相互作用的。

交易方面

Transactional Aspect 是一个'around' 方面,在带注释的业务方法之前和之后都被调用。实现方面的具体类是 TransactionInterceptor.

事务方面有两个主要职责:

  • 在“之前”时刻,方面提供了一个挂钩点,用于确定将要调用的业务方法是否应该在正在进行的数据库事务的范围内运行,或者是否应该启动一个新的单独事务。
  • 在“之后”时刻,方面需要决定事务是应该提交、回滚还是继续运行。

在“之前”时刻,事务方面本身不包含任何决策逻辑,如果需要,启动新事务的决策被委托给事务管理器。

事务管理器

事务管理器需要回答两个问题:

  • 是否应该创建一个新的实体管理器?
  • 是否应该启动一个新的数据库事务?

这需要在调用事务方面“之前”逻辑时决定。事务管理器将根据以下因素做出决定:

  • 一项交易是否已经在进行的事实
  • 事务方法的传播属性(例如 REQUIRES_NEW总是启动一个新事务)

如果事务管理器决定创建一个新事务,那么它将:

  • 创建一个新的实体管理器
  • 将实体管理器绑定到当前线程
  • 从数据库连接池中获取一个连接
  • 将连接绑定到当前线程
  • 实体管理器和连接都使用 ThreadLocal变量绑定到当前线程。

它们在事务运行时存储在线程中,由事务管理器在不再需要时清理它们。

需要当前实体管理器或连接的程序的任何部分都可以从线程中检索它们。一个程序组件就是 EntityManager 代理。


EntityManager 代理

EntityManager 代理(我们之前介绍过)是最后一块拼图。例如,当业务方法调用时 
entityManager.persist(),此调用不是直接调用实体管理器。

相反,业务方法调用代理,该代理从事务管理器放置它的线程中检索当前实体管理器。

现在知道了 @Transactional机制的活动部分是什么,让我们回顾一下实现这项工作所需的常用 Spring 配置。

把它们放在一起

让我们来看看如何设置使事务注释正常工作所需的三个组件。我们首先定义实体管理器工厂。

这将允许通过持久性上下文注释注入实体管理器代理:

@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注释的类中应用事务方面:

@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 声明式事务管理机制非常强大,但它很容易被误用或错误配置。

在解决机制根本不工作或以意外方式工作的情况时,了解它的内部工作原理很有帮助。

要记住的最重要的事情是,实际上有两个概念需要考虑:数据库事务和持久性上下文,每个概念都有自己的不明显的生命周期。

未来的文章将讨论事务注释最常见的陷阱以及如何避免它们。

示例

EnableTransactionManagement (Spring Framework 5.3.16 API)

以上是关于每日必读DZone Spring:Spring @Transactional 是如何真正工作的?的主要内容,如果未能解决你的问题,请参考以下文章

DZone每日必读-News:2022 年 Java 开发:预测和选定趋势

SpingSpringMVCSpringBoot的对比

学习Spring之前必读的文章,Spring框架第0章(Spring核心+优点+体系结构+简单搭建)

干货 | 3月跳槽季,Java面试必读之Spring十三问

2017JAVA必读书籍

工程师值得一看,Java Web必读