在学习Spring事务这节的时候,遇到这样一个问题:调用同类方法事务不生效!具体问题描述如下:
测试的主要代码——
public interface FooService {
void insertThenRollback() throws RollBackException;
void invokeInsertThenRollback() throws RollBackException;
}
@Component
public class FooServiceImpl implements FooService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional(rollbackFor = RollBackException.class)
public void insertThenRollback() throws RollBackException {
jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('BBB')");
throw new RollBackException();
}
// 注意! 这个方法没有申明事务!
public void invokeInsertThenRollback() throws RollBackException {
this.insertThenRollback();
}
}
@SpringBootApplication
@EnableTransactionManagement(mode = AdviceMode.PROXY)
@Slf4j
public class XiaoLearnSpringApplication implements CommandLineRunner {
@Autowired
private FooService fooService;
@Autowired
private JdbcTemplate jdbcTemplate;
public static void main(String[] args) {
SpringApplication.run(XiaoLearnSpringApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
try {
fooService. insertThenRollback ();
} catch (Exception e) {
log.info("BBB {}",
jdbcTemplate
.queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='BBB'", Long.class));
}
try {
fooService.invokeInsertThenRollback();
} catch (Exception e) {
log.info("BBB {}",
jdbcTemplate
.queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='BBB'", Long.class));
}
}
执行insertThenRollback()方法时,事务可以正常实现(运行结果:手动抛异常让事务回滚,数据‘BBB’没有插入数据库,查询条数=0), 但是执行invokeInsertThenRollback()方法时,查询结果为1,证明数据‘BBB’成功写入数据库——事务没有正常回滚
由于这两次调用唯一的区别就是,
第一次调用:外部直接调用方法insertThenRollback,
第二次调用:外部调用方法invokeInsertThenRollback(),再由方invokeInsertThenRollback()调用insertThenRollback()
由此提出问题:
调用同类方法事务不生效!
思考
加了事务注解的方法,在Bean创建时,都会通过AOP创建一个代理实例,在实际调用过程中,调用的时这个代理实例,而不是真正的FooService对象,但上述现象显然事务没有生效,是否就证明,第二次调用是直接调用的真正的FooService对象,没有走代理实例呢?
通过http://localhost:8080/actuator/beans查看Spring Bean的情况
思考
发现fooServiceImpl只有一个代理实例,并且是单列模式,也就说不可能存在两个fooServiceImpl的bean实例,那么真正的实例对象存在哪呢?
通过Debug日志发现原始的实例对象其实是存放在代理对象的target属性里
思考:
什么情况会调用到target(原始实例)呢?
继续执行Debug,可以看到在执行fooService.invokeInsertThenRollback()这行代码时,会调用JdkDynamicAopProxy.invoke方法,让我们进到这个方法里看下源码(这里只粘贴了部分核心代码):
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
……(省略)
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
……(省略)
// 获取到原始实例
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// 获取当前方法的通知拦截器链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// 检查通知拦截器链是否为空
if (chain.isEmpty()) {
// 如果为空:直接调用target(原始实例)的方法,不生成方法通知
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// 如果不为空,纠需要生成方法通知执行拦截器里的内容
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
//通过拦截器链进入连接点
retVal = invocation.proceed();
}
……(后续返回值的处理)
return retVal;
}
finally {
……(省略)
}
}
对invokeInsertThenRollback()方法进行Debug验证:
chain返回结果为0,由此得出结论:
进入获取chain的源码,来到DefaultAdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice()方法,在这里会遍历一个Advisor数组,将method(调用的方法)标注的通知匹配上对应拦截器添加到chain中返回
这里的Advisor数组在AOP代理对象创建时,会根据每个方法的注解完成初始化操作,具体源码参见: ProxyFactoryBean. initializeAdvisorChain()方法
通过分析,我们知道了调用同类方法事务不生效问题的原因,那么在实际开发中如果遇到这类问题该如何解决呢,这里给出简单的几个思路:
1⃣️ 在实现类里面引用当前类对象
@Component
public class FooServiceImpl implements FooService {
@Autowired
private JdbcTemplate jdbcTemplate;
// 在这里引入当前类的实体对象
@Autowired
private FooService fooService;
@Autowired
private TransactionTemplate transactionTemplate;
@Transactional
public void insert() {
jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('AAA')");
}
@Transactional(rollbackFor = RollBackException.class)
public void insertThenRollback() throws RollBackException {
jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('BBB')");
throw new RollBackException();
}
// 通过对象实例去调用方法,这样就是通过代理实例调用,就可以走到事务切面了
public void invokeInsertThenRollback() throws RollBackException {
fooService.insertThenRollback();
}
}
2⃣️给invokeInsertThenRollback()方法加上事务
编程式:
public void invokeInsertThenRollback() throws RollBackException {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@SneakyThrows
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
insertThenRollback();
}
});
}
申明式:
@Transactional(rollbackFor = RollBackException.class)
public void invokeInsertThenRollback() throws RollBackException {
insertThenRollback();
}
至此, 我们通过一层层探索,顺利解决了问题~~~
每多看懂一行源码 就又进步了一点哟