文章目录
一、背景
- 在
springboot
项目中,使用@Transactional
注解,非直接调用的方法注解会失效,如:UserController(A方法)
->UserServiceImpl(B方法)
->UserServiceImpl(C方法)
,把@Transactional
加在B方法
有效,加在C方法
无效
二、目的
-
- 揭秘为什么事务注解加在
C方法
无效
- 揭秘为什么事务注解加在
-
- 如何使
C方法
的事务有效
- 如何使
三、内容
1、自己写代码控制事务提交
- 代码示例
public Boolean transactional(String driverClassName, String url, String username, String password) {
Connection connection = null;
Statement statement = null;
try {
connection = getConnection(driverClassName, url, username, password);
//设置自动提交为false
connection.setAutoCommit(false);
// 获取执行sql的对象
statement = connection.createStatement();
String sql = "xxxxxx";
statement.execute(sql);
//提交事务
connection.commit();
} catch (Exception exception) {
try {
if (connection != null) {
connection.rollback();
}
} catch (SQLException sqlException) {
//省略...
}
throw new UpgradeException(ResultTypeEnum.System_Exception, exception.getMessage());
} finally {
//省略...
}
return true;
}
- 分析:通过
connection.setAutoCommit(false)
设置事务不自动提交,通过connection.commit()
控制事务的提交点,通过connection.rollback()
来控制事务回滚;思考:框架是如何实现的呢?是不是也有这几个步骤,只是它的功能更加强大?
2、直接调用的方法上加事务
- 代码示例
@Test
void testTransactional_1() {
userService.testTransactional_1();
}
@Transactional
@Override
public void testTransactional_1() {
User user = new User();
user.setUsername("zzg1");
user.setAge(20);
user.setPassword("123456");
userMapper.insert(user);
int i = 1 / 0;
}
-
执行前
-
执行后
-
总结:这是常见的事务注解形式,直接加在
B方法
上,事务是生效的
3、非直接调用的方法上加事务
- 代码示例
@Test
void testTransactional_2() {
userService.testTransactional_2();
}
@Override
public void testTransactional_2() {
this.testTransactional_0();
}
@Transactional
@DS("master")
public void testTransactional_0() {
User user = new User();
user.setUsername("zzg1");
user.setAge(20);
user.setPassword("123456");
userMapper.insert(user);
int i = 1 / 0;
}
-
执行前
-
执行后
-
总结:加在非直接调用的
C方法
上,事务失效
4、Debug调试,分析执行过程
-
关键点1:
org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept
-
关键点2:
org.springframework.aop.framework.CglibAopProxy.CglibMethodInvocation
-
关键点3:
ReflectiveMethodInvocation.java:186
-
关键点4:
org.springframework.jdbc.datasource.ConnectionHolder#getConnection
-
关键点5:
DataSourceTransactionManager.java:287
-
关键点6:
org.springframework.jdbc.datasource.ConnectionHolder#getConnection
-
关键点7:
com.baomidou.mybatisplus.core.executor.MybatisSimpleExecutor#prepareStatement
-
关键点8:
MybatisSimpleExecutor.java:54
-
关键点9:
org.springframework.transaction.interceptor.TransactionAspectSupport#completeTransactionAfterThrowing
-
关键点10:
org.springframework.transaction.interceptor.TransactionAspectSupport.TransactionInfo#getTransactionManager
-
关键点11:
org.mybatis.spring.transaction.SpringManagedTransaction#connection
-
关键点12:
java.sql.Connection#rollback()
-
分析总结:这是执行事务生效的
testTransactional_1()
方法的关键步骤
,关键点1、2、3
是GGLIB动态代理的处理,关键点4
获取数据库连接
,关键点5
设置事务提交为false
,关键点8
执行sql
,关键点9
事务异常捕获,关键点12
事务回滚;通过这些分析可以得出,事务生效是被CGLIB
代理之后,进行的一些增强方法的操作,核心本质的代码与我们自己写的事务代码没有区别思考:如何找到这些关键步骤呢?
5、动态代理类的字节码反编译分析
-
HSDB
连接进程-
jps
获取进程ID
-
执行命令
java -classpath sa-jdi.jar "sun.jvm.hotspot.HSDB"
-
File
->Attach to HotSpot process
->Enter Project ID
-
-
Tools->Class Brower
-
HSDB
输入目标类名称,找到动态代理之后的类
-
生成的类保存在在
jdk1.8.0_131\lib
的目录
-
复制到
idea
-
关键代码截图:
-
分析总结:可以分析得出
CGLIB
是通过继承的方式来代理的;不论是testTransactional_2()
还是testTransactional_0()
都进行了方法拦截操作。var10000.intercept(this, CGLIB$testTransactional_0$1$Method, CGLIB$emptyArgs, CGLIB$testTransactional_0$1$Proxy);
这行代码会进入步骤4中进行注解分析;如果没有注解,则直接调用父类的方法;到这里其实就可以得出为什么注解会失效了;形如:A方法
->UserServiceImpl(B方法、C方法,B方法调用C方法)
,UserServiceImpl
代理之后,UserServiceImpl’
(B’方法
、C’方法
,B’方法
本质调用的是B方法
只是对B方法
进行了增强,而B方法
调用的还是C方法
并不是C’方法
,这就是没有生效
的原因)
6、如何解决事务注解非直接注解带来的注解无效问题
- 方式一:在
B方法
中,明确调用的是C’的方法
,AopContext.currentProxy();
- 代码示例:
@Override
public void testTransactional_4() {
//明确使用代理类的对象
UserServiceImpl userService = (UserServiceImpl) AopContext.currentProxy();
userService.testTransactional_0();
}
- 方式二:在
application
启动类加上注解@EnableAspectJAutoProxy(exposeProxy = true)
,在目标类中注入private TransactionTemplate transactionTemplate;
- 代码示例:
private void testTransactional_01() {
transactionTemplate.execute(transactionStatus -> {
User user = new User();
user.setUsername("zzg1");
user.setAge(20);
user.setPassword("123456");
userMapper.insert(user);
int i = 1 / 0;
return Boolean.TRUE;
});
}
7、拓展:如何找到步骤4
中的这些关键点呢?
- 如上图所示,一个是
事务生效
的堆栈信息
,一个是事务不生效
的堆栈信息
,对比分析可以知道,事务生效的堆栈信息里,有关于事务
的处理,针对这些堆栈信息去Debug
,梳理逻辑,然后进一步的查看具体
的方法,查找我们想要的信息
附录:
相关文章推荐:Spring 官方推荐的 @Transactional 还能导致生产事故?