说明:
(1)在【Spring JDBC与事务管理6:Spring声明式事务一:声明式事务配置;】 中,我们知道propagation属性设置为“REQURIED”就是开启声明式事务;但是,对于propagation属性的详细内容并不了解;所以,本篇博客就来详细介绍下事务传播行为的内容;
(2)在实际开发中,我们很少去设置事务传播方式(行为);只是,在面试的时候可能会被问到;所以,本篇博客的内容就作为一个扩展知识吧;
(3)本篇博客的代码沿用自【Spring JDBC与事务管理6:Spring声明式事务一:声明式事务配置;】;
目录
(1)创建BatchService类:该类里,添加了两个新增的方法;
(2)修改EmployeeService类:增加BatchService属性,并调用BatchService中的方法;
(3)在IoC容器中配置BatchService对象;在EmployeeService对象中注入BatchService对象;设置BatchService两个方法的事务传播方式;
一:事务传播行为简介
(1)事务传播行为:可以使用XML的方式配置;
(2) 事务传播行为:也可以使用注解的方式配置;【使用注解方式,开发声明式事务】在下篇博客中会介绍;
(3)这儿不懂【事务传播行为】没事,接下来的案例会更好的说明;
二:为演示【事务传播行为】,而准备的案例;
代码沿用自【Spring JDBC与事务管理6:Spring声明式事务一:声明式事务配置;】;
(1)创建BatchService类:该类里,添加了两个新增的方法;
package com.imooc.spring.jdbc.service; import com.imooc.spring.jdbc.dao.EmployeeDao; import com.imooc.spring.jdbc.entity.Employee; import java.util.Date; public class BatchService { private EmployeeDao employeeDao; public void importJob1() { for (int i = 1; i <= 10; i++) { Employee employee = new Employee(); employee.setEno(8000 + i); employee.setEname("研发部员工" + i); employee.setSalary(4000f); employee.setDname("研发部"); employee.setHiredate(new Date()); employeeDao.insert(employee); } } public void importJob2() { for (int i = 1; i <= 10; i++) { Employee employee = new Employee(); employee.setEno(9000 + i); employee.setEname("市场部员工" + i); employee.setSalary(4500f); employee.setDname("市场部"); employee.setHiredate(new Date()); employeeDao.insert(employee); } } public EmployeeDao getEmployeeDao() { return employeeDao; } public void setEmployeeDao(EmployeeDao employeeDao) { this.employeeDao = employeeDao; } }
说明:
(1)在实际项目中,业务肯定会很复杂,自然会有很多业务方法;而自然,也会有很多新增、修改删除的业务方法。而这类新增操作的业务方法,都是需要进行事务控制的;
(2)BatchService类的主要的功能就是批量导入;
(3)比如,BatchService类中的,inportJob1()和importJob2()这两个新增的方法,按理说都是需要进行业务控制的;
(2)修改EmployeeService类:增加BatchService属性,并调用BatchService中的方法;
package com.imooc.spring.jdbc.service; import com.imooc.spring.jdbc.dao.EmployeeDao; import com.imooc.spring.jdbc.entity.Employee; import java.util.Date; public class EmployeeService { private EmployeeDao employeeDao; private BatchService batchService; public void batchImport() { for (int i = 1; i <= 10; i++) { // if (i == 3) { // throw new RuntimeException("一个意料之外的异常。"); // } Employee employee = new Employee(); employee.setEno(8000 + i); employee.setEname("员工" + i); employee.setSalary(4000f); employee.setDname("市场部"); employee.setHiredate(new Date()); employeeDao.insert(employee); } } /** * 调用BatchService这个【批量导入类】的批量导入方法; */ public void startImportJob() { batchService.importJob1(); batchService.importJob2(); } public EmployeeDao getEmployeeDao() { return employeeDao; } public void setEmployeeDao(EmployeeDao employeeDao) { this.employeeDao = employeeDao; } public BatchService getBatchService() { return batchService; } public void setBatchService(BatchService batchService) { this.batchService = batchService; } }
说明:
(1)在EmployeeService类中,新增了startImportJob()方法,该方法中,调用了BatchService类中的方法;
(2)为了能实现(1)中的需求,在EmployeeService类中增加了BatchService属性,并添加对应的get和set方法;
(3)在IoC容器中配置BatchService对象;在EmployeeService对象中注入BatchService对象;设置BatchService两个方法的事务传播方式;
三:初始演示一:【事务传播行为】初体验;
(1)情况说明:
(2)测试、运行
(3)分析
可以发现,上面涉及到了方法的嵌套调用;如果代码正常运行、没有出现问题,其结果看着是OK的;但是,在实际开发中上面这种配置是有问题的;
因为,importJob1()和importJob2(),只需要各自保证自己就行了;这两个方法之间应该互不影响;如下:
![]()
![]()
……………………………………………………
即可以发现,importJob1()、importJob2(),startImportJob()三个方法是嵌套调用的;然后,三个方法的事务传播方式均设置成了“REQURIED”;此时,通过运行结果可以发现,importJob1()、importJob2()是一荣俱荣、一损俱损的,二者并没有做到互不影响;
四:【事务传播行为】的七种类型;
1.七种【事务传播行为】;
(1)【REQURIED】:事务传播行为的默认值:如果我们不设置事务传播方式,那么就默认是【REQURIED】;
● 当【设置了事务的方法】进行嵌套时,如果【外侧方法】有事务,那么【内侧被调用的方法】就会加入到外侧方法的事务中;如下图所示:
● 【REQURIED】详细分析;
(2)【REQURIES_NEW】:方法会创建新的事务;
● 【REQURIES_NEW】:即便【外侧方法】有事务,【内侧被调用的方法】依旧会给自己创建一个新事务;如下案例演示;
然后,重新运行;
● 【REQURIES_NEW】:详细分析;
(3)【NOT_SUPPORTED】:如果【外侧方法】设置了事务,则把当前事务挂起,以非事务方式执行【内侧被调用的方法】;这种策略无法保证数据的完整性,不太安全;
(4)【SUPPORTS】:如果【外侧方法】设置了事务,【内侧被调用的方法】就用这个事务;如果没有事务,就不使用事务;因为,【新增、修改、删除】不使用事务会存在风险,所以这种无法保证数据的完整性,不太安全;
(5)【MANDATORY】:如果【外侧方法】设置了事务,【内侧被调用的方法】就用这个事务;如果【外侧方法】没有设置事务,就报错;
PS:mandatory:n:受托者;adj:强制性的; 强制的; 法定的; 义务的;
(6)【NEVER】:如果【外侧方法】设置了事务,就报错;如果【外侧方法】没有设置事务,【内侧被调用的方法】就以非事务方式执行;这个几乎不用;
(7)【NESTED】:如果【外侧方法】设置了事务,【内侧被调用的方法】就用这个事务;如果【外侧方法】没有设置事务,【内侧被调用的方法】就采用【REQURIED】的方式;
……………………………………………………
2.summary
● 根据不同的业务需求,设置不同的事务传播行为;在实际开发中,【REQURIED】、【REQURIES_NEW】、【NOT_SUPPORTED】使用较多;
● 日常开发中,大多数我们使用【REQURIED】,因为我们一般要保证一个Service方法中的内容要么全部OK,要么什么也不做;
● 少部分情况下,使用【REQURIES_NEW】;
● 【NOT_SUPPORTED】多用于查询方法上;仔细一想,因为【新增、修改、删除】存在出错的风险,一旦出错其事务就会回滚;如果,【新增、修改删除】和【查询】混合在一起时,【NOT_SUPPORTED】可以把【查询】从【整体的潜在的回滚风险】中,拎出来;