栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring 事务设计与实现

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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)
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[] rollbackFor() default {};
	String[] rollbackForClassName() default {};

	Class[] noRollbackFor() default {};
	String[] noRollbackForClassName() default {};
}
Spring 事务使用方式

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

首先引入 spring-boot-starter-jdbc 即可开启声明式事务。


    org.springframework.boot
    spring-boot-starter-jdbc
    2.2.7.RELEASE

然后在 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> 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 事务。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/888765.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号