一. Spring事务分类
Spring 提供了两种事务管理方式:声明式事务管理和编程式事务管理。
1.1编程式事务
在 Spring 出现以前,编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中显式调用 beginTransaction()、commit()、rollback() 等事务管理相关的方法,这就是编程式事务管理。
简单地说,编程式事务就是在代码中显式调用开启事务、提交事务、回滚事务的相关方法。
1.2声明式事务
Spring 的声明式事务管理是建立在 Spring AOP 机制之上的,其本质是对目标方法前后进行拦截,并在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。而Spring 声明式事务可以采用 基于 XML 配置 和 基于注解 两种方式实现
简单地说,声明式事务是编程式事务 + AOP 技术包装,使用注解进行扫包,指定范围进行事务管理。
二. @Transacational失效的场景
2.1.数据库引擎是否支持事务( MySql的MyIsam引擎不支持事务)
2.2.注解所在的类是否被加载成Bean
2.3注解所在方法是否为public修饰的
protected TransactionAttribute computeTransactionAttribute(Method method,
Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。注意:
protected
、private
修饰的方法上使用@Transactional
注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。
2.4.是否发生了自调用问题
开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。
那为啥会出现这种情况?其实这还是由于使用
Spring AOP
代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring
生成的代理对象来管理。(具体可以查看动态代理这篇博客)来看两个示例: @Service public class OrderServiceImpl implements OrderService{ public void update(Order order){ updateOrder(order); } @Transactional public void updateOrder(Order order){ // update order } } update方法上面没有加 @Transactional 注解,调用有 @Transactional 注解的 updateOrder 方法,updateOrder 方法上的事务管用吗?不管用 再来看下面这个例子: @Service public class OrderServiceImpl implementsOrderService{ @Transactional public void update(Order order){ updateOrder(order); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateOrder(Order order){ // update order } } 这次在 update 方法上加了 @Transactional ,updateOrder 加了 REQUIRES_NEW 新开启一个事务,那么新开的事务管用么? 这两个例子的答案是:不管用! 因为它们发生了自身调用,就调该类自己的方法,而没有经过 Spring 的代理类,默认只有在外部调用事务才会生效 解决方法一:从Aop上下文获取代理 @Transactional public void update(){ logger.info("=====update()方法被调用======"); try{ //解决方案: 1,从Aop上下文获取代理 TestService proxy = (TestService) AopContext.currentProxy(); proxy.child(); } catch (Exception e) { logger. error( "update捕获了child的异常",e); } // parent自己的业务 System.out.println("========update=========="); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateOrder(){ logger.info("=====updateOrder()方法被调用======"); int i = 1/0; System.out.println("========updateOrder=========="); } 解决方法二:从Spring应用上下文获职代理对象 @Autowired private ApplicationContext ac; private TestService proxy;//代理 @PostConstruct private void init(){ this.proxy = ac.getBean(TestService.class); } @Transactional public void update(){ logger.info("=====update()方法被调用======"); try{ //解决方案2:从Spring应用上下文获职代理对象 this.proxy.updateOrder(); } catch (Exception e) { logger. error( "parent捕获了child的异常",e); } // parent自己的业务 System.out.println("========update=========="); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateOrder(){ logger.info("=====updateOrder()方法被调用======"); int i = 1/0; System.out.println("========updateOrder=========="); } ///在下面的这种情况都会失效// //child和parent使用的是同一事务,child发生错误,事务会标记为回滚,当parent运行结束时,不会提交数据,事务回滚 @Transactional public void parent(){ logger.info("=====parent()方法被调用======"); try{ //解决方案: 1,从Aop上下文获取代理 TestService proxy = (TestService) AopContext.currentProxy(); proxy.child(); } catch (Exception e) { logger. error( "parent捕获了child的异常",e); } // parent自己的业务 System.out.println("========parent=========="); } @Transactional public void child(){ logger.info("=====child()方法被调用======"); int i = 1/0; System.out.println("========child=========="); } /** * mysql:默认的隔离级别:可重复读,可以查询出数据 * oracle:不能查出来 oracle默认隔离级别应该是:read committed.能读取已提交的事务,但是存在不可重复读的问题。 * 应为还没有走事务进行提交所以不能读取到 */ @Transactional public void test(){ Order order = new Order(); insert(order); select(order); }
小记:
@Transactional等价于
@Transact ional (propagat ion=Propagati on. REQUIRED)
如果当前线程中存在事务,则使用该事务执行,如果不存在事务,则新建
个事务
@Transact ional (propagation=Propagation. REQUIRES_ NEW)
如果当前线程中存在事务,则挂起当前事务,并且新建-一个事务继续执行,
新事务执行完毕之后,唤醒之前挂起的事务,继续执行
如果当前线程不存在事务,则新建一一个事务继续执行
2.5.所用数据源是否加载了事务管理器
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
return newDataSourceTransactionManager(dataSource);
}
如上面所示,当前数据源若没有配置事务管理器,那也是白搭!
2.6.@Transactional的扩展配置propagation是否正确
这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。
TransactionDefinition.PROPAGATION_SUPPORTS
:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。TransactionDefinition.PROPAGATION_NOT_SUPPORTED
:以非事务方式运行,如果当前存在事务,则把当前事务挂起。TransactionDefinition.PROPAGATION_NEVER
:以非事务方式运行,如果当前存在事务,则抛出异常
2.7.@Transactional 注解属性 rollbackFor 设置错误
rollbackFor
可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked
异常(继承自RuntimeException
的异常)或者Error
才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。例如:
@ Transactional( rollbackFor= Exception.class)
这个配置仅限于 Throwable 异常类及其子类。
2.8.异常被你的 catch“吃了”导致@Transactional失效
这种情况是最常见的一种@Transactional注解失效场景,
@Transactional
private Integer A() throws Exception {
int insert = 0;
try {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
cityInfoDict.setParentCityId(2);
/**
* A 插入字段为 2的数据
*/
insert = cityInfoDictMapper.insert(cityInfoDict);
/**
* B 插入字段为 3的数据
*/
b.insertB();
} catch (Exception e) {
e.printStackTrace();
}
}
如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务还能正常回滚吗?答案:不能!
会抛出异常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
因为当
ServiceB
中抛出了一个异常以后,ServiceB
标识当前事务需要rollback
。但是ServiceA
中由于你手动的捕获这个异常并进行处理,ServiceA
认为当前事务应该正常commit
。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException
异常。
spring
的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit
orrollback
,事务是否执行取决于是否抛出runtime异常
。如果抛出runtime exception
并在你的业务方法中没有catch到的话,事务会回滚。在业务方法中一般不需要catch异常,如果非要catch一定要抛出
throw new RuntimeException()
,或者注解中指定抛异常类型@Transactional(rollbackFor=Exception.class)
,否则会导致事务失效,数据commit造成数据不一致,所以有些时候try catch反倒会画蛇添足。
使用try->catch捕获异常并回滚的方法