1. Spring 事务简介
Spring 本身并不实现事务,Spring事务 的本质 还是 底层数据库 对事务的支持,没有 数据库 事务的支持,Spring事务就不会生效。
Spring 事务 提供一套抽象的事务管理,并且结合 Spring IOC 和 Spring AOP,简化了应用程序使用数据库事务,通过声明式事务,可以做到对应用程序无侵入的实现事务功能。例如 使用JDBC 操作数据库,想要使用事务的步骤为:
1、获取连接 Connection con = DriverManager.getConnection()
2、开启事务con.setAutoCommit(true/false);
3、执行CRUD
4、提交事务/回滚事务 con.commit() / con.rollback();
5、关闭连接 conn.close();
采用Spring 事务后,只需要 关注第3步的实现,其他的步骤 都是Spring 完成。
Spring事务的本质 其实就是 AOP 和 数据库事务,Spring 将数据库的事务操作提取为 切面,通过AOP的方式 增强 事务方法。
2. 事务传播行为
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
3. 使用Spring事务
3.1. 通过 PlatformTransactionManager使用(不推荐)
@Test
public void test12(){
// 默认的事务定义
DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
//开启事务。
TransactionStatus transaction = transactionManager.getTransaction(defaultTransactionDefinition);
try {
Account account = new Account();
account.setUserName("wokalakala");
List accountList = accountMapper.queryAccountList(account);
}catch (Exception e){
//事务回滚
transactionManager.rollback(transaction);
}
//事务提交
transactionManager.commit(transaction);
}
3.2. 通过TransactionTemplate 使用事务
@Test
public void testTransactionTemplate(){
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
List accountList = transactionTemplate.execute(status -> {
Account account = new Account();
account.setUserName("wokalakala");
return accountMapper.queryAccountList(account);
});
}
3.3. 声明式事务
我们经常使用的 方式,通过AOP 实现,对应用程序 侵入较少,采用注解的方式比较简单方便,省去XML 繁琐的配置。
4. 组件介绍
4.1事务管理 PlatformTransactionManager
PlatformTransactionManager 是 Spring 事务结构中的核心接口,Spring并不直接管理事务,而是提供了多种事务管器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现.Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。此接口的内容如下:
Public interface PlatformTransactionManager(){
// 由TransactionDefinition得到TransactionStatus对象
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// 提交
Void commit(TransactionStatus status) throws TransactionException;
// 回滚
Void rollback(TransactionStatus status) throws TransactionException;
}
通过PlatformTransactionManager的接口 可以看出,Spring 事务的 的三个核心方法,事务开启 ,提交事务,回滚事务,Spring 事务功能的实现 都是围绕这三个方法来实现。
4.2 TransactionInfo
事务信息对象,包括一个事务所有的信息,持有 事务管理器,事务定义对象,目标方法唯一标识,事务状态对象,外层的TransactionInfo,外层的TransactionInfo 用于在应用程序中获取 当前的TransactionInfo 对象。
4.3 TransactionStatus
表示一个事务状态,在应用程序中可以通过 TransactionInterceptor.currentTransactionStatus() 的静态函数获取到。
- 持有事务对象 ,使用JDBC 事务时 ,事务对象为 DataSourceTransactionObject,
- 对保存点的支持,可以在应用程序中 通过 设置保存点 ,在事务回滚时,回滚到 保存点,而不是回滚部分。
4.4 TransactionDefinition
事务定义对象,封装了@Transactional 注解中设置的各种信息,通过收集@Transactional属性信息,获取到 一个 TransationalDefinition ,有了事务定义信息 就可以通过PlatformTransactionManager开启事务 或者 复用一个事务了。
4.5 TransationSynchronization 事务同步回调接口
事务同步回调接口,在事务 周期的各个点 执行回调 方法。比如 挂起 ,继续,提交前后 ,完成前后 。用于 管理 应用程序在事务周期中绑定的资源。在Spring - Mybatis 整合时,正式Mybatis 正式利用了TransationSynchronization同步器,才让Mybatis 的事务管理交给了 Spring 事务来管理。
4.6 TransactionSynchronizationManager 事务同步回调的管理器
在事务运行过程中,需要保存一些状态,比如 数据库连接,
ThreadLocal
4.7 SuspendedResourceHolder 挂起的资源持有对象
在挂起一个事务时,用于记录被挂起事务的运行时信息,这些信息就是TransactionSynchronizationManager中记录的事务信息。然后将这些信息 保存在 新的DefaultTransactionStatus对象中,便于内部事务运行结束后,恢复外层事务。
5. Spring 事务实现
Spring事务 把 整个事务流程 模板化,采用AOP的形式 增强到 需要事务 的方法,所以 按照 AOP 的 实现 一定存在 一个事务的增强器,这个增强器就是 BeanFactoryTransactionAttributeSourceAdvisor,该增强器中有个环绕通知TransactionInterceptor,所有的事务逻辑 都在这个类的Invoke 方法中,分析Spring 事务实现就从这个函数开始。
- invoke
//invocation 维护了 AOP 拦截器链 ,执行 invocation.prcess 方法 会沿着拦截器链执行下去直到目标方法。
public Object invoke(final MethodInvocation invocation) throws Throwable {
//获取目标对象
Class> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// 执行 父类 TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
@Override
public Object proceedWithInvocation() throws Throwable {
//继续一下执行下拦截器 也可能式目标方法
return invocation.proceed();
}
});
}
- TransactionAspectSupport's invokeWithinTransaction
protected Object invokeWithinTransaction(Method method, Class> targetClass, final InvocationCallback invocation)
throws Throwable {
1. 准备事务的基本信息
// If the transaction attribute is null, the method is non-transactional.
//事务定义 TransactionAttribute 是 TransationDefinition 的子类
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
//获取 事务管理器 ,这里是一个策略模式,根据 事务定义 指定的 事务管理器 获取到 指定的 事务管理器。
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
//连接点 标识
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
2. 开启事务
//如果必要 才会开启事务,这里会根据 事务的传播行为 信息 来决定是否开启事务 还是 加入一个已经存在的事务。这里会涉及到事务的挂起
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
执行目标方法或者 执行AOP 拦截器链中的下一个拦截器。
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
3. 事务的回滚
//是否回滚 会根据 rollback 属性 判断
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
清理事务信息
cleanupTransactionInfo(txInfo);
}
4. 提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
}
//省略部代码
5.1 准备事务
准备事务 主要是做了两件事情
- 收集@Transactional注解属性信息,生成 事务定义对象。
由于@Transactional可以作用在类上 又可以作用在方法上,所以在收集属性信息的时候,就考虑到这种情况。
AnnotationTransactionAttributeSource 类就是用来解析类和方法上面的@Transactional 注解属性。
那么到底 先解析类上面的 还是 先解析 方法上面的注解呢?如果方法上面 和 类上面 同时存在呢,是完整替换? 还是取并集?
定义解析@Transactional 注解的 逻辑定义在 其父类AbstractFallbackTransactionAttributeSource的computeTransactionAttribute,通过查看代码 便可以一目了然:
protected TransactionAttribute computeTransactionAttribute(Method method, Class> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// Ignore CGLIB subclasses - introspect the actual user class.
Class> userClass = ClassUtils.getUserClass(targetClass);
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);
// If we are dealing with method with generic parameters, find the original method.
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
//首先 解析方法上面 的 属性信息
// First try is the method in the target class.
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
//如果方法上面存在 就返回。
if (txAttr != null) {
return txAttr;
}
//其次 解析作用在类上面的注解属性信息,如果找到 就返回。
// Second try is the transaction attribute on the target class.
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
//解析接口方法上面的注解属性信息 ,如果找到返回。
if (specificMethod != method) {
// Fallback is to look at the original method.
txAttr = findTransactionAttribute(method);
if (txAttr != null) {
return txAttr;
}
//最后 解析接口上面的注解信息。
// Last fallback is the class of the original method.
txAttr = findTransactionAttribute(method.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
}
return null;
}
通过上面的代码可以看出来,@Transactional 注解定义在不同位置的 优先级
为 ,实列方法,实列类,接口方法,接口类。不会取并集 ,也不会覆盖,按照优先级查找,直到找到为止。
- 用到了缓存
虽然解析注解属性不是那么的耗时,但是也不能每次执行事务方法都要解析一次注解属性,所以在解析注解的时候Spring采用了缓存,这样就只需要一次解析注解,而后的每次执行都会存缓存中获取。这是一个典型的拿空间换时间的列子。采用缓存的代码在其父类 AbstractFallbackTransactionAttributeSource 的getTransactionAttribute函数。
在使用缓存的时候 难免遇到缓存穿透的现象,就是用key 获取缓存的时候 没有 获取到对象,然后就要去解析@Transactional ,结果发现 还是没有,此后的每次调用都会持续这个现象,所以Spring 在发现 不存在的时候 就会定义一个特殊的 value 放到缓存中,以标识 这个已经解析过了,确实不存在。
- 解析注解的时机
解析的时机 是在IOC 第一次初始化 Bean的时候,具体点就是 在为目标对象匹配增强器的时候,会触发解析注解。
- 获取事务管理器
如果使用@Transactional 指定了使用哪个事务管理器 ,就会获取响应的事务管理器。如果没有 就从IOC 容器中获取。
5.2 开启事务
收集到了事务定义信息,和 事务管理器之后,就可以 利用PlatformTransactionManager.getTransactional 开启事务了,但是开启事务,有很多情况需要考虑,比如繁多的事务传播行为,比如是否已经存在事务,不同的条件都会影响是否要开启一个新事务。有的传播行为 还会设计到 挂起 已经存在的事务。也是相当复杂的。
protected TransactionInfo createTransactionIfNecessary(
PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
// 采用 委托的方式 包装 事务定义 对象.
if (txAttr != null && txAttr.getName() == null) {
txAttr = new DelegatingTransactionAttribute(txAttr) {
@Override
public String getName() {
return joinpointIdentification;
}
};
}
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
//调用 事务管理器开启事务。
status = tm.getTransaction(txAttr);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
"] because no transaction manager has been configured");
}
}
}
//封装成事务信息对象。
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
AbstractPlatformTransactionManager. getTransaction
这是一个模版方法 ,提供了两个抽象方法 供 子类实现。
该方法主要逻辑
- 判断当前线程是否存在事务
- 存在事务 根据 事务传播行为 创建事务 或 加入当前事务 或 抛出不支持异常
- 不存在事务 判断 传播行为是否 为 TransactionDefinition.PROPAGATION_MANDATORY ,如果是抛出异常
- PROPAGATION_REQUIRED,PROPAGATION_REQUIRED_NEW ,TransactionDefinition.PROPAGATION_NESTED创建事务
- 不运行在事务中,创建空事务。
public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
//获取事务,从线程绑定的信息中获取 事务,抽象方法 留给子类实现。
Object transaction = doGetTransaction();
// Cache debug flag to avoid repeated checks.
boolean debugEnabled = logger.isDebugEnabled();
if (definition == null) {
// Use defaults if no transaction definition given.
definition = new DefaultTransactionDefinition();
}
// 判断是否 已经存在事务
if (isExistingTransaction(transaction)) {
//已经存在事务 根据传播行为 创建事务 或者 加入事务
return handleExistingTransaction(definition, transaction, debugEnabled);
}
// 检查超时时间 的设置 是否合法
if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
}
//如果传播行为 强制 PROPAGATION_MANDATORY
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
//开启新事务
else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
}
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
//抽象方法 开启事务。留给子类实现。
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException ex) {
resume(null, suspendedResources);
throw ex;
}
catch (Error err) {
resume(null, suspendedResources);
throw err;
}
}
else {
// Create "empty" transaction: no actual transaction, but potentially synchronization.
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + definition);
}
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
}
}
DataSourceTransactionManager.doBegin
这里实际获取数据库 连接 开启事务的地方,从DataSource 中获取连接,并且设置 自动提交为false ,
- 获取 数据库连接
- 设置 数据库连接自动提交 为 false,开启事务
- 绑定数据库连接到 线程
/**
* This implementation sets the isolation level but ignores the timeout.
*/
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
//如果当前不存在数据库
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
//获取数据库连接,如果采用数据库连接池 这里就是连接池对象。
Connection newCon = this.dataSource.getConnection();
//设置连接到 事务对象中。
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
//记录上一个 事务的隔离级别 ,如果没有外层事务 ,隔离级别就是null
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
//设置自动提交 为false,如果使用 连接池,连接池 或许已经设置自动提交为false了,所以这里先判断一下。
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
//如果事务是只读事务 ,那么就会执行 SQL "SET TRANSACTION READ ONLY".
prepareTransactionalConnection(con, definition);
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// 如果是一个新的连接 ,绑定数据库连接到当前线程
if (txObject.isNewConnectionHolder()) {
//调用 事务同步回调管理器 的 绑定资源方法,key= dataSource ,key = ConnectionHodler
TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
//异常之后 释放连接,
DataSourceUtils.releaseConnection(con, this.dataSource);
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
事务挂起
当线程中已经存在事务,在某些事务传播行为下就需要挂起外层事务,
比如PROPAGATION_NOT_SUPPORTED:不能运行在一个事务中,如果存在事务就挂起当前事务,执行。
PROPAGATION_REQUIRES_NEW: 必须运行在一个新事务中,如果当前存在事务,则挂起当前事务,开启新事务执行。
如何实现挂起一个事务呢?
挂起事务需要完成几项工作:
1.TransactionSynchronizationManager 中解除绑定的 TransactionSynchronization 集合
2.重置当前事务名称绑定
3.重置事务只读属性绑定
4.重置事务隔离级别绑定
5.重置实际事务激活标志绑定
6.记录以上几部的数据,封装到 SuspendedResourceHolder对象中。
7.将SuspendedResourceHolder对象,交给内部事务 ,以便内部事务执行结束后,恢复外层事务。
事务恢复
如果内部事务出现异常或者 内部事务提交 都会触发外层事务的恢复,事务的恢复就是将内存事务TransactionStauts 中记录的挂起事务的信息,重新绑定到 TransactionSynchronizationManager中去。
5.3 事务回滚
如果事务运行过程中出现某些异常 会导致事务回滚,在JDBC中 我们执行connection.rollback()回滚事务,Spring事务也不列外,只是Spring 事务 在JDBC 的基础之上提供了更多丰富的功能,比如 指定某些异常进行回滚。
关于事务回滚rollback 设置,还有一个容易被忽视 和 误解的地方。就是如果我们设置rollbackFor = IllegalArgumentException.class 那么事务运行期间出现了IndexOutOfBoundsException异常会不会导致事务回滚?出现了 Error 错误会不会回滚?
处理事务回滚的在TransactionAspectSupport . completeTransactionAfterThrowing 函数中
- 首先判断 异常是否需要回滚。判断逻辑 最终是委托给 RuleBasedTransactionAttribute.rollbackOn
public boolean rollbackOn(Throwable ex) {
RollbackRuleAttribute winner = null;
int deepest = Integer.MAX_VALUE;
if (this.rollbackRules != null) {
//遍历 查找 指定的 rollbackException 进行匹配
for (RollbackRuleAttribute rule : this.rollbackRules) {
int depth = rule.getDepth(ex);
if (depth >= 0 && depth < deepest) {
deepest = depth;
winner = rule;
}
}
}
// 如果没有匹配到 采用默认的回滚规则进行判断。
//默认的规则就是 ex instanceof RuntimeException || ex instanceof Error,所以 如果我们指定了rollback = IllegalArgumentException,当遇到 IndexOutOfBoundsException时 或者 Error 时也会回滚事务。
if (winner == null) {
logger.trace("No relevant rollback rule found: applying default rules");
return super.rollbackOn(ex);
}
return !(winner instanceof NoRollbackRuleAttribute);
}
如果需要回滚则会执行 AbstranctPlatformTransactionManager.processRollback函数
2.1 if (status.hasSavepoint())
如果存在保存点 则回滚到 保存点
2.2 else if (status.isNewTransaction())
如果 是一个新事务 执行回滚。
2.3 . else if (status.hasTransaction())
如果 是嵌套事务 设置 当前数据库链接 rollbackOnly如果不需要回滚 则提交事务
触发钩子函数
在回滚前后 会分别触发 TransactionSynchronuzation 的beforeCompletion,afterCompletion 函数,进行资源释放,连接关闭等。
5.4 事务提交
只有当事务是一个 新事务的时候才会进行提交,就是如果 有一个 内嵌事务传播 行为 PROPAGATION_SUPPORTS ,PROPAGATION_REQUIRED ,PROPAGATION_MANDATORY 的事务 执行完之后 不会 提交,会随着外层事务的提交而提交。
事务的提交 最终 是调用 connect.commit 函数提交事务。
在事务提交前后 会触发TransactionSynchronuzation 钩子函数。进行资源释放操作。
mybatis 会在beforeCommit 中 执行Sqlsession commit
6 一些细节
- 方法上面 @Transaction 注解 会覆盖 类上面的 @Transaction注解信息。是完全的覆盖,而不是 部分覆盖,就是说 ,如果类上设置了 事务超时时间 为 10秒,但是 方法上面没有设置事务超时时间,那么 最终 事务 就是没有超时时间,并不会 采用 类上面的 超时时间。
事务隔离级别 和 超时时间 只能作用于 一个 新事务,也就是说 ,当内部事务参与到一个已经存在的事务中时,事务隔离级别 和 超时时间将会被忽略。因为 内部事务 是参与到外层事务。
事务rollbackFor 的 含义是 默认异常 或 指定异常,就是说,默认回滚异常时 runtimeException 或 Error 或 自己指定的异常。