Spring 嵌套事务回滚的问题
问题描述
线上的项目出现,Spring的事务回滚了,但仍触发了事务提交成功的事件。这就导致原先通过异步事件解藕的业务处理数据会不正确。例如业务更新成功后才发送通知消息的处理,会出现业务回滚了,仍触发了发送消息通知的处理。
项目背景:
Spring Boot 2.0.0
+ druid 1.1.9
问题重现
1. Spring的声明事务
事务的传播级别: PROPAGATION_NESTED
@Configuration
public class TxAnoConfig {
private static final int TX_METHOD_TIMEOUT = 3600;
private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.abc.service..*.*(..)) ";
/*事务拦截类型*/
@Bean("txSource")
public TransactionAttributeSource transactionAttributeSource(){
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
/**
* REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
* SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
* MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
* REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
* NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
* NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
* NESTED--如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
*/
readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS );
/*当前存在事务就使用当前事务,当前不存在事务就创建一个新的事务*/
//RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
//requiredTx.setRollbackRules(
// Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
//requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
RuleBasedTransactionAttribute nestedTx = new RuleBasedTransactionAttribute(TransactionDefinition.PROPAGATION_NESTED,
Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
nestedTx.setTimeout(TX_METHOD_TIMEOUT);
Map<String, TransactionAttribute> txMap = new HashMap<>();
//定义哪些方法启动事务
txMap.put("add*", nestedTx);
txMap.put("save*", nestedTx);
txMap.put("insert*", nestedTx);
txMap.put("update*", nestedTx);
txMap.put("delete*", nestedTx);
txMap.put("create*", nestedTx);
source.setNameMap( txMap );
return source;
}
/**切面拦截规则 参数会自动从容器中注入*/
@Bean
public AspectJExpressionPointcutAdvisor pointcutAdvisor(TransactionInterceptor txInterceptor){
AspectJExpressionPointcutAdvisor pointcutAdvisor = new AspectJExpressionPointcutAdvisor();
pointcutAdvisor.setAdvice(txInterceptor);
pointcutAdvisor.setExpression(AOP_POINTCUT_EXPRESSION);
return pointcutAdvisor;
}
/*事务拦截器*/
@Bean("txInterceptor")
TransactionInterceptor getTransactionInterceptor(PlatformTransactionManager tx){
return new TransactionInterceptor(tx , transactionAttributeSource()) ;
}
}
2. 事务的事件处理逻辑
@Log4j2
@Component
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class TestListener {
@Async
@TransactionalEventListener(phase= TransactionPhase.BEFORE_COMMIT)
public void beforeCommitEvent(TestEvent event){
log.info("测试事件-事务提交前事件:【{}】",event.getStr());
}
@Async
@TransactionalEventListener(phase= TransactionPhase.AFTER_COMMIT)
public void afterCommitEvent(TestEvent event){
log.info("测试事件-事务提交成功事件:【{}】",event.getStr());
}
@Async
@TransactionalEventListener(phase= TransactionPhase.AFTER_ROLLBACK)
public void afterRollbackEvent(TestEvent event){
log.info("测试事件-事务回滚之后事件:【{}】",event.getStr());
}
@Async
@TransactionalEventListener(phase= TransactionPhase.AFTER_COMPLETION)
public void afterCompletionEvent(TestEvent event){
log.info("测试事件-事务完成之后事件:【{}】",event.getStr());
}
}
3.实际业务Service层方法
public void updateInfo(Integer id) {
SystemUserCommonResult user = new SystemUserCommonResult();
user.setId(0);
user.setName("系统");
CommentInfoForm form = new CommentInfoForm();
form.setDataType(10);
form.setRelationId(5);
form.setContent("测试事务回滚01");
LocalDateTime now = now();
CommentInfo record = new CommentInfo();
record.setContent(StringUtils.trimToEmpty(form.getContent()));
record.setDataType(form.getDataType());
record.setRelationId(form.getRelationId());
record.setCreateDate(now);
record.setCreatorId(user.getId());
record.setCreator(user.getName());
commentInfoMapper.insert(record);
//手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
//推送测试事件
applicationContext.publishEvent(new TestEvent(this,"测试事务回滚代码执行后"));
}
4.Druid数据源的配置
启用Druid的Sping的监控
#dataSource Pool configuration for DruidDataSource
spring.datasource.url=jdbc:mysql://xxxxx
spring.datasource.username=xxx
spring.datasource.password=xxxxx
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#连接池配置
spring.datasource.druid.initialSize=5
spring.datasource.druid.minIdle=5
spring.datasource.druid.maxActive=20
spring.datasource.druid.maxWait=60000
spring.datasource.druid.timeBetweenEvictionRunsMillis=60000
spring.datasource.druid.minEvictableIdleTimeMillis=300000
spring.datasource.druid.validationQuery=SELECT 1 FROM DUAL
spring.datasource.druid.testWhileIdle=true
spring.datasource.druid.testOnBorrow=false
spring.datasource.druid.testOnReturn=false
spring.datasource.druid.poolPreparedStatements=true
spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.druid.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
spring.datasource.druid.useGlobalDataSourceStat=true
## 使用那些过滤器,支持stat,wall,log4j2等
### stat : StatFilter 用于统计监控信息。
### wall : wallfilter SQL防火墙。
spring.datasource.druid.filters=stat,wall,log4j2
## druid 监控 WebStatFilter配置,说明请参考Druid Wiki,配置_配置WebStatFilter
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*
## druid 监控页面 StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
##监控页面用户名和密码
spring.datasource.druid.stat-view-servlet.login-username=xxxx
spring.datasource.druid.stat-view-servlet.login-password=xxxx
##禁用HTML页面上的“Reset All”功能
spring.datasource.druid.stat-view-servlet.reset-enable=true
##IP白名单 (没有配置或者为空,则允许所有访问)
spring.datasource.druid.stat-view-servlet.allow=
##IP黑名单 (存在共同时,deny优先于allow)
spring.datasource.druid.stat-view-servlet.deny=
## Spring监控配置,说明请参考Druid Github Wiki,配置_Druid和Spring关联监控配置
### Spring监控AOP切入点,如x.y.z.service.*,配置多个英文逗号分隔
spring.datasource.druid.aop-patterns= com.abc.service.*
5.测试执行
修改Spring的日志的级别DEBUG,用于输出事务回滚的日志
控制台输出的日志
[2021-10-27 13:32:53.141] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:372] - Creating new transaction with name [com.abc.service.impl.TestServiceImpl.updateInfo]: PROPAGATION_NESTED,ISOLATION_DEFAULT,timeout_3600,-java.lang.Exception
[2021-10-27 13:32:53.162] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10005} pool-connect
[2021-10-27 13:32:53.163] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:264] - Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@f7c20c] for JDBC transaction
[2021-10-27 13:32:53.167] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:281] - Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@f7c20c] to manual commit
[2021-10-27 13:32:53.167] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10005} setAutoCommit false
[2021-10-27 13:32:53.184] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:447] - Creating nested transaction with name [com.abc.service.impl.TestServiceImpl.updateInfo]
[2021-10-27 13:32:53.213] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn 10005} setSavepoint-SAVEPOINT_1
[2021-10-27 13:32:53.239] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.m.s.SqlSessionUtils:97] - Creating a new SqlSession
[2021-10-27 13:32:53.247] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.m.s.SqlSessionUtils:128] - Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4a08e603]
[2021-10-27 13:32:53.255] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.m.s.t.SpringManagedTransaction:87] - JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@f7c20c] will be managed by Spring
[2021-10-27 13:32:53.260] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [c.e.m.d.C.insert:159] - ==> Preparing: insert into `comment_info` ( `id`, `data_type`, `relation_id`, `create_date`, `creator`, `creator_id`, `content` ) values ( ?, ?, ?, ?, ?, ?, ? )
[2021-10-27 13:32:53.416] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Statement:137] - {conn-10005, pstmt-20000} created. insert into `comment_info` (
`id`,
`data_type`,
`relation_id`,
`create_date`,
`creator`,
`creator_id`,
`content`
)
values (
?,
?,
?,
?,
?,
?,
?
)
[2021-10-27 13:32:53.438] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [c.e.m.d.C.insert:159] - ==> Parameters: null, 10(Integer), 5(Integer), 2021-10-27 13:32:53.2346418(Timestamp), 系统(String), 0(Integer), java.io.StringReader@60487067(StringReader)
[2021-10-27 13:32:53.439] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Statement:137] - {conn-10005, pstmt-20000} Parameters : [null, 10, 5, 2021-10-27 13:32:53.2346418, 系统, 0, java.io.StringReader@60487067]
[2021-10-27 13:32:53.439] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Statement:137] - {conn-10005, pstmt-20000} Types : [INTEGER, INTEGER, INTEGER, TIMESTAMP, VARCHAR, INTEGER, OTHER]
[2021-10-27 13:32:53.463] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Statement:137] - {conn-10005, pstmt-20000} executed. 23.7545 millis. insert into `comment_info` (
`id`,
`data_type`,
`relation_id`,
`create_date`,
`creator`,
`creator_id`,
`content`
)
values (
?,
?,
?,
?,
?,
?,
?
)
[2021-10-27 13:32:53.464] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [c.e.m.d.C.insert:159] - <== Updates: 1
[2021-10-27 13:32:53.464] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Statement:137] - {conn-10005, pstmt-20000} clearParameters.
[2021-10-27 13:32:53.464] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [c.a.d.p.PreparedStatementPool:129] - {conn-10005, pstmt-20000} enter cache
[2021-10-27 13:32:53.465] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.m.s.SqlSessionUtils:186] - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4a08e603]
[2021-10-27 13:32:53.466] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:700] - Transactional code has requested rollback
[2021-10-27 13:32:55.759] [] [idle_connection_reaper] DEBUG: [o.a.h.i.c.PoolingHttpClientConnectionManager:428] - Closing expired connections
[2021-10-27 13:32:55.759] [] [idle_connection_reaper] DEBUG: [o.a.h.i.c.PoolingHttpClientConnectionManager:421] - Closing connections idle longer than 60000 MILLISECONDS
[2021-10-27 13:32:56.252] [] [Connection evictor] DEBUG: [o.a.h.i.c.PoolingHttpClientConnectionManager:428] - Closing expired connections
[2021-10-27 13:32:56.486] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:830] - Rolling back transaction to savepoint
[2021-10-27 13:32:56.500] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn 10005} rollback -> SAVEPOINT_1
[2021-10-27 13:32:56.500] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.b.f.s.DefaultListableBeanFactory:255] - Returning cached instance of singleton bean 'testListener'
[2021-10-27 13:32:56.504] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.b.f.s.DefaultListableBeanFactory:255] - Returning cached instance of singleton bean 'transactionManager'
[2021-10-27 13:32:56.504] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:422] - Suspending current transaction, creating new transaction with name [com.abc.listener.TestListener.beforeCommitEvent]
[2021-10-27 13:32:56.504] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.m.s.SqlSessionUtils:251] - Transaction synchronization suspending SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4a08e603]
[2021-10-27 13:32:56.514] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10004} pool-connect
[2021-10-27 13:32:56.515] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:264] - Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@277226d6] for JDBC transaction
[2021-10-27 13:32:56.516] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:281] - Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@277226d6] to manual commit
[2021-10-27 13:32:56.516] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10004} setAutoCommit false
[2021-10-27 13:32:56.532] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:743] - Initiating transaction commit
[2021-10-27 13:32:56.532] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:326] - Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@277226d6]
[2021-10-27 13:32:56.533] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-1] DEBUG: [o.s.j.d.DataSourceTransactionManager:372] - Creating new transaction with name [com.abc.listener.TestListener.beforeCommitEvent]: PROPAGATION_REQUIRES_NEW,ISOLATION_DEFAULT; ''
[2021-10-27 13:32:56.541] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10004} commited
[2021-10-27 13:32:56.541] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10004} setAutoCommit true
[2021-10-27 13:32:56.543] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-1] DEBUG: [d.s.Connection:132] - {conn-10003} pool-connect
[2021-10-27 13:32:56.543] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-1] DEBUG: [o.s.j.d.DataSourceTransactionManager:264] - Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1fa1e475] for JDBC transaction
[2021-10-27 13:32:56.543] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-1] DEBUG: [o.s.j.d.DataSourceTransactionManager:281] - Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1fa1e475] to manual commit
[2021-10-27 13:32:56.543] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-1] DEBUG: [d.s.Connection:132] - {conn-10003} setAutoCommit false
[2021-10-27 13:32:56.557] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-1] INFO : [c.e.l.TestListener:24] - 测试事件-事务提交前事件:【测试业务】
[2021-10-27 13:32:56.557] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-1] DEBUG: [o.s.j.d.DataSourceTransactionManager:743] - Initiating transaction commit
[2021-10-27 13:32:56.557] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-1] DEBUG: [o.s.j.d.DataSourceTransactionManager:326] - Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1fa1e475]
[2021-10-27 13:32:56.559] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:384] - Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@277226d6] after transaction
[2021-10-27 13:32:56.559] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceUtils:340] - Returning JDBC Connection to DataSource
[2021-10-27 13:32:56.560] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10004} pool-recycle
[2021-10-27 13:32:56.560] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:1011] - Resuming suspended transaction after completion of inner transaction
[2021-10-27 13:32:56.560] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.m.s.SqlSessionUtils:264] - Transaction synchronization resuming SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4a08e603]
[2021-10-27 13:32:56.560] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.m.s.SqlSessionUtils:284] - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4a08e603]
[2021-10-27 13:32:56.560] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.m.s.SqlSessionUtils:310] - Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4a08e603]
[2021-10-27 13:32:56.560] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.m.s.SqlSessionUtils:315] - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4a08e603]
[2021-10-27 13:32:56.560] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:743] - Initiating transaction commit
[2021-10-27 13:32:56.560] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:326] - Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@f7c20c]
[2021-10-27 13:32:56.566] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-1] DEBUG: [d.s.Connection:132] - {conn-10003} commited
[2021-10-27 13:32:56.566] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-1] DEBUG: [d.s.Connection:132] - {conn-10003} setAutoCommit true
[2021-10-27 13:32:56.570] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10005} commited
[2021-10-27 13:32:56.570] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.b.f.s.DefaultListableBeanFactory:255] - Returning cached instance of singleton bean 'testListener'
[2021-10-27 13:32:56.570] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:422] - Suspending current transaction, creating new transaction with name [com.abc.listener.TestListener.afterCommitEvent]
[2021-10-27 13:32:56.570] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10004} pool-connect
[2021-10-27 13:32:56.570] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:264] - Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@277226d6] for JDBC transaction
[2021-10-27 13:32:56.571] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:281] - Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@277226d6] to manual commit
[2021-10-27 13:32:56.571] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10004} setAutoCommit false
[2021-10-27 13:32:56.581] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:743] - Initiating transaction commit
[2021-10-27 13:32:56.581] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:326] - Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@277226d6]
[2021-10-27 13:32:56.581] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-2] DEBUG: [o.s.j.d.DataSourceTransactionManager:372] - Creating new transaction with name [com.abc.listener.TestListener.afterCommitEvent]: PROPAGATION_REQUIRES_NEW,ISOLATION_DEFAULT; ''
[2021-10-27 13:32:56.584] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-1] DEBUG: [o.s.j.d.DataSourceTransactionManager:384] - Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1fa1e475] after transaction
[2021-10-27 13:32:56.584] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-1] DEBUG: [o.s.j.d.DataSourceUtils:340] - Returning JDBC Connection to DataSource
[2021-10-27 13:32:56.584] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-1] DEBUG: [d.s.Connection:132] - {conn-10003} pool-recycle
[2021-10-27 13:32:56.589] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10004} commited
[2021-10-27 13:32:56.589] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10004} setAutoCommit true
[2021-10-27 13:32:56.589] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-2] DEBUG: [d.s.Connection:132] - {conn-10002} pool-connect
[2021-10-27 13:32:56.590] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-2] DEBUG: [o.s.j.d.DataSourceTransactionManager:264] - Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@2a2e613c] for JDBC transaction
[2021-10-27 13:32:56.590] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-2] DEBUG: [o.s.j.d.DataSourceTransactionManager:281] - Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@2a2e613c] to manual commit
[2021-10-27 13:32:56.590] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-2] DEBUG: [d.s.Connection:132] - {conn-10002} setAutoCommit false
[2021-10-27 13:32:56.600] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-2] INFO : [c.e.l.TestListener:30] - 测试事件-事务提交成功事件:【测试业务】
[2021-10-27 13:32:56.600] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-2] DEBUG: [o.s.j.d.DataSourceTransactionManager:743] - Initiating transaction commit
[2021-10-27 13:32:56.601] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-2] DEBUG: [o.s.j.d.DataSourceTransactionManager:326] - Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@2a2e613c]
[2021-10-27 13:32:56.607] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:384] - Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@277226d6] after transaction
[2021-10-27 13:32:56.607] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceUtils:340] - Returning JDBC Connection to DataSource
[2021-10-27 13:32:56.607] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10004} pool-recycle
[2021-10-27 13:32:56.607] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:1011] - Resuming suspended transaction after completion of inner transaction
[2021-10-27 13:32:56.607] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.b.f.s.DefaultListableBeanFactory:255] - Returning cached instance of singleton bean 'testListener'
[2021-10-27 13:32:56.607] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:422] - Suspending current transaction, creating new transaction with name [com.abc.listener.TestListener.afterCompletionEvent]
[2021-10-27 13:32:56.607] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10004} pool-connect
[2021-10-27 13:32:56.608] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:264] - Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@277226d6] for JDBC transaction
[2021-10-27 13:32:56.608] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:281] - Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@277226d6] to manual commit
[2021-10-27 13:32:56.608] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10004} setAutoCommit false
[2021-10-27 13:32:56.610] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-2] DEBUG: [d.s.Connection:132] - {conn-10002} commited
[2021-10-27 13:32:56.611] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-2] DEBUG: [d.s.Connection:132] - {conn-10002} setAutoCommit true
[2021-10-27 13:32:56.618] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:743] - Initiating transaction commit
[2021-10-27 13:32:56.618] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:326] - Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@277226d6]
[2021-10-27 13:32:56.619] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-3] DEBUG: [o.s.j.d.DataSourceTransactionManager:372] - Creating new transaction with name [com.abc.listener.TestListener.afterCompletionEvent]: PROPAGATION_REQUIRES_NEW,ISOLATION_DEFAULT; ''
[2021-10-27 13:32:56.619] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-3] DEBUG: [d.s.Connection:132] - {conn-10003} pool-connect
[2021-10-27 13:32:56.619] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-3] DEBUG: [o.s.j.d.DataSourceTransactionManager:264] - Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1fa1e475] for JDBC transaction
[2021-10-27 13:32:56.619] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-3] DEBUG: [o.s.j.d.DataSourceTransactionManager:281] - Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1fa1e475] to manual commit
[2021-10-27 13:32:56.619] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-3] DEBUG: [d.s.Connection:132] - {conn-10003} setAutoCommit false
[2021-10-27 13:32:56.627] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10004} commited
[2021-10-27 13:32:56.628] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10004} setAutoCommit true
[2021-10-27 13:32:56.628] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-3] INFO : [c.e.l.TestListener:42] - 测试事件-事务完成之后事件:【测试业务】
[2021-10-27 13:32:56.628] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-3] DEBUG: [o.s.j.d.DataSourceTransactionManager:743] - Initiating transaction commit
[2021-10-27 13:32:56.628] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-3] DEBUG: [o.s.j.d.DataSourceTransactionManager:326] - Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1fa1e475]
[2021-10-27 13:32:56.629] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-2] DEBUG: [o.s.j.d.DataSourceTransactionManager:384] - Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@2a2e613c] after transaction
[2021-10-27 13:32:56.629] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-2] DEBUG: [o.s.j.d.DataSourceUtils:340] - Returning JDBC Connection to DataSource
[2021-10-27 13:32:56.630] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-2] DEBUG: [d.s.Connection:132] - {conn-10002} pool-recycle
[2021-10-27 13:32:56.637] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-3] DEBUG: [d.s.Connection:132] - {conn-10003} commited
[2021-10-27 13:32:56.637] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-3] DEBUG: [d.s.Connection:132] - {conn-10003} setAutoCommit true
[2021-10-27 13:32:56.646] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:384] - Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@277226d6] after transaction
[2021-10-27 13:32:56.646] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceUtils:340] - Returning JDBC Connection to DataSource
[2021-10-27 13:32:56.646] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10004} pool-recycle
[2021-10-27 13:32:56.646] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:1011] - Resuming suspended transaction after completion of inner transaction
[2021-10-27 13:32:56.646] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10005} setAutoCommit true
[2021-10-27 13:32:56.655] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-3] DEBUG: [o.s.j.d.DataSourceTransactionManager:384] - Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1fa1e475] after transaction
[2021-10-27 13:32:56.655] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-3] DEBUG: [o.s.j.d.DataSourceUtils:340] - Returning JDBC Connection to DataSource
[2021-10-27 13:32:56.655] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-3] DEBUG: [d.s.Connection:132] - {conn-10003} pool-recycle
[2021-10-27 13:32:56.666] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:384] - Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@f7c20c] after transaction
[2021-10-27 13:32:56.666] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceUtils:340] - Returning JDBC Connection to DataSource
[2021-10-27 13:32:56.666] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10005} pool-recycle
[2021-10-27 13:32:56.726] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.w.s.m.m.a.HttpEntityMethodProcessor:277] - Written [ResponseResult(code=0, message=成功, result=null, results=null, errors=null)] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@7220a594]
[2021-10-27 13:32:56.727] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.w.s.DispatcherServlet:1076] - Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
[2021-10-27 13:32:56.727] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.w.s.DispatcherServlet:1004] - Successfully completed request
日志分析
从日志中可以看到事务的创建、回滚的以及触发事务事件的全部过程。
创建nested
的顶层事务的日志
[2021-10-27 13:32:53.141] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:372] - Creating new transaction with name [com.abc.service.impl.TestServiceImpl.updateInfo]: PROPAGATION_NESTED,ISOLATION_DEFAULT,timeout_3600,-java.lang.Exception
[2021-10-27 13:32:53.162] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10005} pool-connect
[2021-10-27 13:32:53.163] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:264] - Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@f7c20c] for JDBC transaction
[2021-10-27 13:32:53.167] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:281] - Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@f7c20c] to manual commit
[2021-10-27 13:32:53.167] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn-10005} setAutoCommit false
对应的调用链
org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
->
org.springframework.transaction.interceptor.TransactionAspectSupport#createTransactionIfNecessary
->
org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction
->
org.springframework.transaction.support.AbstractPlatformTransactionManager#doBegin
具体的代码分析后面再补,这里就不展开。
创建nested
的第二层事务的日志
[2021-10-27 13:32:53.184] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:447] - Creating nested transaction with name [com.expertsplatform.service.impl.TestServiceImpl.updateInfo]
[2021-10-27 13:32:53.213] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn 10005} setSavepoint-SAVEPOINT_1
对应的调用链:
org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
->
org.springframework.transaction.interceptor.TransactionAspectSupport#createTransactionIfNecessary
->
org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction
->
org.springframework.transaction.support.AbstractPlatformTransactionManager#handleExistingTransaction
具体的代码分析后面再补,这里就不展开。
事务回滚的日志
[2021-10-27 13:32:53.465] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.m.s.SqlSessionUtils:186] - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4a08e603]
[2021-10-27 13:32:53.466] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:700] - Transactional code has requested rollback
[2021-10-27 13:32:56.486] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [o.s.j.d.DataSourceTransactionManager:830] - Rolling back transaction to savepoint
[2021-10-27 13:32:56.500] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [http-nio-80-exec-10] DEBUG: [d.s.Connection:132] - {conn 10005} rollback -> SAVEPOINT_1
对应的调用链:
org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
->
org.springframework.transaction.interceptor.TransactionAspectSupport#commitTransactionAfterReturning
->
org.springframework.transaction.support.AbstractPlatformTransactionManager#commit
->
org.springframework.transaction.support.AbstractPlatformTransactionManager#processRollback
->
org.springframework.transaction.support.AbstractTransactionStatus#rollbackToHeldSavepoint
具体的代码分析后面再补,这里就不展开。
事务事件触发日志
[2021-10-27 13:32:56.557] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-1] INFO : [c.e.l.TestListener:24] - 测试事件-事务提交前事件:【测试业务】
[2021-10-27 13:32:56.600] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-2] INFO : [c.e.l.TestListener:30] - 测试事件-事务提交成功事件:【测试业务】
[2021-10-27 13:32:56.628] [d60c4bd9-5f42-4aff-bbb1-0f11e16890b5] [spring-async-3] INFO : [c.e.l.TestListener:42] - 测试事件-事务完成之后事件:【测试业务】
从日志中就能看出,调用业务方法时,创建了两个事件,回滚里其中一个,最终没有触发事务回滚事件,而是触发了事务提交成功事件。
通过分析源码和运行时的调用栈,发现问题的原因是业务方法被Sping的事务增强了两次,即同一个方法被包了两层Spring的事务。
示意图:
实际的调用栈:
分析Spring的事务处理源码后发现,当出现这种情况时,对于PROPAGATION_NESTED
事务传播类型,如果里层事务是通过TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
手动回滚的,上一层和顶层事务是不会回滚的,回滚的只是执行了该代码的当前层的事务。更上层的事务仍会按照正常成功的事务提交。
分析问题产生的原因
通过分析调用栈发现问题的原因是Druid
Spring 监控配置导致的重复增强的处理,这个问题之前在分析Spring Boot循环依赖的问题时也发现了,具体可以参考Spring Boot 2.0.0 升级到2.4.1 循环依赖问题的解决 ,原因是同一个原因,升级Druid的版本就可以了。这个应该是Druid的一个BUG。当然也可以通过不配置spring.datasource.druid.aop-patterns
来禁用Druid的Sping监控来处理。
总结
Spring 的事务代码分析
事务增强的处理逻辑
org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
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 = null;
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
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
final ThrowableHolder throwableHolder = new ThrowableHolder();
// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
try {
Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
try {
return invocation.proceedWithInvocation();
}
catch (Throwable ex) {
if (txAttr.rollbackOn(ex)) {
// A RuntimeException: will lead to a rollback.
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
else {
throw new ThrowableHolderException(ex);
}
}
else {
// A normal return value: will lead to a commit.
throwableHolder.throwable = ex;
return null;
}
}
finally {
cleanupTransactionInfo(txInfo);
}
});
// Check result state: It might indicate a Throwable to rethrow.
if (throwableHolder.throwable != null) {
throw throwableHolder.throwable;
}
return result;
}
catch (ThrowableHolderException ex) {
throw ex.getCause();
}
catch (TransactionSystemException ex2) {
if (throwableHolder.throwable != null) {
logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
ex2.initApplicationException(throwableHolder.throwable);
}
throw ex2;
}
catch (Throwable ex2) {
if (throwableHolder.throwable != null) {
logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
}
throw ex2;
}
}
}
事务方法执行异常的处理
org.springframework.transaction.interceptor.TransactionAspectSupport#completeTransactionAfterThrowing
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
"] after exception: " + ex);
}
//判断异常是否在事务配置的异常捕获范围内,是的话执行回滚操作
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
}
//不是的话,执行提交操作
else {
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
}
}
}
判断事务是否回滚的处理逻辑
org.springframework.transaction.support.AbstractPlatformTransactionManager#commit
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;
// 判断是否手动设置了回滚,即调用了TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
//手动回滚,不是意外的回滚
processRollback(defStatus, false);
return;
}
// 判断是否需要全局回滚,即整个事务触发异常了,或者手动设置了Connection的回滚标志
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);
}
作死小技巧:
手动修改事务的isGlobalRollbackOnly
为true
的方法(玩玩可以,别用在实际代码上)
TransactionStatus transactionStatus = TransactionAspectSupport.currentTransactionStatus();
if(transactionStatus instanceof DefaultTransactionStatus){
DefaultTransactionStatus defaultTransactionStatus = (DefaultTransactionStatus)transactionStatus;
Object object = defaultTransactionStatus.getTransaction();
if(object instanceof JdbcTransactionObjectSupport){
JdbcTransactionObjectSupport transactionObject = (JdbcTransactionObjectSupport) object;
transactionObject.getConnectionHolder().setRollbackOnly();
}
}
回滚的处理逻辑
org.springframework.transaction.support.AbstractPlatformTransactionManager#processRollback
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
try {
//触发事务完成前的事件
triggerBeforeCompletion(status);
//如果当前事务支持部分回滚,则执行部分回滚,即`nested`事务
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 {
//异常的事务回滚处理
// Participating in larger transaction
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");
}
// Unexpected rollback only matters here if we're asked to fail early
if (!isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = false;
}
}
}
catch (RuntimeException | Error ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
//触发事务提交后的事件
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
// Raise UnexpectedRollbackException if we had a global rollback-only marker
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
}
finally {
cleanupAfterCompletion(status);
}
}
Spring 的事务传播类型总结
PROPAGATION_NESTED
- 1、共用一个事务,但每一层都有独立的保存点,可以独立提交事务的回滚。
- 2、每层事务可以通过
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
独立设置是否回滚。当前层的回滚不会影响上层的回滚,但上层的回滚会影响下层的回滚。 - 3、如果是通过抛出异常导致的回滚,这个会触发整个事务的回滚(Sping的事务处理中异常的回滚处理和手动回滚走的不同的处理逻辑)。
- 4、如果下层回滚,最上层不回滚,则最终事务仍是作为正常提交成功的事务,仍会触发事务提交成功后的事件,并不会触发事务回滚的事件。即最终事务的状态由最顶层的事务决定。
PROPAGATION_REQUIRED
- 1、共用一个事务,只能设置全局回滚或全局不回滚,不支持局部回滚。
- 2、如果有多层,只要有任意一层设置了回滚,则它的上一层及最上层也必须设置回滚,否则会抛出
Transaction rolled back because it has been marked as rollback-only
异常。 - 3、由于所有层都是统一的提交和回滚,因此不会出现部分提交的问题。
新的问题
如果要实现在事务中任意一层回滚了,整个事务全部回滚,在不修改业务逻辑的情况下,如何实现呢?
思路有以下几个:
方案1
修改所有的需要手动提交事务的业务方法,当需要手动回滚事务时,方法返回需要回滚事务。
优点
实现简单
缺点
需要修改所有相关的业务方法,并且需要占用业务方法的返回值。
方案2
用AOP方法对所有需要事务增强的方法再包一层,增加自定义的否全局回滚的标志,在调用完实际业务方法后,判断全局回滚标志是否开启,是的话,手动设置当前层回滚事务。 同时在所有需要手动回滚的事务方法里,在执行手动回滚事务时,同时修改该当前事务的自定义的全局回滚标志。
优点
实现困难,类似于分布式事务的处理逻辑,需要考虑多线程的问题。
缺点
不影响业务代码,只需对手动回滚业务的操作再封装一层即可。
后续抽空实现以下方案2,看看效果,目前先用方案1