1 spring事务的传播特性
package com.zs.service;
@Service
public class UserService {
@Autowired
private UserDao userDA0;
@Transactional
public void transfer(String fromName, String toName, Integer money) {
userDA0.out(fromName, money);
int a = 1 / 0;
userDA0.in(toName, money);
}
}
在spring中可通过使用注解@Transcation( propagation = “传播机制”)实现事务的传播,Spring中有7种传播机制。所谓事务传播机制,也就是在事务在多个方法的调用中是如何传递的,是重新创建事务还是使用父方法的事务?父方法的回滚对子方法的事务是否有影响?这些都是可以通过事务传播机制来决定的。
事务传播行为常量都是以PROPAGATION_ 开头,形如PROPAGATION_XXX。
1.默认传播:PROPAGATION_REQUIRED
- 支持当前的事务,如果当前没有事务,就新建事务;
- 如果当前已有事务,则合并为一个事务。
- 解释:如果有个父方法A和子方法B,只要有一个带有事务,那么A和B都将拥有事务。
操作1:将BlogServiceImpl和BlogServiceImpl2的事务传播机制都修改为
@Transactional(propagation=Propagation.REQUIRED)
操作2:将BlogServiceImpl事务传播机制修改为@Transactional(propagation=Propagation.NOT_SUPPORTED),BlogServiceImpl2的仍为@Transactional(propagation=Propagation.REQUIRED)
总结:当BlogServiceImpl提供事务的时候,BlogServiceImpl2的方法执行使用当前已有事务,不再新建事务;当BlogServiceImpl不创建事务的时候,BlogServiceImpl2的方法执行发现没有事务可用,自己新建事务;
2.独立事务:REQUIRES_NEW
- 如果当前已拥有事务,则把当前事务挂起,新建事务
- 该机制下的事务不受其它调用者事务的影响
- 解释:如果有个父方法A(有事务)和子方法B(有事务),如果A抛出异常,而B使用了这个声明事务,那么B仍会继续提交(不受A事务影响)
操作:将BlogServiceImpl事务传播机制为@Transactional(propagation=Propagation.REQUIRED),BlogServiceImpl2的为@Transactional(propagation=Propagation.REQUIRES_NEW)
REQUIRES_NEW为当前方法创建一个新的事务,并且当前事务先提交,然后再提交老的事务
3.NESTED
- 如果当前存在事务,它将会成为父级的一个子事务,方法结束后并没有提交,只是等待父事务结束才提交。
- 如果当前没有事务,则新建事务。
- 如果它本身异常,父级可以捕获到它的异常,而不进行回滚。正常提交。但是如果父级异常,它必然回滚。
- 解释:一切以父级事务为主
操作1:将BlogServiceImpl事务传播机制修改为@Transactional(propagation=Propagation.REQUIRED),BlogServiceImpl2的仍为@Transactional(propagation=Propagation.NESTED)
操作2:将BlogServiceImpl事务传播机制修改为@Transactional(propagation=Propagation.NOT_SUPPORTED),BlogServiceImpl2的仍为@Transactional(propagation=Propagation.NESTED)
总结:save方法创建一个事务,则再调用delete方法时,直接在该事务的基础上创建一个嵌套事务,本质上还是同一个事务,做一次提交;save方法不创建事务,则调用delete方法时,直接创建一个新的事务,单独提交。
4.SUPPORTS
- 若当前已有事务,则加入事务;
- 若当前没有事务,则以无事务进行;
- 解释:佛系事务,有就用,没有就不用了
操作1:将BlogServiceImpl事务传播机制修改为@Transactional(propagation=Propagation.REQUIRED),BlogServiceImpl2的仍为@Transactional(propagation=Propagation.SUPPORTS)
操作2:将BlogServiceImpl事务传播机制修改为@Transactional(propagation=Propagation.NOT_SUPPORTED),BlogServiceImpl2的仍为@Transactional(propagation=Propagation.SUPPORTS)
总结: SUPPORTS类型的事务传播机制,是否使用事务取决于调用方法是否有事务,如果有则直接用,如果没有则不使用事务
5.NOT_SUPPORTS
- 不支持事务,如果当前有事务,则把该事物挂起
操作:将BlogServiceImpl和BlogServiceImpl2的事务传播机制都
修改为@Transactional(propagation=Propagation.NOT_SUPPORTED)
总结:NOT_SUPPORTED相当于没有Spring事务,每条执行语句单独执行,单独提交
6.MAMDATORY
- 若当前有事务,则运行当前事务;
- 若当前没有事务,则抛异常;
- 解释:父级若没有事务,就不干了
操作:将BlogServiceImpl事务传播机制修改为@Transactional(propagation=Propagation.NOT_SUPPORTED),BlogServiceImpl2的仍为@Transactional(propagation=Propagation.MANDATORY),查看是否报错
总结:MANDATORY必须在已有事务下被调用,否则报错;NOT_SUPPORTED执行数据库层面的事务操作,故当前测试中,insert方法成功执行,delete方法的抛错并不影响insert方法的执行
7.NEVER
- 有事务就抛异常
操作:将BlogServiceImpl事务传播机制修改为@Transactional(propagation=Propagation.REQUIRED),BlogServiceImpl2的仍为@Transactional(propagation=Propagation.NEVER),查看是否报错
2 spring事务的失效
2.1 异常被吃了
@Transactional(rollbackFor = Throwable.class)
public void deal() throws FileNotFoundException {
Bank1 bank1 = bank1Service.getById(1);
Bank2 bank2 = bank2Service.getById(1);
//模拟转账
//用户1转账20给用户B
bank1.setMoney(bank1.getMoney()-20);
bank1Service.updateById(bank1);
try {
//模拟业务执行出现了异常
int i = 1 / 0;
} catch (Exception e) {
e.printStackTrace();
}
bank2.setMoney(bank2.getMoney()+20);
bank2Service.updateById(bank2);
}
异常被 try { } catch () { } 包裹了。结果异常没有回滚。原因:异常被方法内部处理了。解决办法:被事务管理的方法有异常直接抛出去,不要用try {} catch(){} 处理。
2.2 错误的标注异常类型
@Transactional
public void updateProductStockCountById(Integer stockCount, Long id){
try{
productDao.updateProductStockCountById(stockCount, id);
}catch(Exception e){
throw new Exception("扣减库存异常");
}
}
在updateProductStockCountById()方法中捕获了异常,并且在异常中抛出了Exception类型的异常,此时,updateProductStockCountById()方法事务的回滚会失效。
为何会失效呢?这是因为Spring中对于默认回滚的事务异常类型为RuntimeException,上述代码抛出的是Exception异常。
默认情况下,Spring事务中无法捕获到Exception异常,所以此时updateProductStockCountById()方法事务的回滚会失效。
此时可以手动指定updateProductStockCountById()方法标注的事务异常类型,如下所示。
@Transactional(rollbackFor = Exception.class)
这里,需要注意的是:Spring事务注解@Transactional中的rollbackFor属性可以指定 Throwable 异常类及其子类。
2.3 方法的事务传播类型不支持事务
如果内部方法的事务传播类型为不支持事务的传播类型,则内部方法的事务在Spring中会失效。
2.4 未配置事务管理器
如果在项目中没有配置Spring的事务管理器,即使使用了Spring的事务管理功能,Spring的事务也不会生效。例如,没有在项目的配置类中配置如下代码。
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
2.5 方法没有被public修饰
如果事务所在的方法没有被public修饰,此时Spring的事务会失效,例如,如下代码所示。
@Service
public class ProductService {
@Autowired
private ProductDao productDao;
@Transactional(propagation = Propagation.REQUIRES_NEW)
private void updateProductStockCountById(Integer stockCount, Long id){
productDao.updateProductStockCountById(stockCount, id);
}
}
虽然ProductService上标注了@Service注解,同时updateProductStockCountById()方法上标注了@Transactional(propagation = Propagation.REQUIRES_NEW)注解。
但是,由于updateProductStockCountById()方法为内部的私有方法(使用private修饰),那么此时updateProductStockCountById()方法的事务在Spring中会失效。
2.6 事务方法未被Spring管理
如果事务方法所在的类没有加载到Spring IOC容器中,也就是说,事务方法所在的类没有被Spring管理,则Spring事务会失效,示例如下。
public class ProductService {
@Autowired
private ProductDao productDao;
@Transactional
public void updateProductStockCountById(Integer stockCount, Long id){
productDao.updateProductStockCountById(stockCount, id);
}
}
ProductService类上没有标注@Service注解,Product的实例没有加载到Spring IOC容器中,就会造成updateProductStockCountById()方法的事务在Spring中失效。
2.7 数据库不支持事务
Spring事务生效的前提是所连接的数据库要支持事务,如果底层的数据库都不支持事务,则Spring的事务肯定会失效。例如,如果使用的数据库为MySQL,并且选用了MyISAM存储引擎,则Spring的事务就会失效。
2.8 同一个类中非事务管理的方法调用了被事务管理的方法
如果同一个类中的两个方法分别为A和B,方法A上没有添加事务注解,方法B上添加了 @Transactional事务注解,方法A调用方法B,则方法B的事务会失效。例如,如下代码所示。
@Transactional(rollbackFor = Throwable.class)
public void deal() throws FileNotFoundException {
Bank1 bank1 = bank1Service.getById(1);
Bank2 bank2 = bank2Service.getById(1);
//模拟转账
//用户1转账20给用户B
bank1.setMoney(bank1.getMoney() - 20);
bank1Service.updateById(bank1);
//模拟业务执行出现了异常
new FileInputStream(new File("abcd://abcd.text"));
bank2.setMoney(bank2.getMoney() + 20);
bank2Service.updateById(bank2);
}
创建一个NoTranMethod类 方法没有加 @Transactional 注解:
@Component
public class NoTranMethod {
@Autowired
private DealServiceImpl dealService;
public void deal() throws FileNotFoundException {
dealService.deal();
}
}
@Autowired
private NoTranMethod noTranMethod;
@Test
void contextLoads() throws FileNotFoundException {
noTranMethod.deal();
}
测试 非同一个类中非事务管理的方法调用被事务管理的方法生效了。
又进行了如下测试:
public void noTranDeal() throws FileNotFoundException {
deal();
}
@Transactional(rollbackFor = Throwable.class)
public void deal() throws FileNotFoundException {
Bank1 bank1 = bank1Service.getById(1);
Bank2 bank2 = bank2Service.getById(1);
//模拟转账
//用户1转账20给用户B
bank1.setMoney(bank1.getMoney() - 20);
bank1Service.updateById(bank1);
//模拟业务执行出现了异常
new FileInputStream(new File("abcd://abcd.text"));
bank2.setMoney(bank2.getMoney() + 20);
bank2Service.updateById(bank2);
}
事务没有生效 什么情况?原因: Spring的声明式事务是基于AOP实现的,而AOP又是基于动态代理,也就说被事务管理的方法将会走代理方法。但是如果是同一个类中非事务管理的方法调用被事务管理的方法是不会调用代理方法的。解决办法:注入代理对象,调用代理对象的方法:
@Service
public class DealServiceImpl {
@Autowired
private Bank1Service bank1Service;
@Autowired
private Bank2Service bank2Service;
@Autowired
private DealServiceImpl dealService;
public void noTranDeal() throws FileNotFoundException {
dealService.deal();
}
@Transactional(rollbackFor = Throwable.class)
public void deal() throws FileNotFoundException {
Bank1 bank1 = bank1Service.getById(1);
Bank2 bank2 = bank2Service.getById(1);
//模拟转账
//用户1转账20给用户B
bank1.setMoney(bank1.getMoney() - 20);
bank1Service.updateById(bank1);
//模拟业务执行出现了异常
new FileInputStream(new File("abcd://abcd.text"));
bank2.setMoney(bank2.getMoney() + 20);
bank2Service.updateById(bank2);
}
}
注意 自动装配了 本类对象 也就是动态代理生成的代理类。事务生效了:重点: Spring 采用动态代理(AOP)生成代理对象,只有在代理对象之间进行调用时,才可以触发切面逻辑。