Spring 事务设计与实现

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

前言


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


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


Spring 事务管理设计目的


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


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


JDBC:即 Java Database Connectivity,定义了访问数据库的基本操作。参见《Java 基础知识之 JDBC》了解更多。

JTA:即 Java Transaction API,支持本地与全局事务。全局事务即分布式事务,主要使用两阶段提交实现。参见《Java 分布式事务规范 JTA 从入门到精通》了解更多。

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)
public void doSomething() {
}


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


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


2. 事务管理器


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


JTA 事务管理器接口如下:


public interface TransactionManager {
    public Transaction getTransaction() throws SystemException;
    public void commit() throws RollbackException,HeuristicMixedException,HeuristicRollbackException, SecurityException,IllegalStateException, SystemException;
    public void rollback() throws IllegalStateException, SecurityException,SystemException;
    ... 省略其他事务操作代码
}


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


public interface PlatformTransactionManager extends TransactionManager {
  TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
      throws TransactionException;  
  void commit(TransactionStatus status) throws TransactionException;
  void rollback(TransactionStatus status) throws TransactionException;

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


3. 外部事务


外部事务是暴露给用户,用于在应用中操作事务的接口。在 JTA 中使用 UserTransaction 表示,Spring 提供了一个功能类似的 TransactionStatus 对象。


JTA UserTransaction 定义如下:


public interface UserTransaction {
    void setRollbackOnly() throws IllegalStateException, SystemException;
    int getStatus() throws SystemException;
    void begin() throws NotSupportedException, SystemException;
    void commit() throws RollbackException,HeuristicMixedException, HeuristicRollbackException, SecurityException,IllegalStateException, SystemException;
    void rollback() throws IllegalStateException, SecurityException,SystemException;
    void setTransactionTimeout(int seconds) throws SystemException;
}


Spring TransactionStatus 定义如下:


public interface TransactionExecution {
  boolean isNewTransaction();
  void setRollbackOnly();
  boolean isRollbackOnly();
  boolean isCompleted();
}
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
  boolean hasSavepoint();
  @Override
  void flush();
}


可以看到,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 方法设置连接对象。


public abstract class JdbcTransactionObjectSupport implements SavepointManager, SmartTransactionObject {
  // Connection 持有者
  @Nullable
  private ConnectionHolder connectionHolder;
  public void setConnectionHolder(@Nullable ConnectionHolder connectionHolder) {
    this.connectionHolder = connectionHolder;
  }
}


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


6. 事务传播行为


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

注解中配置。


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


public enum TxType {
  REQUIRED,
  REQUIRES_NEW,
  MANDATORY,
  SUPPORTS,
  NOT_SUPPORTED,
  NEVER;
}


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


public enum Propagation {
  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 @interface Transactional {
    public Class[] rollbackOn() default {};
    public Class[] dontRollbackOn() default {};
}


JTA @Transactional 事务回滚属性配置如下:


public @interface Transactional {
  Class<? extends Throwable>[] rollbackFor() default {};
  String[] rollbackForClassName() default {};
  Class<? extends Throwable>[] 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 注解即可,示例如下。


@Service
public class SpringService {
    @Transactional(rollbackFor = Exception.class)
    public void doSomething() {
    }
}


更详细的 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。


public interface SQLExceptionTranslator {
  DataAccessException translate(String task, @Nullable String sql, SQLException ex);
}
public interface PersistenceExceptionTranslator {
  DataAccessException translateExceptionIfPossible(RuntimeException ex);
}


前者 SQLExceptionTranslator 可以由 ORM 框架执行 SQL 时直接调用,后者 PersistenceExceptionTranslator 则由 ORM 框架实现,在其内部可以复用前者。MyBatis 使用的就是后者。示例如下:


public class CustomExceptionTranslator implements PersistenceExceptionTranslator {
    private SQLExceptionTranslator exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator();
    @Override
    public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
        if (ex instanceof CustomException) {
            if (ex.getCause() instanceof SQLException) {
                return exceptionTranslator.translate(ex.getMessage(), null, (SQLException) ex.getCause());
            } else {
                return new UncategorizedDataAccessException(ex.getMessage(), e);
            }
        }
        return null;
    }
}


为了使用 ORM 框架实现的 PersistenceExceptionTranslator,还需要将其声明为 bean,并添加 PersistenceExceptionTranslationPostProcessor bean,以便转换 @Repository 事务方法中抛出的 RuntimeException。


Spring 事务实现浅析


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


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


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


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


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


这里调用了 TransactionSynchronizationManager.getResource 方法获取 ConnectionHolder,然后将其设置到了事务对象 DataSourceTransactionObject 中。TransactionSynchronizationManager 还有一个对应的 bindResource,刚好一个获取,一个设置,最终会存在线程上下文 ThreadLocal 中。


public abstract class TransactionSynchronizationManager {
  private static final ThreadLocal<Map<Object, Object>> resources =
      new NamedThreadLocal<>("Transactional resources");
}


这样设计有何优势呢?还记得前面讲的 ORM 框架整合部分吗?ORM 框架调用的 DataSourceUtils.getConnection 方法内部就使用了 bindResource 方法将资源设置到线程上下文,然后 Spring 事务管理器就可以获取了,从而将 Spring 内部的事务与 ORM 框架管理的资源进行关联。


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


DataSourceTransactionManager 还关联了一个 DataSource,当开启事务时,事务管理器会再次判断是否关联资源,如果未关联资源则从 DataSoruce 获取 Connection 并设置到线程上下文,相关代码如下。


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


由于 DataSourceTransactionManager 和 ORM 框架都从 DataSource 获取 Connection,因此需要保证依赖的 DataSource 为同一个,这样 ORM 框架才能加入 Spring 事务。

目录
相关文章
|
18天前
|
Java 开发者 Spring
Spring高手之路24——事务类型及传播行为实战指南
本篇文章深入探讨了Spring中的事务管理,特别是事务传播行为(如REQUIRES_NEW和NESTED)的应用与区别。通过详实的示例和优化的时序图,全面解析如何在实际项目中使用这些高级事务控制技巧,以提升开发者的Spring事务管理能力。
32 1
Spring高手之路24——事务类型及传播行为实战指南
|
12天前
|
XML Java 数据库连接
Spring中的事务是如何实现的
Spring中的事务管理机制通过一系列强大的功能和灵活的配置选项,为开发者提供了高效且可靠的事务处理手段。无论是通过注解还是AOP配置,Spring都能轻松实现复杂的事务管理需求。掌握这些工具和最佳实践,能
17 3
|
2月前
|
Java 数据库连接 数据库
spring复习05,spring整合mybatis,声明式事务
这篇文章详细介绍了如何在Spring框架中整合MyBatis以及如何配置声明式事务。主要内容包括:在Maven项目中添加依赖、创建实体类和Mapper接口、配置MyBatis核心配置文件和映射文件、配置数据源、创建sqlSessionFactory和sqlSessionTemplate、实现Mapper接口、配置声明式事务以及测试使用。此外,还解释了声明式事务的传播行为、隔离级别、只读提示和事务超时期间等概念。
spring复习05,spring整合mybatis,声明式事务
|
2月前
|
Java 测试技术 数据库
Spring事务传播机制(最全示例)
在使用Spring框架进行开发时,`service`层的方法通常带有事务。本文详细探讨了Spring事务在多个方法间的传播机制,主要包括7种传播类型:`REQUIRED`、`SUPPORTS`、`MANDATORY`、`REQUIRES_NEW`、`NOT_SUPPORTED`、`NEVER` 和 `NESTED`。通过示例代码和数据库插入测试,逐一展示了每种类型的运作方式。例如,`REQUIRED`表示如果当前存在事务则加入该事务,否则创建新事务;`SUPPORTS`表示如果当前存在事务则加入,否则以非事务方式执行;`MANDATORY`表示必须在现有事务中运行,否则抛出异常;
147 4
Spring事务传播机制(最全示例)
|
1月前
|
Java 关系型数据库 MySQL
Spring事务失效,我总结了这7个主要原因
本文详细探讨了Spring事务在日常开发中常见的七个失效原因,包括数据库不支持事务、类不受Spring管理、事务方法非public、异常被捕获、`rollbackFor`属性配置错误、方法内部调用事务方法及事务传播属性使用不当。通过具体示例和源码分析,帮助开发者更好地理解和应用Spring事务机制,避免线上事故。适合所有使用Spring进行业务开发的工程师参考。
32 2
|
1月前
|
Java 程序员 Spring
Spring事务的1道面试题
每次聊起Spring事务,好像很熟悉,又好像很陌生。本篇通过一道面试题和一些实践,来拆解几个Spring事务的常见坑点。
Spring事务的1道面试题
|
2月前
|
Java Spring
Spring 事务传播机制是什么?
Spring 事务传播机制是什么?
23 4
|
1月前
|
监控 Java 数据库
Spring事务中的@Transactional注解剖析
通过上述分析,可以看到 `@Transactional`注解在Spring框架中扮演着关键角色,它简化了事务管理的复杂度,让开发者能够更加专注于业务逻辑本身。合理运用并理解其背后的机制,对于构建稳定、高效的Java企业应用至关重要。
58 0
|
3月前
|
XML Java 数据库
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
这篇文章是Spring5框架的实战教程,详细介绍了事务的概念、ACID特性、事务操作的场景,并通过实际的银行转账示例,演示了Spring框架中声明式事务管理的实现,包括使用注解和XML配置两种方式,以及如何配置事务参数来控制事务的行为。
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
|
3月前
|
前端开发 Java 数据库连接
一天十道Java面试题----第五天(spring的事务传播机制------>mybatis的优缺点)
这篇文章总结了Java面试中的十个问题,包括Spring事务传播机制、Spring事务失效条件、Bean自动装配方式、Spring、Spring MVC和Spring Boot的区别、Spring MVC的工作流程和主要组件、Spring Boot的自动配置原理和Starter概念、嵌入式服务器的使用原因,以及MyBatis的优缺点。
下一篇
无影云桌面