问题:
在如下代码中,当调用insertA方法时,如果insertB插入b表的时候有异常,能否保证insertA()中的a表插入成功,如果不能,应该如何修改?
@Service
@Transactional
public class TestService {
@Autowired
private JdbcTemplate jt;
public void insertA(){
jt.execute("insert into a(m,n) values(1,2)");
insertB();
}
public void insertB(){
jt.execute("insert into b(h,i) values(1,2)");
}
}
解释:
不能保证insertA()中的a表插入成功,因为@Transactional
加载类上,相当于加载了该类的每一个方法上
首先明确一点“同一个对象内的事务方法互相调用会导致被调用方的事务失效”
,我们都知道事务使用代理对象来完成事务功能,如果在insertA()
方法中直接调用同一个对象(@Service表示把当前类交给了spring管理,而spring默认使用单例模式创建对象,因此调用方法的时候使用的会是同一个对象)中的insertB()
方法,这和直接把insertB()
方法的代码写在insertA()
方法中是一致的,这也会导致绕过了代理对象,因此这种调用方式会导致insertB()
方法上的事务失效
如果我们想让事务生效,我们可以调用暴露出来的代理对象,那我们就不能使用默认JDK动态代理了,而是要使用aspectj动态代理
,以后所有的动态代理都是由aspectj
来创建,即使没有借口,也可以进行动态代理
根据题意要求,insertB()
方法不能和insertA()
方法共用一个事务,所以我们要改变insertB()
方法的事务传播行为,默认事务传播行为是Propagation.REQUIRED
,常见事务传播行为如下所示:
答案:
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、在主启动类上使用注解开启aspectj动态代理
,并且暴露代理对象
@EnableAspectJAutoProxy(exposeProxy = true)
3、使用aspectj代理对象来调用本对象中的事务方法,并且改变insertB()
方法上的事务传播行为
@Service
@Transactional
public class TestService {
@Autowired
private JdbcTemplate jt;
public void insertA(){
jt.execute("insert into a(m,n) values(1,2)");
// 1、拿到当前代理对象
TestService testService = (TestService)AopContext.currentProxy();
// 2、使用代理对象调用insertB()事务方法
testService.insertB();
}
// 3、改变insertB()方法的事务传播行为,默认事务传播行为是Propagation.REQUIRED,代表继承父类的事务传播行为,即被调用方法和调用方法在同一个事务中,遵循ACID特性,根据题意要求"我们要保证insertB()方法出现异常的时候,insertA()方法中的a表插入成功",因此我们就不能让insertA()和insertB()方法使用同一个事务,因此我们改变insertB()方法的事务传播行为是"创建新事务"
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertB(){
jt.execute("insert into b(h,i) values(1,2)");
}
}