有那个网站,成都明腾网站建设公司,2345网址导航下载安装到桌面,站长平台怎么添加网站Transactional注解的失效场景
引言
Transactional 注解相信大家并不陌生#xff0c;平时开发中很常用的一个注解#xff0c;它能保证方法内多个数据库操作要么同时成功、要么同时失败。使用Transactional注解时需要注意许多的细节#xff0c;不然你会发现Transactional总是…Transactional注解的失效场景
引言
Transactional 注解相信大家并不陌生平时开发中很常用的一个注解它能保证方法内多个数据库操作要么同时成功、要么同时失败。使用Transactional注解时需要注意许多的细节不然你会发现Transactional总是莫名其妙的就失效了。
下面我们从what wherewhen四个方面彻底弄明白如何回答面试官的问题。
一、什么是事务WHAT
事务Transaction一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。
这里我们以取钱的例子来讲解比如你去ATM机取1000块钱大体有两个步骤第一步输入密码金额银行卡扣掉1000元钱第二步从ATM出1000元钱。这两个步骤必须是要么都执行要么都不执行。如果银行卡扣除了1000块但是ATM出钱失败的话你将会损失1000元如果银行卡扣钱失败但是ATM却出了1000块那么银行将损失1000元。
如何保证这两个步骤不会出现一个出现异常了而另一个执行成功呢事务就是用来解决这样的问题。事务是一系列的动作它们综合在一起才是一个完整的工作单元这些动作必须全部完成如果有一个失败的话那么事务就会回滚到最开始的状态仿佛什么都没发生过一样。在企业级应用程序开发中事务管理是必不可少的技术用来确保数据的完整性和一致性。
在我们日常开发中事务分为声明式事务和编程式事务。
编程式事务
是指在代码中手动的管理事务的提交、回滚等操作代码侵入性比较强。
编程式事务指的是通过编码方式实现事务允许用户在代码中精确定义事务的边界。
即类似于JDBC编程实现事务管理。管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。
对于编程式事务管理spring推荐使用TransactionTemplate。
try {//TODO somethingtransactionManager.commit(status);
} catch (Exception e) {transactionManager.rollback(status);throw new InvoiceApplyException(异常);
}声明式事务
管理建立在AOP之上的。其本质是对方法前后进行拦截然后在目标方法开始之前创建或者加入一个事务在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务最大的优点就是不需要通过编程的方式管理事务这样就不需要在业务逻辑代码中掺杂事务管理的代码只需在配置文件中做相关的事务规则声明(或通过基于Transactional注解的方式)便可以将事务规则应用到业务逻辑中。
简单地说编程式事务侵入到了业务代码里面但是提供了更加详细的事务管理
而声明式事务由于基于AOP所以既能起到事务管理的作用又可以不影响业务代码的具体实现。
声明式事务也有两种实现方式一是基于TX和AOP的xml配置文件方式二种就是基于Transactional注解了。
GetMapping(/user)
Transactional
public String user() {int insert userMapper.insert(userInfo);
}二、Transactional可以在什么地方使用WHERE
1、Transactional注解可以作用于哪些地方
Transactional 可以作用在接口、类、类方法。
作用于类当把Transactional 注解放在类上时表示所有该类的public方法都配置相同的事务属性信息。作用于方法当类配置了Transactional方法也配置了Transactional方法的事务会覆盖类的事务配置信息。作用于接口不推荐这种使用方法因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理将会导致Transactional注解失效
Transactional
RestController
RequestMapping
public class MybatisPlusController {Autowiredprivate UserMapper userMapper;Transactional(rollbackFor Exception.class)GetMapping(/user)public String test() throws Exception {User user new User();user.setName(javaHuang);user.setAge(2);user.setSex(2);int insert userMapper.insert(cityInfoDict);return insert ;}
}2、Transactional属性详解
propagation属性
propagation 代表事务的传播行为默认值为 Propagation.REQUIRED其他的属性信息如下
Propagation.REQUIRED如果当前存在事务则加入该事务如果当前不存在事务则创建一个新的事务。( 也就是说如果A方法和B方法都添加了注解在默认传播模式下A方法内部调用B方法会把两个方法的事务合并为一个事务 Propagation.SUPPORTS如果当前存在事务则加入该事务如果当前不存在事务则以非事务的方式继续运行。Propagation.MANDATORY如果当前存在事务则加入该事务如果当前不存在事务则抛出异常。Propagation.REQUIRES_NEW重新创建一个新的事务如果当前存在事务暂停当前的事务。( 当类A中的 a 方法用默认Propagation.REQUIRED模式类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式然后在 a 方法中调用 b方法操作数据库然而 a方法抛出异常后b方法并没有进行回滚因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )Propagation.NOT_SUPPORTED以非事务的方式运行如果当前存在事务暂停当前的事务。Propagation.NEVER以非事务的方式运行如果当前存在事务则抛出异常。Propagation.NESTED 和 Propagation.REQUIRED 效果一样。
isolation 属性
isolation 事务的隔离级别默认值为 Isolation.DEFAULT。
TransactionDefinition.ISOLATION_DEFAULT
这是默认值表示使用底层数据库的默认隔离级别。对大部分数据库而言通常这值就是
TransactionDefinition.ISOLATION_READ_UNCOMMITTED
该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读不可重复读和幻读因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
TransactionDefinition.ISOLATION_READ_COMMITTED
该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读这也是大多数情况下的推荐值。
TransactionDefinition.ISOLATION_REPEATABLE_READ
该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
TransactionDefinition.ISOLATION_SERIALIZABLE
所有的事务依次逐个执行这样事务之间就完全不可能产生干扰也就是说该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
timeout 属性
timeout 事务的超时时间默认值为 -1。如果超过该时间限制但事务还没有完成则自动回滚事务。
readOnly 属性
readOnly 指定事务是否为只读事务默认值为 false为了忽略那些不需要事务的方法比如读取数据可以设置 read-only 为 true。
rollbackFor 属性
rollbackFor 用于指定能够触发事务回滚的异常类型可以指定多个异常类型。
noRollbackFor属性**
noRollbackFor抛出指定的异常类型不回滚事务也可以指定多个异常类型。
二、Transactional什么时候会失效WHEN
面试官就直接问我有没有用过Transactional我肯定不能说没用过啊十分自信的说常用。
面试官又问我在实际开发过程有没有遇到过Transactional失效的情况我肯定不能说没有啊再次十分自信的说到经常。
面试官一脸问号经常那你给我说说Transactional在什么时候会失效呢
下面的内容是我将我面试时说的失效场景整理了一下。
1、Transactional 应用在非 public 修饰的方法上
如果Transactional注解应用在非public 修饰的方法上Transactional将会失效。
之所以会失效是因为在Spring AOP 代理时TransactionInterceptor事务拦截器在目标方法执行前后进行拦截DynamicAdvisedInterceptorCglibAopProxy 的内部类的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute方法获取Transactional 注解的事务配置信息。
protected TransactionAttribute computeTransactionAttribute(Methodmethod,Class? targetClass) {// Dont allow no-public methods as required.if (allowPublicMethodsOnly() !Modifier.isPublic(method.getModifiers())) {return null;
}Modifier.isPublic会检查目标方法的修饰符是否为 public不是 public则不会获取Transactional 的属性配置信息。
注意protected、private 修饰的方法上使用 Transactional 注解虽然事务无效但不会有任何报错这是我们很容犯错的一点。
2、数据库引擎要不支持事务
数据库引擎要支持事务如果是MySQL注意表要使用支持事务的引擎比如innodb如果是myisam事务是不起作用的。
3、由于propagation 设置错误导致注解失效
在上面解读propagation 属性的时候我们知道
TransactionDefinition.PROPAGATION_SUPPORTS
如果当前存在事务则加入该事务如果当前没有事务则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
以非事务方式运行如果当前存在事务则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER
以非事务方式运行如果当前存在事务则抛出异常。
当我们将propagation 属性设置为上述三种时Transactional 注解就不会产生效果
4、rollbackFor 设置错误Transactional 注解失效
上述我们解读rollbackFor 属性的时候我们知道
rollbackFor 可以指定能够触发事务回滚的异常类型。
Spring默认抛出了未检查unchecked异常继承自 RuntimeException 的异常或者 Error才回滚事务
其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常但却期望 Spring 能够回滚事务就需要指定 rollbackFor属性。
// 希望自定义的异常可以进行回滚
Transactional(propagation Propagation.REQUIRED,rollbackForMyException.class若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类事务同样会回滚。Spring源码如下
private int getDepth(Class? exceptionClass, int depth) {if (exceptionClass.getName().contains(this.exceptionName)) {// Found it!return depth;
}// If weve gone as far as we can go and havent found it...if (exceptionClass Throwable.class) {return -1;
}
return getDepth(exceptionClass.getSuperclass(), depth 1);
}5、方法之间的互相调用也会导致Transactional失效
我们来看下面的场景
比如有一个类User它的一个方法AA再调用本类的方法B不论方法B是用public还是private修饰但方法A没有声明注解事务而B方法有。则外部调用方法A之后方法B的事务是不会起作用的。这也是经常犯错误的一个地方。
那为啥会出现这种情况其实这还是由于使用Spring AOP代理造成的因为只有当事务方法被当前类以外的代码调用时才会由Spring生成的代理对象来管理。 //TransactionalGetMapping(/user)private Integer A() throws Exception {User user new User();user.setName(javaHuang);/*** B 插入字段为 topJavaer的数据*/this.insertB();/*** A 插入字段为 2的数据*/int insert userMapper.insert(user);return insert;}Transactional()public Integer insertB() throws Exception {User user new User();user.setName(topJavaer);return userMapper.insert(user);}6、异常被你的 catch“吃了”导致Transactional失效
这种情况是最常见的一种Transactional注解失效场景 Transactionalprivate Integer A() throws Exception {int insert 0;try {User user new User();user.setCityName(javaHuang);user.setUserId(1);/*** A 插入字段为 javaHuang的数据*/insert userMapper.insert(user);/*** B 插入字段为 topJavaer的数据*/b.insertB();} catch (Exception e) {e.printStackTrace();}}如果B方法内部抛了异常而A方法此时try catch了B方法的异常那这个事务就不能正常回滚而是会报出异常
org.springframework.transaction.UnexpectedRollbackException: Transactionrolled back because it has been marked as rollback-only解决方法
第一声明事务的时候加上rollback‘exception’
第二 cath代码块里面手动回滚
总结
Transactional 注解我们经常使用但是往往我们也只是知道它是一个事务注解很多时候遇到事务注解失效的情况下我们都是一头雾水看不出个所以然来花费了很长的时间都不能解决。
通过本文了解了Transactional 注解的失效场景在以后遇到这种情况时基本就能一眼看破然后摸摸自己光滑的脑门sogaso easy
妈妈再也不用担心我找不到自己写的bug了。 分析spring事务Transactional注解在同一个类中的方法之间调用不生效的原因及解决方案
问题
在Spring管理的项目中方法A使用了Transactional注解试图实现事务性。但当同一个class中的方法B调用方法A时会发现方法A中的异常不再导致回滚也即事务失效了。
当这个方法被同一个类调用的时候spring无法将这个方法加到事务管理中。
我们来看一下生效时候和不生效时候调用堆栈日志的对比。 通过对比两个调用堆栈可以看出spring的Transactional事务生效的一个前提是进行方法调用前经过拦截器TransactionInterceptor也就是说只有通过TransactionInterceptor拦截器的方法才会被加入到spring事务管理中查看spring源码可以看到在AdvisedSupport.getInterceptorsAndDynamicInterceptionAdvice方法中会从调用方法中获取Transactional注解如果有该注解则启用事务否则不启用。 这个方法是通过spring的AOP类CglibAopProxy的内部类DynamicAdvisedInterceptor调用的而DynamicAdvisedInterceptor继承了MethodInterceptor用于拦截方法调用并从中获取调用链。
如果是在同一个类中的方法调用则不会被方法拦截器拦截到因此事务不会起作用必须将方法放入另一个类并且该类通过spring注入。
原因
Transactional是Spring提供的事务管理注解。
重点在于Spring采用动态代理(AOP)实现对bean的管理和切片它为我们的每个class生成一个代理对象。只有在代理对象之间进行调用时可以触发切面逻辑。
而在同一个class中方法B调用方法A调用的是原对象的方法而不通过代理对象。所以Spring无法切到这次调用也就无法通过注解保证事务性了。
也就是说在同一个类中的方法调用则不会被方法拦截器拦截到因此事务不会起作用。
解决方法1
将事务方法放到另一个类中或者单独开启一层取名“事务层”进行调用即符合了在对象之间调用的条件。
解决方法2
获取本对象的代理对象再进行调用。具体操作如 Spring-content.xml上下文中增加配置aop:aspectj-autoproxy expose-proxy“true”/ 在xxxServiceImpl中用(xxxService)(AopContext.currentProxy())获取到xxxService的代理类再调用事务方法强行经过代理类激活事务切面。
解决方法3
很多时候方法内调用又希望激活事务是由于同一个方法既有DAO操作又有I/O等耗时操作不想让耗时的I/O造成事务的太长耗时比如新增商品同时需要写入库存。此时可以将I/O做成异步操作如加入线程池而加入线程池的操作即便加入事务也不会导致事务太长问题可以迎刃而解。
解决方法4
用Autowired 注入自己 然后在用注入的bean调用自己的方法也可以
参考 https://blog.csdn.net/ligeforrent/article/details/79996797
https://www.jianshu.com/p/2e4e1007edf2