0


Spring 事务设计与实现

前言

很多人都知道 Spring 包含声明式与编程式两种事务管理方式,通常来说这已经足够日常使用 Spring 事务了。然而,要掌握一门技术,我们还要关心这门技术出现的背景,解决了什么问题,Spring 的事务设计也有其历史原因。

最近回顾 Spring 事务相关知识,发现它的设计还是包含不少内容的,分享给大家,也便于大家更容易理解与掌握 Spring 事务。

Spring 事务管理设计目的

事务是数据库中的基础概念,早在 Spring 诞生之前就已经出现。如果你对事务的概念有所遗忘,可以参考《数据库事务基础知识》 了解更多。

Java 为了操作数据库,提出过 3 个规范,这三个规范都对事务进行了支持。

  1. JDBC:即 Java Database Connectivity,定义了访问数据库的基本操作。参见《Java 基础知识之 JDBC》了解更多。
  2. JTA:即 Java Transaction API,支持本地与全局事务。全局事务即分布式事务,主要使用两阶段提交实现。参见《Java 分布式事务规范 JTA 从入门到精通》了解更多。
  3. JPA:即 Java Persistence API,ORM 框架实现的规范,定义了 Java 类到数据库表之间的映射,可以用面向对象的方式操作数据库,而不用写 SQL,典型的实现是 Hibernate。

Spring 框架的目的是简化 Java 的开发工作,因此面对不同的事务 API,Spring 选择将其整合成一套,这样不管底层的实现是哪一套,都能以统一的方式使用,降低了学习成本。

Spring 事务管理设计思路

Java 中的事务可以简单分为本地事务与全局事务。

本地事务使用单个事务资源,资源通常为关系型数据库,不能跨多个事务资源使用,依赖于 JDBC 或 JPA 规范。

全局事务可以使用多个事务资源,资源可以为关系型数据库或消息队列,依赖于 JTA 规范。然而 JTA 规范要求 EJB 容器对事务管理器进行实现,这就意味着全局事务与 EJB 容器进行绑定。

为了同时支持本地事务与全局事务,Spring 事务的设计参考了 JTA 规范,并且消除或降低了全局事务对 EJB 容器的依赖。

这里总结了 Spring 事务管理的一些细节,它们均参考了 JTA,而非 Spring 首创。

1. 编程式事务与声明式事务

编程式事务以 API 的形式操作事务。事务对象由事务管理器进行管理,通过事务管理器或事务对象提交、回滚事务。事务管理器在 JTA 和 Spring 中的类名都是

TransactionManager

,根据使用的 JDBC、JTA、JPA、Hibernate 不同规范或框架,Spring 事务管理器有不同的实现。

JTA 编程式事务伪代码如下:

TransactionManager transactionManager =...;
transactionManager.begin();Transaction transaction = transactionManager.getTransaction();try{...数据库或消息队列操作
    transaction.commit();}catch(Exception e){
    transaction.rollback();}

Spring 编程式事务伪代码如下:

PlatformTransactionManager transactionManager =...;TransactionStatus transaction = transactionManager.getTransaction(TransactionDefinition.withDefaults());try{...数据库或消息队列操作
    transactionManager.commit(transaction);}catch(Exception e){
    transactionManager.rollback(transaction);}

声明式事务在 JTA 中由 EJB 容器实现,主要通过拦截对象方法的执行,在方法执行前后控制事务,并通过

@Transactional

声明事务行为。Spring 声明式事务同样通过 AOP 拦截 bean 方法,并提供了一个同名的

@Transactional

注解声明事务行为。

JTA 声明式事务注解配置示例如下:

@Transactional(value =Transactional.TxType.REQUIRES_NEW,
        rollbackOn =Exception.class,
        dontRollbackOn =RuntimeException.class)publicvoiddoSomething(){}

对应的 Spring 声明式事务注解配置示例如下:

@Transactional(propagation =Propagation.REQUIRES_NEW,
        rollbackFor =Exception.class,
        noRollbackFor =RuntimeException.class)publicvoiddoSomething(){}

2. 事务管理器

JTA 的事务管理器通常由 EJB 容器实现,限制了应用的运行环境。Spring 事务管理器不强制要求应用运行在 EJB 容器,标准环境下使用 JTA 可以引入第三方 JTA 实现,如 Atomikos。两者实现思路基本类似,后文对 Spring 事务管理器实现简单分析。

JTA 事务管理器接口如下:

publicinterfaceTransactionManager{publicTransactiongetTransaction()throwsSystemException;publicvoidcommit()throwsRollbackException,HeuristicMixedException,HeuristicRollbackException,SecurityException,IllegalStateException,SystemException;publicvoidrollback()throwsIllegalStateException,SecurityException,SystemException;... 省略其他事务操作代码
}

Spring 事务管理器接口定义如下:

publicinterfacePlatformTransactionManagerextendsTransactionManager{TransactionStatusgetTransaction(@NullableTransactionDefinition definition)throwsTransactionException;voidcommit(TransactionStatus status)throwsTransactionException;voidrollback(TransactionStatus status)throwsTransactionException;

可以看到,两者都具有获取事务对象、提交、回滚事务的能力。

3. 外部事务

外部事务是暴露给用户,用于在应用中操作事务的接口。在 JTA 中使用

UserTransaction

表示,Spring 提供了一个功能类似的

TransactionStatus

对象。

JTA

UserTransaction

定义如下:

publicinterfaceUserTransaction{voidsetRollbackOnly()throwsIllegalStateException,SystemException;intgetStatus()throwsSystemException;voidbegin()throwsNotSupportedException,SystemException;voidcommit()throwsRollbackException,HeuristicMixedException,HeuristicRollbackException,SecurityException,IllegalStateException,SystemException;voidrollback()throwsIllegalStateException,SecurityException,SystemException;voidsetTransactionTimeout(int seconds)throwsSystemException;}

Spring

TransactionStatus

定义如下:

publicinterfaceTransactionExecution{booleanisNewTransaction();voidsetRollbackOnly();booleanisRollbackOnly();booleanisCompleted();}publicinterfaceTransactionStatusextendsTransactionExecution,SavepointManager,Flushable{booleanhasSavepoint();@Overridevoidflush();}

可以看到,JTA 的外部事务接口

UserTransaction

TransactionManager

存在很多相似的方法,包括提交、回滚事务、设置超时时间等。

Spring 认为事务操作应该由事务管理器来完成,因此 Spring 的外部事务接口

TransactionStatus

只存在一些获取或设置事务状态的方法。

4. 内部事务

内部事务是在事务管理器内部使用的事务接口,不暴露给用户。

JTA 使用

Transaction

表示事务管理器内部使用的事务,其接口定义与

UserTransaction

很相似,不再赘述。

对于 Spring 来说,由于它主要用来整合 JDBC、JTA、JPA、Hibernate,不同的规范或者实现事务有所不同,没有固定的类型来表示内部事务,对于 JDBC 来说事务对象是

DataSourceTransactionObject

,对于 JTA 事务对象则是

JtaTransactionObject

5. 事务关联资源

这里的事务为内部事务,与资源进行关联的目的是为了支持分布式事务,这样当全局事务提交时,所有的资源对应的事务分支都会提交。

JTA 使用

Transaction.enlistResource

Transaction.delistResource

方法关联和取消关联资源。

对于 Spring 来说,由于内部事务不固定,因此没有固定的方法,不过基于 JDBC、JPA、Hibernate 的事务对象都继承了

JdbcTransactionObjectSupport

类,这个类提供了

setConnectionHolder

方法设置连接对象。

publicabstractclassJdbcTransactionObjectSupportimplementsSavepointManager,SmartTransactionObject{// Connection 持有者@NullableprivateConnectionHolder connectionHolder;publicvoidsetConnectionHolder(@NullableConnectionHolder connectionHolder){this.connectionHolder = connectionHolder;}}

Spring 事务管理器获取内部事务时将触发事务与资源的关联。

6. 事务传播行为

事务传播行为是指声明式事务中,在一个事务方法中调用另一个事务方法,另一个事务方法的事务行为,加入现有事务、使用新的事务还是其他的行为。它们可以在

@Tranactional

注解中配置。

JTA 定义了 6 种事务传播行为,它们定义在 TxType 枚举类中,具体如下。

publicenumTxType{
    REQUIRED,
    REQUIRES_NEW,
    MANDATORY,
    SUPPORTS,
    NOT_SUPPORTED,
    NEVER;}

Spring 除了支持 JTA 定义的事务传播行为,还添加了支持嵌套事务的

NESTED

publicenumPropagation{REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),NEVER(TransactionDefinition.PROPAGATION_NEVER),NESTED(TransactionDefinition.PROPAGATION_NESTED);}

关于 Spring 事务传播行为,如果你想了解更多,也可以参考《那些年面试官问过我的 Spring 事务传播行为》。

7. 事务回滚异常

声明式事务有两种方式控制事务回滚。

第一种是通过 API 的方式设置事务仅回滚,JTA 可以调用

UserTransaction.setRollbackOnly

方法,Spring 提供了相同功能的

TransactionStatus.setRollbackOnly

方法。

第二种是通过抛出特定的异常来回滚事务。默认情况下,JTA 和 Spring 都会在方法抛出

RuntimeException

Error

异常时才回滚异常。同时 JTA 和 Spring 都支持配置

@Transactional

属性来指定回滚的异常。

JTA

@Transactional

事务回滚属性配置如下:

public@interfaceTransactional{publicClass[]rollbackOn()default{};publicClass[]dontRollbackOn()default{};}

JTA

@Transactional

事务回滚属性配置如下:

public@interfaceTransactional{Class<?extendsThrowable>[]rollbackFor()default{};String[]rollbackForClassName()default{};Class<?extendsThrowable>[]noRollbackFor()default{};String[]noRollbackForClassName()default{};}

Spring 事务使用方式

目前,主要在 Spring Boot 环境下使用声明式的方式使用 Spring 事务。

首先引入

spring-boot-starter-jdbc

即可开启声明式事务。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId><version>2.2.7.RELEASE</version></dependency>

然后在 bean 方法或类上添加

@Transactional

注解即可,示例如下。

@ServicepublicclassSpringService{@Transactional(rollbackFor =Exception.class)publicvoiddoSomething(){}}

更详细的 Spring 事务使用与实现方式,可以参考《如何正确打开 Spring 事务?》。

Spring 事务与 ORM 框架整合

Spring 支持第三方 ORM 框架集成 Spring 事务,以便以一致的方式使用 Spring 事务,降低用户学习成本。

资源与事务同步

第三方 ORM 框架的事务资源由框架自身控制,为了将 ORM 框架管理的资源交给 Spring 内部事务管理,Spring 提供了一些获取资源的方法,这些方法交由第三方框架直接调用即可,具体如下。

  • JDBC:DataSourceUtils.getConnection
  • JPA:EntityManagerFactoryUtils.getTransactionalEntityManager
  • HIbernate:SessionFactoryUtils.getDataSource

如果当前事务已经和资源建立同步关系,则这些方法直接返回资源实例,否则会创建新的资源对象并和事务建立关系。

如果你对 MyBatis 与 Spring 事务整合的原理感兴趣,可以参考 《MyBatis 与 Spring 整合原理分析》。

异常转换

Spring 提供了两个异常转换器,可以将事务操作时产生的异常统一转换为

DataAccessException

publicinterfaceSQLExceptionTranslator{DataAccessExceptiontranslate(String task,@NullableString sql,SQLException ex);}publicinterfacePersistenceExceptionTranslator{DataAccessExceptiontranslateExceptionIfPossible(RuntimeException ex);}

前者

SQLExceptionTranslator

可以由 ORM 框架执行 SQL 时直接调用,后者

PersistenceExceptionTranslator

则由 ORM 框架实现,在其内部可以复用前者。MyBatis 使用的就是后者。示例如下:

publicclassCustomExceptionTranslatorimplementsPersistenceExceptionTranslator{privateSQLExceptionTranslator exceptionTranslator =newSQLErrorCodeSQLExceptionTranslator();@OverridepublicDataAccessExceptiontranslateExceptionIfPossible(RuntimeException ex){if(ex instanceofCustomException){if(ex.getCause()instanceofSQLException){return exceptionTranslator.translate(ex.getMessage(),null,(SQLException) ex.getCause());}else{returnnewUncategorizedDataAccessException(ex.getMessage(), e);}}returnnull;}}

为了使用 ORM 框架实现的

PersistenceExceptionTranslator

,还需要将其声明为 bean,并添加

PersistenceExceptionTranslationPostProcessor

bean,以便转换

@Repository

事务方法中抛出的

RuntimeException

Spring 事务实现浅析

Spring 事务的实现基本上遵照 JTA 规范,只是定义了自己的一套 API。

AbstractPlatformTransactionManager

提供了事务管理的整体流程,包括事务获取、挂起、恢复、提交、回滚等,重点在于事务获取时的事务传播行为管理,可以参见《Spring 事务传播行为》。

它主要使用了模板方法设计模式,并预留了一些方法供子类实现。由于其内容过于复杂,限于篇幅这里不对其一一分析。不过我们重点关注下事务是如何与资源关联的,这由子类进行控制。

DataSourceTransactionManager

为例,首先看其实现的父类获取内部事务对象的方法。

@OverrideprotectedObjectdoGetTransaction(){DataSourceTransactionObject txObject =newDataSourceTransactionObject();
        txObject.setSavepointAllowed(isNestedTransactionAllowed());// 从线程上下文中获取 ConnectionHolder,同一个 DataSource 只会对应一个 ConnectionHolderConnectionHolder conHolder =(ConnectionHolder)TransactionSynchronizationManager.getResource(obtainDataSource());
        txObject.setConnectionHolder(conHolder,false);return txObject;}

这里调用了

TransactionSynchronizationManager.getResource

方法获取

ConnectionHolder

,然后将其设置到了事务对象

DataSourceTransactionObject

中。

TransactionSynchronizationManager

还有一个对应的

bindResource

,刚好一个获取,一个设置,最终会存在线程上下文

ThreadLocal

中。

publicabstractclassTransactionSynchronizationManager{privatestaticfinalThreadLocal<Map<Object,Object>> resources =newNamedThreadLocal<>("Transactional resources");}

这样设计有何优势呢?还记得前面讲的 ORM 框架整合部分吗?ORM 框架调用的

DataSourceUtils.getConnection

方法内部就使用了

bindResource

方法将资源设置到线程上下文,然后 Spring 事务管理器就可以获取了,从而将 Spring 内部的事务与 ORM 框架管理的资源进行关联。

不过首次进入

@Transactional

方法时,ORM 框架可能还未将资源设置到线程上下文,这时事务如何与资源关联呢?

 DataSourceTransactionManager

还关联了一个

DataSource

,当开启事务时,事务管理器会再次判断是否关联资源,如果未关联资源则从

DataSoruce

获取

Connection

并设置到线程上下文,相关代码如下。

protectedvoiddoBegin(Object transaction,TransactionDefinition definition){DataSourceTransactionObject txObject =(DataSourceTransactionObject) transaction;Connection con =null;if(!txObject.hasConnectionHolder()||
                    txObject.getConnectionHolder().isSynchronizedWithTransaction()){Connection newCon =obtainDataSource().getConnection();}// 初始化 ConnectionHolder
                txObject.setConnectionHolder(newConnectionHolder(newCon),true);}... 省略部分代码
            
            if(txObject.isNewConnectionHolder()){// 资源设置到线程上下文TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());}}

由于

DataSourceTransactionManager

和 ORM 框架都从

DataSource

获取

Connection

,因此需要保证依赖的

DataSource

为同一个,这样 ORM 框架才能加入 Spring 事务。

标签: spring java transaction

本文转载自: https://blog.csdn.net/zzuhkp/article/details/124779246
版权归原作者 大鹏cool 所有, 如有侵权,请联系我们删除。

“Spring 事务设计与实现”的评论:

还没有评论