首先感谢网友的文章
Spring事务失效的2种情况
JDK动态代理给Spring事务埋下的坑
前提知识: 两个前提
1 注解使用 spring容器的事务管理注解
@org.springframework.transaction.annotation.Transactional
2.方法开启了事务注解之后,需要异常的触发,
如果在方法内部捕获了异常,即使你开启了事务,也是没法事务回滚的
错误的使用:下面这种捕获了异常 , saveEmployee 方法里面的即使有事务,也是不会回滚的
@Transactional() //开启事务,捕获了异常,没法触发,所以有注解也没作用
public void save(){
// 捕获了异常,没法触发,所以有注解也没作用
try {
// 捕获了异常,没法触发,所以有注解也没作用
this.saveEmployee();
}catch (Exception e){
e.printStackTrace();
}
}
@Transactional()
public void saveEmployee(){
Employee employee = new Employee();
employee.setName("zhangsan");
employee.setAge("26";
employeeDao.save(employee);
throw new RuntimeException();
}
我们平时在java开发中经常使用的是 Spring 的事务回滚,而且我们大多数情况下都是使用注解为主的,最近工作中,方法内部调用,事务没有起效,走了一些弯路,今天来总结一下.
方法内部调用是指: 同一个类,两个方法, 外层方法( 暂且叫 A 方法 )调用内层方法(B 方法)
使用默认的事务处理方式
因为在java的设计中,它认为不继承RuntimeException的异常是”checkException”或普通异常,如IOException,这些异常在java语法中是要求强制处理的。
对于这些普通异常,spring默认它们都已经处理,所以默认不回滚。
可以添加rollbackfor=Exception.class来表示所有的Exception都回滚。
内部调用
不带事务的方法调用该类中带事务的方法,不会回滚。因为spring的回滚是用过代理模式生成的,如果是一个不带事务的方法调用该类的带事务的方法,直接通过this.xxx()调用,而不生成代理事务,所以事务不起作用。常见解决方法,拆类。
内部调用没有发生我们期待中的事务回滚:**
spring中在一个拥有事务的方法A中调用另一个会挂起事务并创建新事务的方法B,
如果使用this调用这个方法B, 此时方法B抛出了一个异常,
此时的方法B的事务会失效的。并不会回滚。**
代码如下:
@Service
public class EmployeeService {
@Autowired
private EmployeeDao employeeDao;
public void save(){
try {
//此处this调用不会开启事务,数据会被保存
this.saveEmployee();
}catch (Exception e){
e.printStackTrace();
}
}
@Transactional()
public void saveEmployee(){
Employee employee = new Employee();
employee.setName("zhangsan");
employee.setAge("26";
employeeDao.save(employee);
throw new RuntimeException();
}
}
问题原因: JDK的动态代理
JDK的动态代理。只有被动态代理直接调用时才会产生事务。
在SpringIoC容器中返回的调用的对象是代理对象而不是真实的对象。
而这里的this是EmployeeService真实对象而不是代理对象。
方法1:
在方法A上开启事务,方法B不用事务或默认事务,
并在方法A的catch中throw new RuntimeException();
(在没指定rollbackFor时,默认回滚的异常为RuntimeException),这样使用的就是方法A的事务。(一定要throw new RuntimeException();否则异常被捕捉处理,同样不会回滚。)
如下:
@Transactional() //开启事务
public void save(){
try {
this.saveEmployee();
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException();
}
}
方法2:
方法2、方法A上可以不开启事务,方法B上开启事务,并在方法A中将this调用改成动态代理调用(AopContext.currentProxy()),如下:
public void save(){
try {
EmployeeService proxy =(EmployeeService) AopContext.currentProxy();
proxy.saveEmployee();
}catch (Exception e){
e.printStackTrace();
}
}
另外还需要注意: 使用动态代理时候记得,在启动类配置开启代理
@EnableTransactionManagement
@EnableAspectJAutoProxy(exposeProxy = true)
@EnableTransactionManagement
@SpringBootApplication
@EnableFeignClients(basePackages={"com.***"})
@EnableEurekaClient
@MapperScan("com.***.production.order.dao.mapper")
@ComponentScan(basePackages={"com.***"})
// 暴露代理
@EnableAspectJAutoProxy(exposeProxy = true)
public class ProductionOrderJobApplication {
protected final static Logger logger = LoggerFactory.getLogger( ProductionOrderJobApplication.class);
public static void main(String[] args) {
SpringApplication app = new SpringApplication( ProductionOrderJobApplication.class);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);
logger.info("ProductionOrderJobApplication is success!");
}
@Bean
public RequestContextListener requestContextListener(){
return new RequestContextListener();
}
}
补充一点: 拆类 的方法可以试试
最终解决方案: 拆类的方法