事务挂起
对于挂起操作,主要目的是记录原有事务的状态,以便于后续操作对事务的恢复:
实际上,suspend()
方法调用的是事务管理器 DataSourceTransactionManager
中的 doSuspend()
方法
protected Object doSuspend(Object transaction) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; // 将数据库连接设置为 null txObject.setConnectionHolder(null); return TransactionSynchronizationManager.unbindResource(obtainDataSource()); }
最后调用的关键方法是 TransactionSynchronizationManager#doUnbindResource
private static Object doUnbindResource(Object actualKey) { Map<Object, Object> map = resources.get(); if (map == null) { return null; } Object value = map.remove(actualKey); if (map.isEmpty()) { resources.remove(); } if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) { value = null; } if (value != null && logger.isTraceEnabled()) { Thread.currentThread().getName() + "]"); } return value; }
看了第七条参考资料中的文章,结合代码理解了事务挂起的操作:移除当前线程、数据源活动事务对象的一个过程
那它是如何实现事务挂起的呢,答案是在 doSuspend()
方法中的 txObject.setConnectionHolder(null)
,将 connectionHolder
设置为 null
。
一个 connectionHolder
表示一个数据库连接对象,如果它为 null
,表示在下次需要使用时,得从缓存池中获取一个连接,新连接的自动提交是 true
。
PROPAGATION_REQUIRES_NEW
表示当前方法必须在它自己的事务里运行,一个新的事务将被启动,而如果有一个事务正在运行的话,则这个方法运行期间被挂起。
SuspendedResourcesHolder suspendedResources = suspend(transaction); 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 | Error beginEx) { resumeAfterBeginException(transaction, suspendedResources, beginEx); throw beginEx; }
与前一个方法相同的是,在 PROPAGATION_REQUIRES_NEW
广播特性下,也会使用 suspend
方法将原事务挂起。方法 doBegin()
,是事务开启的核心。
PROPAGATION_NESTED
表示如果当前正有一个事务在运行中,则该方法应该运行在一个嵌套的事务中,被嵌套的事务可以独立于封装事务进行提交或者回滚,如果封装事务不存在,行为就像 PROPAGATION_REQUIRES_NEW
。
在代理处理上,有两个分支,与 PROPAGATION_REQUIRES_NEW
相似的不贴出来,讲下使用 savepoint
保存点的方式事务处理:
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { // 嵌入式事务的处理 if (useSavepointForNestedTransaction()) { DefaultTransactionStatus status = prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null); // 创建 savepoint status.createAndHoldSavepoint(); return status; } }
学习过数据库的朋友应该清楚 savepoint
,可以利用保存点回滚部分事务,从而使事务处理更加灵活和精细。跟踪代码,发现创建保存点调用的方法是 org.hsqldb.jdbc.JDBCConnection#setSavepoint(java.lang.String)
,感兴趣的可以往下继续深入学习~
事务创建
其实在前面方法中,都出现过这个方法 doBegin()
,在这个方法中创建事务,顺便设置数据库的隔离级别、timeout
属性和设置 connectionHolder
:
DataSourceTransactionManager#doBegin
protected void doBegin(Object transaction, TransactionDefinition definition) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; Connection con = null; if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { Connection newCon = obtainDataSource().getConnection(); txObject.setConnectionHolder(new ConnectionHolder(newCon), true); } txObject.getConnectionHolder().setSynchronizedWithTransaction(true); con = txObject.getConnectionHolder().getConnection(); // 设置隔离级别 Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); txObject.setPreviousIsolationLevel(previousIsolationLevel); // configured the connection pool to set it already). // 更改自动提交设置,由 spring 进行控制 if (con.getAutoCommit()) { txObject.setMustRestoreAutoCommit(true); con.setAutoCommit(false); } // 准备事务连接 prepareTransactionalConnection(con, definition); // 设置判断当前线程是否存在事务的依据 txObject.getConnectionHolder().setTransactionActive(true); int timeout = determineTimeout(definition); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); } // Bind the connection holder to the thread. if (txObject.isNewConnectionHolder()) { // 将当前获取到的连接绑定到当前线程 TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); } } }
结论:Spring 事务的开启,就是将数据库自动提交属性设置为 false
小结
在声明式的事务处理中,主要有以下几个处理步骤:
- 获取事务的属性:
tas.getTransactionAttribute(method, targetClass)
- 加载配置中配置的 `TransactionManager`:
determineTransactionManager(txAttr);
- 不同的事务处理方式使用不同的逻辑:关于声明式事务和编程式事务,可以查看这篇文章-Spring编程式和声明式事务实例讲解
- 在目标方法执行前获取事务并收集事务信息:
createTransactionIfNecessary(tm, txAttr, joinpointIdentification)
- 执行目标方法:
invocation.proceed()
- 出现异常,尝试异常处理:
completeTransactionAfterThrowing(txInfo, ex);
- 提交事务前的事务信息消除:
cleanupTransactionInfo(txInfo)
- 提交事务:
commitTransactionAfterReturning(txInfo)
事务回滚 & 提交
这两步操作,主要调用了底层数据库连接的 API
,所以没有细说。
总结
本篇文章简单记录了如何使用 Spring
的事务,以及在代码中如何实现。
在之前的使用场景中,只用到了默认配置的声明式事务 @Transactional
,不了解其它属性设置的含义,也不知道在默认配置下,如果是同一个类中的方法自调用是不支持事务。
所以,经过这一次学习和总结,在下一次使用时,就能够知道不同属性设置能解决什么问题,例如修改广播特性 PROPAGATION
,让事务支持方法自调用,还有设置事务超时时间 timeout
、隔离级别等属性。