关于spring事务注解实战
1.概述
spring的事务注解@Transaction 相信很多人都用过,而@Transaction 默认配置适合80%的配置。
本篇文章不是对spring注解事务做详细介绍,而是解决一些实际场景下遇到的问题
spring事务注解的基本原理
下面针对是否需要开启事务和是否需要回滚事务在特定场景下的介绍
2.事务回滚
2.1 默认回滚策略
@Transactional public void rollback() throws SQLException { // update db throw new SQLException("exception"); }
上述代码事务会回滚吗?不会的,就算抛出SQLException了,但是之前的数据库操作依然会提交,原因就是@Transactional默认情况下只回滚RuntimeException和Error。
2.2 指定回滚异常
因此,如果要指定哪些异常需要回滚,则通过配置@Transactional中rollbackFor,例如
@Transactional(rollbackFor = {SQLException.class}) public void rollback() throws SQLException { // update db throw new SQLException("exception"); }
按照上面例子,那指定的SQLException,当抛出RuntimeException的时候,还会回滚吗?
@Transactional(rollbackFor = {SQLException.class}) public void rollback() throws SQLException { // update db throw new Runtime("exception"); }
还是会回滚的。
2.3 事务嵌套的回滚
假设有下面的逻辑,事务会回滚吗(或者说 updateA,updateB,updateC)哪些更新会提交
@Transactional public void rollback() { // updateA try{ selfProxy.innelTransaction() }catch(RuntimeException e){ //do nothing } //updateC } @Transactional public void innelTransaction() throws SQLException { // updateB throw new RuntimeException("exception"); }
答案是会回滚,因为内部事务触发回滚,当前事务被标记为 rollback-only,
当外部事务提交的时候,Spring抛出以下异常,同时回滚外部事务
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only |
2.4 小结
所以,在需要事务回滚的时候,最好还是抛出RuntimeException,并且不要在代码中捕获此类异常
三、事务传播性
@Transaction中的propagation的可以配置事务的传播性,网上介绍的很多,就直接复制一段
PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。 (也是默认策略)
PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
3.1 如何在事务中读取最新配置
有时候需要在一个事务中,读取最新数据(默认是读取事务开始前的快照)。其实很简单,只要使用上面PROPAGATION_NOT_SUPPORTED传播性就可以了。
@Transactional public void transaction() throws SQLException { // do something selfProxy.queryNewValue(); } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void queryNewValue() throws SQLException { //查询数据中的最新值 }
四、内部调用事务方法
事务注解的实质就是在创建一个动态代理,在调用事务方法前开启事务,在事务方法结束以后决定是事务提交还是回滚。
因此,直接在类内部中调用事务方法,是不会经过动态代理的
。 因此,如果要使方法B点事务生效,必须这样
4.1 解决办法
解决思路:需要在内部调用方法B的时候,找到当前类的代理类,用代理类去调用方法B
4.1.1 解决办法1
@Service public class MyService{ @Transactional public void transaction(){ // do something ((MyService) AopContext.currentProxy()).queryNewValue(); } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void queryNewValue(){ //查询数据中的最新值 } }
通过AopContext.currentProxy()可以拿到当前类的代理类,但是要使用这个时候,必须在启动类上加上
@EnableAspectJAutoProxy(exposeProxy=true)
4.1.2 解决办法2
既然是要拿到当前代理类,那其实直接在Spring的容器里面去拿也可以啊。在spring中拿Bean的方法大致有2种
通过注入
@Service public class MyService{ @Autowired private MyService self; @Transactional public void transaction() { // do something self.queryNewValue(); } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void queryNewValue() { //查询数据中的最新值 } }
tips:spring现在对一些循环依赖是提供支持的,简单来说,满足:
- Bean是单例
- 注入的方式不是构造函数注入
通过BeanFactory
@Service public class MyService implements BeanFactoryAware{ private MyService self; @Transactional public void transaction(){ // do something self.queryNewValue(); } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void queryNewValue() { //查询数据中的最新值 } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { self = beanFactory.getBean(MyService.class); } }
4.2 需要注意的地方
- 使用@Transaction注解的方法,必须用public来修饰。
- 其实不止是@Transaction,其他类似@Cacheable,@Retryable等依赖spring proxy也必须使用上述方式达到内部调用。
- @Transactional,@Async放在同一个类中,如果使用Autowire注入会循环依赖,而使用BeanFactoryAware会使得@Transactional无效