1 先看现象
有如下代码,调用addUserAndSalary方法,t_user表和t_salary表哪个表里会被插入数据呢???
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDaoImpl;
@Autowired
private SalaryDao salaryDao;
/***
* 插入用户信息和该用户的薪资信息
* @param username
* @param salary
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void addUserAndSalary(String username, BigDecimal salary) {
//按照生日等生成一个14位的员工编号
String account = "20191218000001";
//往员工表里插入该用户的用户信息
userDaoImpl.saveUserInfo(username, account);
addSalary(account, salary);
//((UserService)AopContext.currentProxy()).addSalary(account,salary);
int i = 1 / 0;
}
/***
* 插入用户的薪资信息
* @param account
* @param salary
*/
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void addSalary(String account, BigDecimal salary) {
salaryDao.addSalaryInfo(account, salary);
}
}
先来分析一下:
(1)假如看过我《【spring事务前置知识】事务的传播行为》那篇文章,肯定会得到如下答案:
- 父级方法(addUserAndSalary)会发生回滚 —> t_user表里无法插入数据
- 子级方法(addSalary)不会发送回滚 —> t_salary表里会插入数据
(2)但是再一想不对啊,在addUserAndSalary里调用addSalary方法,相当于this.addSalary(…),这不就相当于直接在addUserAndSalary里累代码么? 也就是说addSalary应该是addUserAndSalary方法的一部分,显然两个表里都不可能插入数据。
跑一下代码就可以轻松的验证,(2)分析的是正确的。
那如何在同一个对象里让事务传播行为生效呢???这里就要用到exposeProxy属性了。
2 从源码看一下exposeProxy在事务源码中的逻辑
上篇文章《【spring事务源码学习】— 目标方法调用流程核心源码解读》的2.1其实就已经见到exposeProxy属性了,这里截取2.1中关于exposeProxy属性的源码如下:
所在类:JdkDynamicAopProxy
//如果aop的exposeProxy设置的为true(将代理的目标对象暴露出来),这个if语句就能进去
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
这段代码具体是什么意思呢?其实就是将当前代理对象放到AopContext对象中,AopContext的源码如下:
public final class AopContext {
//线程对象
private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal<>("Current AOP proxy");
private AopContext() {
}
//从线程对象中将代理对象取出来
public static Object currentProxy() throws IllegalStateException {
Object proxy = currentProxy.get();
if (proxy == null) {
throw new IllegalStateException(
"Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.");
}
return proxy;
}
//将代理对象set到AopContext中的线程对象里
@Nullable
static Object setCurrentProxy(@Nullable Object proxy) {
Object old = currentProxy.get();
if (proxy != null) {
currentProxy.set(proxy);
}
else {
currentProxy.remove();
}
return old;
}
}
可以看到AopContext对像其实比较简单,它就是里面封装了一个线程对象,如果你将exposeProxy属性设置为true时,目标方法被调用时,就会将当前的目标对象的代理对象存入到其所持有的ThreadLocal对象里。
知道了AopContext对象和exposeProxy属性的原理,其实就可以很轻松的 解决同一个对象里方法间调用事务传播行为生效的问题了。
3 解决方式
其实很简单,只需要做两件事:
(1)在启动配置类中加上@EnableAspectJAutoProxy(exposeProxy = true)注解,注意该注解正是开启AOP的注解
(2)在addUserAndSalary方法里通过AopContext对象获取到UserService的代理对象,并利用代理对象调用addSalary方法,即使用1中addSalary(account, salary);语句下的注释语句:
((UserService)AopContext.currentProxy()).addSalary(account,salary);
当然如果是springboot项目,只需要在启动类上加上@EnableAspectJAutoProxy(exposeProxy = true)注解就可以了。