Skip to content

同步资源与事务 (Synchronizing Resources with Transactions)

现在你应该已经清楚如何创建不同的事务管理器,以及它们如何与需要同步到事务的相关资源相关联(例如 DataSourceTransactionManager 关联到 JDBC DataSourceHibernateTransactionManager 关联到 Hibernate SessionFactory 等)。本节描述应用程序代码(直接或间接地,通过使用 JDBC、Hibernate 或 JPA 等持久化 API)如何确保正确地创建、重用和清理这些资源。本节还讨论了如何通过相关的 TransactionManager(可选地)触发事务同步。

高层同步方法

首选的方法是使用 Spring 的最高层模板化持久化集成 API,或者使用带有事务感知(transaction-aware)工厂 Bean 或代理的原生 ORM API,来管理原生的资源工厂。这些事务感知解决方案内部处理了资源的创建和重用、清理、可选的资源事务同步以及异常映射。因此,用户的数据访问代码无需处理这些任务,而可以纯粹专注于非样板式的持久化逻辑。通常,你可以使用原生的 ORM API,或者对于 JDBC 访问,采取使用 JdbcTemplate 的模板方法。这些解决方案在参考文档的后续章节中会有详细介绍。

底层同步方法

在较低层级上,存在诸如 DataSourceUtils(用于 JDBC)、EntityManagerFactoryUtils(用于 JPA)、SessionFactoryUtils(用于 Hibernate)等类。当你希望应用程序代码直接处理原生持久化 API 的资源类型时,你会使用这些类来确保获取到由 Spring 框架管理的正确实例、确保事务(可选地)得到同步,并且将过程中发生的异常正确映射到一致的 API。

例如,在 JDBC 的情况下,不再使用传统的从 DataSource 调用 getConnection() 方法的方法,而是改用 Spring 的 org.springframework.jdbc.datasource.DataSourceUtils 类,如下所示:

java
Connection conn = DataSourceUtils.getConnection(dataSource);

如果现有事务已经有一个与之同步(链接)的连接,则返回该实例。否则,该方法调用会触发创建一个新连接,该连接会(可选地)同步到任何现有事务,并可供同一事务中的后续重用。如前所述,任何 SQLException 都会被包装在 Spring 框架的 CannotGetJdbcConnectionException 中,这是 Spring 框架非检查型 DataAccessException 类型层级中的一种。这种方法比单纯从 SQLException 中获取信息更丰富,并确保了跨数据库、甚至跨不同持久化技术的移植性。

这种方法在没有 Spring 事务管理的情况下也能工作(事务同步是可选的),因此无论你是否使用 Spring 的事务管理,都可以使用它。

当然,一旦你使用了 Spring 的 JDBC 支持、JPA 支持或 Hibernate 支持,你通常就不会再想使用 DataSourceUtils 或其他辅助类了。因为你会更乐于通过 Spring 抽象来工作,而不是直接使用相关的 API。例如,如果你使用 Spring 的 JdbcTemplatejdbc.object 包来简化 JDBC 的使用,正确的连接获取过程会在后台自动发生,你无需编写任何特殊代码。

TransactionAwareDataSourceProxy

在最底层存在 TransactionAwareDataSourceProxy 类。这是目标 DataSource 的代理,它包装了目标 DataSource,以增加对 Spring 管理事务的感知。在这方面,它类似于 Jakarta EE 服务器提供的事务性 JNDI DataSource

你几乎永远不需要也不应该使用这个类,除非必须调用现有的代码,并且该代码必须传入一个标准的 JDBC DataSource 接口实现。在这种情况下,虽然该代码是可以运行的,但它也能参与到 Spring 管理的事务中。对于新编写的代码,你应该使用前面提到的更高层抽象。


补充教学

1. 资源同步背后的“黑盒”:ThreadLocal

Spring 是如何保证在同一个事务方法里,多次调用 DAO 获取的是同一个数据库连接?

关键在于 TransactionSynchronizationManager

  • 当事务开启时,Spring 会从连接池获取一个连接,并将其对应的“连接持有者” (Connection Holder) 绑定到当前线程的 ThreadLocal 中。
  • 后续无论是 JdbcTemplate 还是 DataSourceUtils.getConnection(),都会先去 ThreadLocal 查看是否有已绑定的连接。
  • 同步 (Synchronization):在事务提交或回滚后,Spring 会负责从 ThreadLocal 中解绑并关闭连接。

2. DataSourceUtils 存在的意义:原生 API 的“救命稻草”

如果你因为某些特殊原因(例如需要调用一个极老的遗留库,或者使用底层数据库厂商的特定 API)不得不操作原生 Connection 对象:

  • 错误示范dataSource.getConnection()。这会从池里拿一个新连接,它不受当前 Spring 事务控制,也不会自动关闭。
  • 正确示范DataSourceUtils.getConnection(dataSource)。它会帮你检查当前是否有事务,如果有,就给你那个事务正在用的连接;如果没有,它会给一个新连接,但在你由于缺乏 @Transactional 而没法自动管理时,它还能提供一致的异常转译。

3. 解读 TransactionAwareDataSourceProxy

这个代理模式通常只出现在集成场景中。

场景举例:你有一段使用了 10 年的老代码,它写死了只接收一个 javax.sql.DataSource 对象,并在内部直接调用 conn.close()

  • 如果你传个普通的 HikariDataSource 进去,事务会乱套,因为老代码会提前关闭连接。
  • 如果你传个 TransactionAwareDataSourceProxy 包装后的数据源,当老代码调用 close() 时,代理会检查:“哦,这个连接还在 Spring 事务里呢,我现在不能真的关掉它,我先留着。等事务提交了,我再关。”

4. 黄金原则:尽可能留在高层

在 99% 的 Spring 项目中,你都不应该看到 DataSourceUtilsTransactionAwareDataSourceProxy

  • JDBC:用 JdbcTemplate
  • JPA:用 JpaRepositoryEntityManager
  • MyBatis:用 mybatis-spring 提供的集成。 只要留在这些框架内,Spring 就会自动帮你处理好所有的资源同步和生命周期管理。

Based on Spring Framework.