Spring 事务管理(1)—— 事务抽象

245 阅读10分钟

官方文档之事务管理:docs.spring.io/spring-fram…

概念

Spring框架为事务管理提供了一致的抽象。优点如下:

  • 为不同的事务 API 提供了一致的编程模型。如:Java Transaction API(JTA)、JDBC、Hibernate、Java Persistence API(JPA)。
  • 支持声明式事务管理(declarative transaction management
  • 用于编程式事务管理(programmatic transaction management)的 API 比复杂事务 API(如 JTA) 更简单。
  • 与 Spring 数据访问抽象层更好的集成。

Spring事务抽象

Spring 事务抽象的关键是事务策略的概念。事务策略是由 TransactionManager 定义的:

  • org.springframework.transaction.PlatformTransactionManager: 命令式事务接口(imperative transaction management)
  • org.springframework.transaction.ReactiveTransactionManager: 响应式事务接口(reactive transaction management)

TransactionManager implementations normally require knowledge of the environment in which they work: JDBC, JTA, Hibernate, and so on

PlatformTransactionManager

这是Spring事务基础结构中的最重要的接口。应用程序可以直接使用它,但它主要不是作为API:通常,应用程序将使用 TransactionTemplate 或通过 AOP 进行声明性事务界定。

public interface PlatformTransactionManager extends TransactionManager {
    
    /***
     * 据指定的传播行为,返回当前活动的事务或创建一个新事务。
     *
     * 请注意,隔离级别或超时等参数将只应用于新事务,因此在参与活动事务时将被忽略。
     *
     * 此外,并非每个事务管理器都支持所有事务定义设置:当遇到不支持的设置时,正确的事务管理器实现应该抛出异常。
     * 此规则有一个例外是只读标志,如果不支持显式只读模式,应该忽略该标志。实际上,只读标志只是潜在优化的一个提示。
     */
    TransactionStatus getTransaction(TransactionDefinition definition) throws 
TransactionException; 
    
    /**
     * 根据事务的状态提交给定的事务。如果以编程方式将事务标记为仅回滚,则执行回滚。
     *  
     * 如果该事务不是新事务,则忽略提交,以便在周围事务中进行适当的参与。如果之前的事务
     * 已被挂起以便能够创建新的事务,则在提交新事务后恢复之前的事务。
     *
     * 请注意,当提交调用完成时,无论是否正常或抛出异常,事务都必须完全完成和清理。
     * 在这种情况下,不应该期望有回滚调用。
     *
     * 如果此方法抛出一个TransactionException以外的异常,则某些提交前的错误导致提交失败。
     * 例如,O/R Mapping工具可能尝试在提交之前将更改刷新到数据库,结果发生DataAccessException导致事务失败。
     * 在这种情况下,原始异常将传播到此提交方法的调用方。
     */
    void commit(TransactionStatus status) throws TransactionException; 
    
    /**
     * 执行给定事务的回滚。
     *
     * 如果该事务不是一个新事务,只需将其设置为回滚,以便适当地参与周围的事务。
     *
     * 如果前一个事务已被挂起以便能够创建一个新事务,新事务回滚后则恢复前一个事务。
     *
     * 如果提交抛出异常,不要在事务上调用回滚。
     * 当提交返回时,事务将已经完成并被清理,即使是在提交异常的情况下。
     * 因此,提交失败后的回滚调用将导致IllegalTransactionStateException异常。
     */
    void rollback(TransactionStatus status) throws TransactionException;
    
}

继承结构

对于实现类,建议从提供的org.springframework.transaction.support.AbstractPlatformTransactionManager 类派生,该类预先实现了定义的传播行为,并负责事务同步处理。子类必须为底层事务的特定状态实现模板方法,例如:begin, suspend, resume, commit。

1649409178(1).jpg

这个策略接口的默认实现是org.springframework.transaction.jta.JtaTransactionManagerorg.springframework.jdbc.datasource。DataSourceTransactionManager,它可以作为其他事务的实现指南。

DataSourceTransactionManager

用于实现单个 JDBC 数据源的事务管理。 只要程序使用 javax.sql.DataSource 作为其Connection factory 机制,该类就能够在任何环境中使用任何JDBC驱动程序。将指定数据源的 JDBC 连接绑定到当前线程,可能允许每个数据源有一个线程绑定连接。

注意: 这个事务管理器操作的数据源需要返回独立的Connections。连接可能来自池(典型情况),但数据源不能返回线程作用域(thread-scoped)/请求作用域(request-scoped) 的连接或类似的东西。这个事务管理器将根据指定的传播行为,将Connections与线程绑定的事务本身相关联。它假设即使在正在进行的事务中也可以获得一个独立的连接。

示例

使用 JDBC,定义一个本地 PlatformTransactionManager 的实现。

创建一个bean用于定义一个 JDBC 数据源:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
    <property name="driverClassName" value="${jdbc.driverClassName}" /> 
    <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> 
    <property name="password" value="${jdbc.password}" /> 
</bean>

然后相关的 PlatformTransactionManager bean 定义有一个对DataSource定义的引用:

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
    <property name="dataSource" ref="dataSource"/> 
</bean>

JtaTransactionManager

用于实现 JTA 的事务管理,委托给后端 JTA 提供者。这通常用于委托给Java EE服务器的事务协调者,但也可以使用嵌入到应用程序中的本地JTA提供者进行配置。

这个事务管理器通常适用于处理分布式事务,即跨越多个资源的事务,以及控制应用服务器资源(例如JNDI中可用的JDBC数据源)上的事务。对于单个JDBC DataSource, DataSourceTransactionManager 就足够了,而对于使用Hibernate访问单个资源(包括事务缓存),HibernateTransactionManager 是合适的。

对于典型的JTA事务(REQUIRED, SUPPORTS, MANDATORY, NEVER),一个普通的JtaTransactionManager 定义就是您所需要的,它可以跨所有Java EE服务器移植。这对应于JTA UserTransaction的功能,Java EE为它指定了一个标准的JNDI名称(“Java:comp/UserTransaction”)。对于这种JTA的使用,不需要配置特定于服务器的TransactionManager查找。

示例

如果在Java EE容器中使用JTA,那么您将使用通过JNDI获得的容器数据源,以及Spring的JtaTransactionManager。

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:jee="http://www.springframework.org/schema/jee" 
xsi:schemaLocation=" http://www.springframework.org/schema/beans 
https://www.springframework.org/schema/beans/spring-beans.xsd 
http://www.springframework.org/schema/jee 
https://www.springframework.org/schema/jee/spring-jee.xsd"> 

<jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/> 

<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" /> 

<!-- other <bean/> definitions here --> </beans>

JtaTransactionManager不需要知道DataSource(或任何其他特定的资源),因为它使用容器的全局事务管理基础设施。

ReactiveTransactionManager

从 Spring 框架5.2版本开始,Spring还为使用响应式类型或Kotlin协程的响应式应用程序提供了事务管理抽象。

这是Spring响应性事务基础结构中的最重要接口。应用程序可以直接使用它,但它主要不是作为API:通常,应用程序将使用事务操作符或通过AOP进行声明性事务界定。

public interface ReactiveTransactionManager extends TransactionManager {

    Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition) throws TransactionException; 
    
    Mono<Void> commit(ReactiveTransaction status) throws TransactionException; 
    
    Mono<Void> rollback(ReactiveTransaction status) throws TransactionException; 
    
}

TransactionDefinition

TransactionDefinition 接口指定:

  • 传播性(Propagation):通常,事务范围内的所有代码都在该事务中运行。但是,如果在事务上下文已经存在的情况下运行事务方法,则可以指定其行为。例如,代码可以在现有事务中继续运行(常见情况),或者现有事务可以挂起并创建一个新事务。Spring提供了与EJB CMT类似的所有事务传播选项。

  • 隔离性(Isolation):该事务与其他事务的工作隔离的程度。例如,这个事务可以看到其他事务未提交的写操作吗?

  • 超时(Timeout):在超时并被底层事务基础设施自动回滚之前该事务运行多长时间。

  • 只读状态(Read-only status):当代码读取但不修改数据时,可以使用只读事务。只读事务在某些情况下可能是一种有用的优化,比如在使用Hibernate时。

TransactionStatus

TransactionStatus 接口为事务代码提供了一种简单的方式来控制事务执行和查询事务状态:

public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable { 

    /**
      * 返回当前事务是否为新事务;否则,将参与现有事务,或者可能一开始就不在实际事务中运行
      */
    boolean isNewTransaction(); 
    
    /**
      * 返回该事务是否在内部携带保存点,也就是说,已根据保存点作为嵌套事务创建。
      */
    boolean hasSavepoint(); 
    
    /**
      * 设置事务仅回滚。这将指示事务管理器,事务的唯一可能结果可能是回滚,以替代抛出异常(后者将反过来触发回滚)
      */
    void setRollbackOnly(); 
    
    /**
      * 返回事务是否被标记为仅回滚(由应用程序或由事务基础设施)。
      */
    boolean isRollbackOnly(); 
    
    /**
      * 将底层会话刷新到数据存储(如果适用的话):例如,所有受影响的Hibernate/JPA会话。
      *
      * 这实际上只是一个提示,如果基础事务管理器没有刷新概念,则可能是一个空操作。
      * 刷新信号可以应用于主资源或事务同步,具体取决于底层资源。
      */ 
    void flush(); 
    
    /**
      * 返回该事务是否已完成,也就是说,它是否已经被提交或回滚。
      */
    boolean isCompleted(); 
 
}

资源与事务同步

高级的同步方法

首选的方法是使用 Spring 的最高级别的基于模板的持久性集成 api,或者使用具有事务感知的工厂bean或代理的本地ORM api来管理本地资源工厂。这些事务感知的解决方案在内部处理资源的创建和重用、清理、资源的可选事务同步以及异常映射。因此,用户数据访问代码不必处理这些任务,而是可以单纯地关注非样板的持久性逻辑。通常,你使用本地 ORM API 或通过使用 JdbcTemplate 来采用JDBC访问的模板方法。

低级的同步方法

诸如 DataSourceUtils(用于JDBC)、EntityManagerFactoryUtils(用于JPA)、SessionFactoryUtils(用于Hibernate)等类存在于较低的级别。当你想让应用程序代码直接处理原生资源类型的持久性API,您使用这些类来确保获得适当的Spring Framework-managed实例,事务是(可选)同步的,在这个过程中意外产生的异常被正确映射到一个一致的API。

例如,在JDBC的情况下,你可以使用Spring的org.springframework. JDBC .DataSource . datasource.DataSourceUtils类,而不是传统的JDBC方法,即在DataSource上调用getConnection()方法,如下所示:

Connection conn = DataSourceUtils.getConnection(dataSource);

如果一个现有事务已经有一个同步(链接)到它的连接,则返回该实例。否则,方法调用将触发新连接的创建,该连接(可选地)与任何现有事务同步,并可用于同一事务中的后续重用。如前所述,任何SQLException 都包装在 Spring 框架的 CannotGetJdbcConnectionException 中,它是Spring框架中 unchecked DataAccessException 类型的层次结构之一。这种方法提供了比从 SQLException 中轻松获得的更多信息,并确保了跨数据库、甚至跨不同持久性技术的可移植性。

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

当然,一旦您使用了Spring的JDBC支持、JPA支持或Hibernate支持,您通常不喜欢使用DataSourceUtils或其他助手类,因为与直接使用相关api相比,通过Spring抽象工作要愉快得多。例如,如果您使用Spring JdbcTemplatejdbc.object 包来简化JDBC的使用,正确的连接检索发生在幕后,您不需要编写任何特殊的代码。

TransactionAwareDataSourceProxy

在最底层存在 TransactionAwareDataSourceProxy 类。这是一个目标DataSource的代理,它包装了目标DataSource,以增加对spring管理的事务的感知。在这方面,它类似于由Java EE服务器提供的事务 JNDI 数据源。

您几乎不应该需要或希望使用这个类,除非必须调用现有代码并传递标准的JDBC DataSource接口实现。在这种情况下,这段代码可能是可用的,但却参与了spring管理的事务。您可以通过使用前面提到的高级抽象来编写新代码。