你真的会用Spring事务@Transactional吗?-创新互联

你真的会用 Spring事务@Transactional 吗?
  • 经典错误案例
    • 错误的使用try catch
    • 抛出检查异常
    • 错误添加切面
    • @Transactional事务默认只能加在public的方法上
    • 调用本类方法导致传播行为失效
    • Transactional并没有保证原子性

  在Spring项目中,我们经常使用注解@Transactional来标识一个事务方法。但是,有时会发现这个事务并不是按照我们想要的方式执行。通常在以下几种情况下,你以为的事务管理可能并不是你认为的事务管理。

创新互联公司主要为客户提供服务项目涵盖了网页视觉设计、VI标志设计、成都全网营销推广、网站程序开发、HTML5响应式网站建设公司手机网站开发、微商城、网站托管及成都网站改版、WEB系统开发、域名注册、国内外服务器租用、视频、平面设计、SEO优化排名。设计、前端、后端三个建站步骤的完善服务体系。一人跟踪测试的建站服务标准。已经为成都主动防护网行业客户提供了网站维护服务。经典错误案例 错误的使用try catch

  在业务方法中使用try catch来捕获了异常,然后异常出现,会进入到catch块中,但是没有进行抛出,结果事务没有被正确的回滚。这里没有正确回滚的原因就在于,没有正确的理解Spring事务管理的原理。Spring采用了代理的方式来实现事务管理,调用的顺序是开启事务,执行目标方法,提交或回滚事务。所以虽然目标方法中出现了异常,但是却将异常直接处理了,在代理类眼中,目标方法没有抛出异常,所以事务也就正常提交。

@Transactional(rollbackFor = Exception.class)
    public void transfer(int amount) {
        try{
            serviceB.sub(amount);
            serviceA.add(amount);
        } catch (Exception e) {
            LOGGER.error("error occur");
        }
    }

  正确的解决方法有两种,一种是直接在catch中继续抛出异常,第二种就是直接告诉Spring当前事务需要rollback。

// 1
throw new RuntimeException(e);
// 2
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
抛出检查异常

  在方法中若是抛出检查异常,比如fileNotFound这种,事务则是不会回滚的,这其中的原因就在于@Transactional 注解默认的是rollbackFor是运行期异常。这也就是在阿里的开发规范中要求一定要指定rollbackFor的原因。
所以在使用这个注解的时候还是建议写明 rollbackFor 这样才能明确知道出现了什么异常才会回滚的。

@Transactional(rollbackFor = Exception.class)
错误添加切面

  Aop切面顺序导致事务不能正确回滚,原因是事务切面优先级最低,但如果自定义的切面优先级和它一样,则还是自定义切面在内层,这是若自定义切面没有正确抛出异常,在catch中吃掉了异常,此时就会出现和第一种情况类似的情况,代理类得不到异常信息,也就不会回滚。
  解决方案就是在切面中抛出异常,或者就是将自定义的切面的优先级设置为更小。但是建议使用第一种在切面中抛出异常。

@Transactional事务默认只能加在public的方法上

  非public的方法会导致事务失效。Spring为方法创建代理,添加事务通知,前提条件是该方法是public的,这点需要注意的就是要么设置方法为public,要么可以为非public的方法添加事务通知。
建议使用public方法而不是添加配置

@Bean
    public TransactionAttributeSource transactionAttributeSource(){
        return new AnnotationTransactionAttributeSource(false);
    }
调用本类方法导致传播行为失效

  同一个Service的两个方法之间调用,就会出现这个问题,原因还是在代理对象这里,我们期待的调用时一个代理类的调用,但是我们若是直接在方法中内部调用,则被调用的方法的事务失效,没有被Aop增强。

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void a (){
        b();
    }

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void b (){
        
    }

  以上的改进方案就是自己调用自己,自己注入自己。你可能看见过这样的代码,就是为了解决这个问题的。这种解决方案最常见。

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES)
    public void a (){
        service.b();
    }

  还有一种方法可以通过 AopContext 拿到代理对象,然后再调用。这里要注意,使用这种方式需要开启暴露代理。

@EnableAspectJAutoProxy(exposeProxy = true)
public class Application {}

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void a (){
 ((TestService)AopContext.currentProxy()).b();
}
Transactional并没有保证原子性

  这个问题非常常见,我们总会以为加了事务管理,尤其是加了 Propagation.REQUIRES_NEW 之后我们的事务就会在方法执行之后提交事务,或是加了 synchronized 关键字之后,这就是一个原子操作了,这是不对的哈。为什么呢?还要从代理类说起,代理类开启事务,执行目标方法,提交事务。而不管是 REQUIRES_NEW 还是 synchronized 关键字都只是作用于目标方法,即使目标方法执行成功,可是事务还是没有提交呢。

  对于 insert,delete,update,select — for update,语句来说,都是原子性的。但是 select 不是。

  解决方案就是扩大 synchronized 的范围,为整个代理方法加锁,而不是把锁加在目标方法上。也可以通过 SQL 来控制,保证操作的原子性,使用 select — for update

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


当前文章:你真的会用Spring事务@Transactional吗?-创新互联
浏览路径:http://cdiso.cn/article/dgecdc.html

其他资讯