前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring事务详解

Spring事务详解

作者头像
麋鹿大哥
发布2020-11-09 17:47:15
8560
发布2020-11-09 17:47:15
举报

最近在项目组的业务技术分析会上,有同事遇到事务的失效的场景导致线上业务不可用。如果对Spring事务的@Transactional理解有限的话,确实很容易在开发中忽视一些细节问题,导致业务不可用的Bug。既然发生了问题,那么必然是要总结和反省的,然后我今天这里有时间总结一下各种事务失效的问题。

@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
      • Isolation.DEFAULT:使用底层数据库默认的隔离级别。
      • Isolation.READ_UNCOMMITTED:读未提交
      • Isolation.READ_COMMITTED:读已提交
      • Isolation.REPEATABLE_READ:可重复读
      • Isolation.SERIALIZABLE:可串行化
  • timeout
    • timeout :事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
  • readOnly
    • readOnly :指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
  • rollbackFor
    • rollbackFor :用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
  • noRollbackFor
    • noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

发生事务失效的问题

一、数据库本身的问题

为什么这么说,因为现在大多数的企业开发当中选择数据库来说,MySQL可谓是如火如荼,MySQL5.5之前默认的存储引擎是MyISAM,这个存储引擎是不支持事务的,只有Innodb存储引擎支持事务。

二、Bean没有注入到IOC容器中的问题

@Transactional采用的是AOP的方式去处理事务的问题,如果Bean本身没有被IOC容器注册的话,那么事务肯定也是不生效的。例如:

代码语言:javascript
复制
/**
 * @author zhanbo
 * @version 1.0
 * @date 2020/11/5 16:51
 * @describe
 */
//@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private TestMapper testMapper;

    /**
     * 测试事务失效1
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testTransactional1() {
        UserEntity userEntity = new UserEntity();
        userEntity.setName("测试事务失效1");
        userEntity.setMoney(1000);
        testMapper.insert(userEntity);
        int i = 10/0;
    }
}

TestService没有@Service注解,此时testTransactional1()方法的事务是会失效的。当然这种问题显然是容易发现的,一般大家都不会犯这种错误。

3、@Transactional 应用在非 public 修饰的方法上

以下来自 Spring 官方文档:

When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

意思就是 @Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。

之所以会失效是因为在Spring AOP 代理时,如上图所示 TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSourcecomputeTransactionAttribute 方法,获取Transactional 注解的事务配置信息。

此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。

注意:protectedprivate 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错。

4、自调用问题

代码语言:javascript
复制
/**
 * @author zhanbo
 * @version 1.0
 * @date 2020/11/5 16:51
 * @describe
 */
@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private TestMapper testMapper;
    /**
     * 自调用事务失效
     */
    @Override
    public void testTransactional1() {
        testTransactional();
    }
    /**
     *
     */
    @Transactional(rollbackFor = Exception.class)
    public void testTransactional(){
        UserEntity userEntity = new UserEntity();
        userEntity.setName("测试事务失效1");
        userEntity.setMoney(1000);
        testMapper.insert(userEntity);
        int i = 10/0;
    }
}

testTransactional()方法中抛出了异常,但是事务并没有回滚。这是一种常见的自调用事务失效问题。解决方案,在testTransactional1()方法上加上@Transactional注解,因为Transactional当中的传播机制是新建一个事务所以加上这个注解即可解决问题。

5、异常被吃了

代码语言:javascript
复制
/**
 * @author zhanbo
 * @version 1.0
 * @date 2020/11/5 16:51
 * @describe
 */
@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private TestMapper testMapper;

    /**
     * 异常被吞
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testTransactional1() {
        UserEntity userEntity = new UserEntity();
        userEntity.setName("测试事务失效1");
        userEntity.setMoney(1000);
        testMapper.insert(userEntity);
        try{
            int i = 1/0;
        }catch (Exception e){
            System.out.println(e);
        }
    }
}

6、@Transactional 注解属性 rollbackFor 设置错误

rollbackFor可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。

代码语言:javascript
复制
/**
 * @author zhanbo
 * @version 1.0
 * @date 2020/11/5 16:51
 * @describe
 */
@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private TestMapper testMapper;

    /**
     * 异常类型不一致问题
     */
    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void testTransactional1() throws Exception {
        UserEntity userEntity = new UserEntity();
        userEntity.setName("测试事务失效1");
        userEntity.setMoney(1000);
        testMapper.insert(userEntity);
        try{
            int i = 1/0;
        }catch (Exception e){
            throw new Exception("发生异常");
        }
    }
}

7、@Transactional 注解属性 propagation 设置错误

这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。

  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
代码语言:javascript
复制
/**
 * @author zhanbo
 * @version 1.0
 * @date 2020/11/5 16:51
 * @describe
 */
@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private TestMapper testMapper;

    /**
     * 不支持事务
     */
    @Override
    @Transactional(rollbackFor = RuntimeException.class,propagation = Propagation.NOT_SUPPORTED)
    public void testTransactional1() throws Exception {
        testTransactionalApi();
    }
}

总结

在业务开发中,@Transactional使用的还是比较多的,大家要注意的点就是贴切自己的业务场景去使用事务。

注意点:

  • 在内部方法调用时,注意事务的传播机制和修饰符非public方法不可用
  • 在处理异常机制的时候,注意rollbackFor默认是RuntimeException,如果是其他继承Exception的异常需要修改rollbackFor
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-11-06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • @Transactional属性介绍
  • 发生事务失效的问题
    • 一、数据库本身的问题
      • 二、Bean没有注入到IOC容器中的问题
        • 3、@Transactional 应用在非 public 修饰的方法上
          • 4、自调用问题
            • 5、异常被吃了
              • 6、@Transactional 注解属性 rollbackFor 设置错误
                • 7、@Transactional 注解属性 propagation 设置错误
                • 总结
                相关产品与服务
                数据库
                云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档