目录
前言
spring的事务和mybatis有什么关系,如何实现的提交和回滚,隔离级别和传播机制如何发挥作用,这一切的答案都将在本文揭晓,本文主要集中与事务相关的运行流程,关于容器启动过程中的事务相关配置过程没有涉及,以后有时间再说吧。
一、事务执行的总体流程
事务的整体流程表面上看比较简单,首先是创建事务,运行业务代码,当有需要回滚的异常发生时,回滚当前事务,没有异常发生,就提交当前事务,当然,当使用了setRollbackOnly()方法时也会回滚,这个问题再具体分析,接下来我们对每个重点方法进行分析。当事务因为异常回滚之后会再次将异常抛出,外层事务也会因为异常回滚。
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
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.
//创建事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal;
try {
// 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
//当系统发生异常时,拦截器会手动抛出异常,能够被外层事务捕获,造成外层事务回滚,那么doSetRollbackOnly(status)
//的意义何在,此处应是为了兼容手动事务回滚,TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
//手动事务回滚能够在开启新事务的情况下,只会回滚当前事务;在共用旧事务的情况下,会记录回滚标志,在外层方法提交时回滚。
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
//没有异常发生,提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
}
二、创建事务
1.总体流程
创建事务的过程中有两个方法比较重要,分别实现了获取事务和将transactionInfo对象写入本地线程中,接下来分别进行分析。
protected TransactionInfo createTransactionIfNecessary(
PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
//获取事务
status = tm.getTransaction(txAttr);
}
}
//准备transactionInfo对象,并写入本地线程中
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
2.获取事务
这里需要关注一下dobegin和prepareSynchronization两个方法,关于事务传播机制相关的代码比较复杂,会在下一篇文章中进行分析。
public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
Object transaction = doGetTransaction();
/**
* 1、这里是new一个新的DataSourceTransactionObject对象
* 在后面开启事务 doBegin()方法中,会对txObject对象进行填充
*/
// Cache debug flag to avoid repeated checks.
boolean debugEnabled = logger.isDebugEnabled();
//事务传播机制相关代码,下一篇进行分析
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(definition, transaction, debugEnabled);
}
// Check definition settings for new transaction.
if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
}
// No existing transaction found -> check propagation behavior to find out how to proceed.
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);
//Activate transaction synchronization,会影响mybatis对sqlsession的获取
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException ex) {
resume(null, suspendedResources);
throw ex;
}
catch (Error err) {
resume(null, suspendedResources);
throw err;
}
}
}
doBegin方法实现了数据库连接的获取,并将连接写入到本地线程中,同时将连接的隔离级别设置为注解中的隔离级别,事务相关操作大部分都是通过连接实现的。
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();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
//将注解中的隔离级别写入到connection中,从而对数据库查询起作用
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already).
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
//设置手动提交
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.
//将数据库连接写入本地线程,mybatis获取连接时从本地获取,从而保证连接一致
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
}
}
}
二、事务回滚
数据库连在事务回滚的时候分了三种情况,当前事务有停留点时,回滚到停留点;当前事务是新事务时,直接回滚;当前事务是子事务,设置回滚标志。
private void processRollback(DefaultTransactionStatus status) {
try {
try {
//如果有停留点,则回滚到停留点
triggerBeforeCompletion(status);
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
status.rollbackToHeldSavepoint();
}
//如果是新事务,直接回滚
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
doRollback(status);
}
//如果和外层方法共用一个事务,设置回滚标志,在外层回滚
else if (status.hasTransaction()) {
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
doSetRollbackOnly(status);
}
else {
if (status.isDebug()) {
logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
}
}
}
else {
logger.debug("Should roll back transaction but cannot - no transaction available");
}
}
finally {
cleanupAfterCompletion(status);
}
}
事务回滚的真正执行方法是调用connention的回滚方法,所以需要保证同一个事务中的数据库相关操作使用同一个connection,下一篇文章中对此进行分析。
三、提交事务
即使程序过程中没有异常,当设置了事务回滚的时候也会造成回滚,但这种情况下的回滚是不会抛出异常的,也就是内层设置事务回滚不会导致外层事务回滚。事务提交的真正执行方法也是调用connection的提交方法,关键还是保证同一个事务的数据库操作同一个connection。
public final void commit(TransactionStatus status) throws TransactionException {
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
//设置了回滚标志后,执行回滚操作
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
processRollback(defStatus);
return;
}
//根据connectionHolder的属性判断是否回滚
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
processRollback(defStatus, true);
return;
}
//提交事务
processCommit(defStatus);
}
即使当内外层共用同一个事务的时候,如果内层事务回滚,但是没有抛出异常,也就是没有让外层事务感知,即设置了事务持有的ConnectionHolder的rollbackOnly属性为true,由于内外层共用连接,此时外层事务也要回滚,但是会抛异常。
在源码中表现为isGlobalRollbackOnly,即判断当前连接是否需要回滚。
public boolean isGlobalRollbackOnly() {
return ((this.transaction instanceof SmartTransactionObject) &&
((SmartTransactionObject) this.transaction).isRollbackOnly());
}
public boolean isRollbackOnly() {
return getConnectionHolder().isRollbackOnly();
}
同一个事务如果是由与连接的rollbackOnly属性导致事务回滚,会在调用processRollback方法方法中传入属性true,最终导致抛出UnexpectedRollbackException异常。
//当事务持有的连接标记为回滚。却没有发生异常时
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
Exception in thread "main" org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
四、添加事务提交之后的回调方法
本文在事务方法提交之后,可以异步调用一些方法。在事务管理类中有一个synchronizations属性,是线程本地类型的,能够为当前事务添加一些异步方法。
public abstract class TransactionSynchronizationManager {
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
}
在事务提交的processCommit方法中,有triggerAfterCommit方法,在这个方法中会调用注册的异步回调方法。
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
// Trigger afterCommit callbacks, with an exception thrown there
// propagated to callers but the transaction still considered as committed.
try {
triggerAfterCommit(status);
}
finally {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
}
finally {
cleanupAfterCompletion(status);
}
}
public static void triggerAfterCommit() {
invokeAfterCommit(TransactionSynchronizationManager.getSynchronizations());
}
public static void invokeAfterCommit(@Nullable List<TransactionSynchronization> synchronizations) {
if (synchronizations != null) {
for (TransactionSynchronization synchronization : synchronizations) {
synchronization.afterCommit();
}
}
}
public void insert(TechBook techBook){
bookMapper.insert(techBook);
// send after tx commit but is async
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
System.out.println("send email after transaction commit...");
}
}
);
}
总结
本文主要对spring中事物的基本流程进行了分析,但还留下了几个问题,spring和mybatis是如何保证connection一致的,事务的传播机制,以及将事物的traninfo信息写入本地线程中的作用吗 ,将在接下来一一解析。