Spring事务的实现

in Spring原理 with 0 comment

Spring自己就有一套它对事务实现的方式。

Spring事务隔离级别

MySQL在事务的ACID中,隔离性规定了四个隔离级别。在Spring中也规定了对事务隔离性的隔离级别,一共有五个级别。

隔离级别作用
ISOLATION_DEFAULT使用后端数据库默认的隔离级别(Spring默认隔离级别)
ISOLATION_READ_UNCOMMITTED未提交读
ISOLATION_READ_COMMITTED已提交读
ISOLATION_REPEATABLE_READ可重复读
ISOLATION_SERIALIZABLE串行化

很好记,就多了一个使用当前数据库的隔离级别。其他都一样。

Spring事务传播机制

在Java代码中的事务操作,与直接数据库操作事务是完全不同的。

数据库的操作事务,是针对于不同的单个事务。
Java业务代码中,是对于某个方法进行事务操作。可能一个业务操作需要调用多个方法,而且多个方法他们又在不同的事务中。

比如一个事务方法里面调用了另外一个事务方法
那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。

这个时候,就需要事务的传播机制来控制。
Spring一共定义了七个传播机制。

传播机制作用
PROPAGATION_REQUIREDSpring默认的传播机制,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行
PROPAGATION_REQUES_NEW该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可
PROPAGATION_SUPPORT如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务
PROPAGATION_NOT_SUPPORT该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码
PROPAGATION_NEVER该传播机制不支持外层事务,即如果外层有事务就抛出异常
PROPAGATION_MANDATORY与NEVER相反,如果外层没有事务,则抛出异常
PROPAGATION_NESTED该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。

传播机制主要解决的是一个新的事务应该被启动还是被挂起,或者是一个方法是否应该在事务性上下文中运行。

Spring事务底层实现

在Spring实现事务,主要是通过核心接口PlatformTransactionManager事务管理器。

PlatformTransactionManager接口

PlatformTransactionManager是一个事务管理器
该接口有如下代码:

// 根据事务定义TransactionDefinition,获取事务
TransactionStatus getTransaction(TransactionDefinition definition);
// 提交事务
void commit(TransactionStatus status);
// 回滚事务
void rollback(TransactionStatus status);

出新了两个新类,分别是TransactionDefinitionTransactionStatus

TransactionDefinition

只要在Spring源码中看见xxxDefinition就是定义了某些东西。TransactionDefinition也是一样。
TransactionDefinition定义了,事务的一些基础信息,如超时时间、隔离级别、传播属性等
源码如下。

public interface TransactionDefinition {
	// 以下是定义了事务的传播机制
	// Spring默认的传播机制,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。
	// 如果外层没有事务,新建一个事务执行
	int PROPAGATION_REQUIRED = 0;

	// 如果外层有事务,则加入外层事务,
	// 如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务
	int PROPAGATION_SUPPORTS = 1;
	
	// 如果外层没有事务,则抛出异常
	int PROPAGATION_MANDATORY = 2;
	
	// 该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,
	// 当当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可
	int PROPAGATION_REQUIRES_NEW = 3;
	
	// 该传播机制不支持事务,
	// 如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码
	int PROPAGATION_NOT_SUPPORTED = 4;

	// 该传播机制不支持外层事务,即如果外层有事务就抛出异常
	int PROPAGATION_NEVER = 5;

	// 该传播机制的特点是可以保存状态保存点,
	// 当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,
	// 如果子事务没有把异常吃掉,基本还是会引起全部回滚的。
	int PROPAGATION_NESTED = 6;

	// 以下是定义了事务的隔离级别
	// 默认使用当前数据库的隔离级别
	int ISOLATION_DEFAULT = -1;

	int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
	int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
	int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
	int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

	// 定义超时时间
	int TIMEOUT_DEFAULT = -1;
	// 获得传播机制
	int getPropagationBehavior();
	// 获得隔离级别
	int getIsolationLevel();
	// 获得超时时间
	int getTimeout();
	// 是否只读
	boolean isReadOnly();

在设置事务定义有个实现类DefaultTransactionDefinition,在它的源码中可以看见事务的一些默认设置。

// 默认传播机制PROPAGATION_REQUIRED
private int propagationBehavior = PROPAGATION_REQUIRED;
// 默认隔离级别
private int isolationLevel = ISOLATION_DEFAULT;
// 默认超时时间
private int timeout = TIMEOUT_DEFAULT;
// 默认是否只读
private boolean readOnly = false;

事务超时

为了使一个应用程序很好地执行,它的事务不能运行太长时间。

假设事务的运行时间变得格外的长,由于事务可能涉及对数据库的锁定,所以长时间运行的事务会不必要地占用数据库资源。这时就可以声明一个事务在特定秒数后自动回滚,不必等它自己结束。

由于超时时钟在一个事务启动的时候开始的,因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、ROPAGATION_NESTED)的方法来说,声明事务超时才有意义。

只读

如果一个事务只对数据库执行读操作,那么该数据库就可能利用那个事务的只读特性,采取某些优化措施。
由于只读的优化措施是在一个事务启动时由后端数据库实施的, 因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、 ROPAGATION_NESTED)的方法来说,将事务声明为只读才有意义。

TransactionStatus

TransactionStatus是事务的一些状态信息,如是否一个新的事务、是否已被标记为回滚。

public interface TransactionStatus extends SavepointManager, Flushable {
	// 是否是一个新的事务
	boolean isNewTransaction();
	// 是否有保存点
	boolean hasSavepoint();
	// 是否标记为回滚
	boolean isRollbackOnly();
	// 是否已完成
	boolean isCompleted();

可以看见TransactionStatus接口还继承了SavepointManager接口

SavepointManager

SavepointManager是对事务中上述保存点功能的封装。
public interface SavepointManager {
// 创建一个保存点
Object createSavepoint() throws TransactionException;
// 回滚到某个保存点
void rollbackToSavepoint(Object savepoint) throws TransactionException;
// 释放某个保存点
void releaseSavepoint(Object savepoint) throws TransactionException;
}


对于Spring事务的底层实现,简单来说,就是下面这张图。
image.png

Spring实现事务

Spring支持编程式事务管理以及声明式事务管理两种方式。

编程式事务管理

编程式事务管理是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,Spring推荐使用TransactionTemplate。

对于编程式事务是通过手动编程方式来实现事务操作。开发人员来管理事务的提交和回滚,但也可能是Spring自己来管理事务,如Spring的TransactionTemplate。

编程式事务案例

如以下

TransactionTemplate template=new TransactionTemplate();
template.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
template.setTransactionManager(transactionManager);
template.execute(new TransactionCallback<User>() {

	@Override
	public User doInTransaction(TransactionStatus status) {
		jdbcTemplate.update(sql2);
		hibernateTemplate.save(user1)
		return null;
	}
	
});

1.TransactionTemplate继承了DefaultTransactionDefinition,有了默认的事务定义,也可以自定义设置隔离级别、传播属性等

2.TransactionTemplate需要一个PlatformTransactionManager事务管理器,来执行事务的操作

3.TransactionTemplate在TransactionCallback中执行业务代码,try catch的事务模板代码,则被封装起来,包裹在业务代码的周围

Template.execute()源码

public <T> T execute(TransactionCallback<T> action) throws TransactionException {
		Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");

		if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
			return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
		}
		else {
			// 根据事务定义获取事务
			TransactionStatus status = this.transactionManager.getTransaction(this);
			T result;
			try {
				// 执行业务代码
				result = action.doInTransaction(status);
			}
			// 如果业务代码出现异常,则回滚事务,没有异常则提交事务
			catch (RuntimeException | Error ex) {
				// Transactional code threw application exception -> rollback
				rollbackOnException(status, ex);
				throw ex;
			}
			// 如果业务代码出现异常,则回滚事务,没有异常则提交事务
			catch (Throwable ex) {
				// Transactional code threw unexpected exception -> rollback
				rollbackOnException(status, ex);
				throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
			}
			// 提交事务
			this.transactionManager.commit(status);
			return result;
		}
	}

注释很清楚,代码也很简单,就不重复解释一遍了。

声明式事务管理(常用)

声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。

声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。
唯一不足的地方就是声明式事务管理的粒度是方法级别,
而编程式事务管理是可以到代码块的,
但是可以通过提取方法的方式完成声明式事务管理的配置。