学习笔记 - Sprging事务失效的场景

概要

记录一些Sprging事务失效的一些场景。

Sprging事务失效的场景

1. 事务方法所在的类没有被Spring管理
Spring声明式事务的实现完全依赖于Spring的AOP代理机制,未被Spring管理的类中的方法不受Spring的AOP代理管理,因此,声明式事务失效。

2. 方法没有被public 修饰。
@Transactional 只能用于 public 的方法上,否则事务不会生效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。之所以会失效的原因是因为在Spring AOP 代理时,TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute 方法,获取Transactional 注解的事务配置信息。
同理:final、static 修饰的方法也会使事务失效。

protected TransactionAttribute computeTransactionAttribute(Method method,
    Class<?> targetClass) {
        // Don't allow no-public methods as required.
        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
        return null;
}

3. 方法内部调用

@Service
public class UserServiceImpl {

    @Autowired
    UserMapper userMapper;

    public void A() {
        B();
    }

    @Transactional
    public void B() {
        userMapper.deleteById(1);
        int i = 10 / 0; //模拟发生异常
    }
    
}

同一个类中有A、B两个方法,B方法使用@Transactional注解标注,在A方法中调用了B方法,在外部调用A方法时,B方法的事务不会生效。这是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。
解决办法:
(1)引入自身bean

@Service
public class UserServiceImpl {

    @Autowired
    UserMapper userMapper;

    @Autowired
    UserServiceImpl userServiceImpl;

    public void A() {
        userServiceImpl.B();
    }

    @Transactional
    public void B() {
        userMapper.deleteById(1);
        int i = 10 / 0; //模拟发生异常
    }

}

(2) AopContext获取当前代理类
在启动类上添加注解@EnableAspectJAutoProxy(exposeProxy = true),表示是否对外暴露代理对象,即是否可以获取AopContext。然后,在业务类上使用AopContext。

@Service
public class UserServiceImpl {

    @Autowired
    UserMapper userMapper;

    public void A() {
       UserServiceImpl userServiceImpl = ((UserServiceImpl) AopContext.currentProxy());
       userServiceImpl.B();
    }

    @Transactional
    public void B() {
        userMapper.deleteById(1);
    }
}

4. 多线程调用
同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。线程B的事务回滚,不会导致线程A的事务也回滚。

@Service
public class UserServiceImpl {

    @Autowired
    UserMapper userMapper;

    @Transactional
    public void A() {
        userMapper.deleteById(1);
        new Thread(()->{
            userMapper.deleteById(2);
            int i = 10/0; //模拟发生异常        
            }).start();
    }

}

如上,A 方法中 userMapper.deleteById(1) 不会回滚。

5. 没有开启事务支持
如果是SpringBoot项目,那么SpringBoot通过DataSourceTransactionManagerAutoConfiguration自动配置类帮我们开启了事务。如果是传统的Spring项目,则需要我们自己配置。在Spring配置文件配置如下:

<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 注解式事务声明配置-->
<tx:annotation-driven transaction-manager="transactionManager" />

6. 设置了错误的事务传播特性
不同的Spring事务传播特性,可能导致事务不会发生回滚。

7. 错误的手动抛出异常
(1)使用了 try-catch 捕获异常没有向上抛出

@s1f4j
@Service
public class UserService {
    @Transactional
    public void add(UserMcdel userModel) {
        try f
        saveData(userModel);
        updateData(userModel);
    } catch(
    Exception e)

    {
        log.error(e.getMessage(), e);
    }
}

这种情况下spring事务当然不会回滚,因为开发者自己捕获了异常,又没有手动抛出,换句话说就是把异常吞
掉了。
如果想要Spring事务能够正常回滚,必须抛出它能够处理的异常。如果没有抛异常,则spring认为程序是正常的。
(2)抛出的异常不正确

@s1f4j
@Service
public class UserService {
    @Transactional
    public void add(UserMcdel userModel) {
        try f
        saveData(userModel);
        updateData(userModel);
    } catch(
    Exception e)

    {
        log.error(e.getMessage(), e);
        threw new Exception();
    }
}

上面的这种情况,开发人员自己捕获了异常,又手动抛出了异常:Exception,事务同样不会回滚。
因为spring事务,默认情况下只会回滚 RuntimeException (运行时异常)和 Error (错误),对于普通的异常(非运行时异常),spring事务不会回滚。

(3)自定义回滚了别的异常
在使用@Transactional 注解声明事务时,有时我们想自定义回滚的异常,Spring也是支持的。可以通过设置 rollbackFor 参数来设置。但是如果这个参数值设置错了,就会导致事务不会回滚。

@S1f4j
@Service
public class UserService {
    @Transactional(rollbackFor = BusinessException.class)
    public void add(UserModel userModel) throws Exception {
        saveData(userModel);
        updateData(userModel);
    }
}

如果在执行上面这段代码,保存和更新数据时,程序报错了,抛了SqlException、DuplicateKeyException等异常,事务也不会回滚。因为 BusinessException 是我们自定义的异常,SqlException、DuplicateKeyException等不属于BusinessException 。
因此,即使rollbackFor有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。
如果使用默认值,一旦程序抛出了Exception,事务不会回滚,这会出现很大的bug。所以,建议一般情况
下,将该参数设置成:Exception或Throwable。
Exception,Throwable 是父类,继承Exception,Throwable 的异常能被正常回滚。

8. 嵌套事务回滚多了


```java
public class UserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleService roleService;
    @Transactional
    public void add(UserModel userModel) throws Exception {
        userMapper.insertUser(userModel);
        roleService.addRole();
    }
}
@Service
public class RoleService {
    @Transactional(propagation = Propagation.NESTED)
    public void addRole() {
        System.out.println("保存Role表数据");
    }
}

以上情况使用了嵌套的内部事务,原本是希望调用roleService.addRole()方法时,如果出现了异常,只回addRole()方法里的内容吗,不回滚 userMapper.insertUser(userModel);但事实是,insertUser也回滚了。因为addRole()方法出现了异常,没有手动捕获,会继续往上抛,到外层add()方法捕获了异常,所以,直接回滚了整个事务,不单只回滚保存点。

public class UserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleService roleService;
    @Transactional
    public void add(UserModel userModel) throws Exception {
        userMapper.insertUser(userModel);
        try {
            roleService.addRole();
        } catch (Exception e) {
            log.error(e.getMessage(),e);
        }
    }
}
@Service
public class RoleService {
    @Transactional(propagation = Propagation.NESTED)
    public void addRole() {
        System.out.println("保存Role表数据");
    }
}

将嵌套事务放在try-catch中,手动捕获并且不继续往上抛,这样当内部嵌套事务出现异常时,只回滚内部事务,不影响外部事务。

9. 数据引擎不支持事务
例如: MySQL 使用MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值