( 三十 )、 Spring 事务失效场景

(  三十 )、 Spring 事务失效场景

 

 

1、简介

 在某些业务场景下,如果一个请求中,需要同时写入多张表的数据。为了保证操作的原子性(要么同时成功,要么同时失败),避免数据不一致的情况,我们一般都会用到事务。Spring 就用一个简单的注解:@Transactional 就能解决事务问题。但是使用不当也会使事务失效。下面我们一一说明。

 

2、失效场景介绍:

2.1、方法上未添加 @Transactional 注解,不会回滚异常:

@Service
public class TestTransactionServiceImpl implements ITransactionTestService {
    @Autowired
    private IUserDao userDao;

    @Override
    public Integer testA() {
        int i = userDao.saveUser(new MyUser("张三", 23));
        System.out.println(1 / 0);
        return i;
    }
}

如果方法上面没有添加 @Transactional 注解, 方法在执行过程中, 不会回滚异常:

 

2.2、Controller 调用 A 方法, 在A 中调用 B 方法, A 方法 未添加 注解 @Transactional,在 B上添加注解依然会失效 

@Service
public class TestTransactionServiceImpl implements ITransactionTestService {
    @Autowired
    private IUserDao userDao;

    @Override
    public Integer testA() {
        Integer i = testB();
        System.out.println(1 / 0);
        return i;
    }

    @Transactional
    public Integer testB(){
        int i = userDao.saveUser(new MyUser("张三", 23));
        return i;
    }
}

 

 如果在 方法A 上添加了注解, 调用同类中的B方法, B 未添加注解,这种情况会回滚, 如下:

@Service
public class TestTransactionServiceImpl implements ITransactionTestService {
    @Autowired
    private IUserDao userDao;

    @Override
    @Transactional
    public Integer testA() {
        Integer i = this.testB();
        System.out.println(1 / 0);
        return i;
    }
    
    public Integer testB(){
        int i = userDao.saveUser(new MyUser("张三", 23));
        return i;
    }
}

如果在 方法A 上添加了注解, 调用同类中的B方法, B 未添加注解,并且私有化B, 这种情况会回滚, 如下:

@Service
public class TestTransactionServiceImpl implements ITransactionTestService {
    @Autowired
    private IUserDao userDao;

    @Override
    @Transactional
    public Integer testA() {
        Integer i = this.testB();
        return i;
    }

    private Integer testB(){
        int i = userDao.saveUser(new MyUser("张三", 23));
        System.out.println(1 / 0);
        return i;
    }
}

 

2.3、多线程调用导致事务失效

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleService roleService;

    @Transactional
    public void add(UserModel userModel) throws Exception {
        userMapper.insertUser(userModel);
        new Thread(() -> {
            roleService.doOtherThing();
        }).start();
    }
}

@Service
public class RoleService {

    @Transactional
    public void doOtherThing() {
        System.out.println("保存role表数据");
    }
}

这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想doOtherThing方法中抛了异常,add方法也回滚是不可能的。

 

2.4、表不支持事务

数据库引擎是 myisam, 是不支持事务的。

 

2.5、错误的传播特性

其实,我们在使用@Transactional注解时,是可以指定propagation参数的。

该参数的作用是指定事务的传播特性,spring目前支持7种传播特性:

  • REQUIRED 如果当前上下文中存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值。
  • SUPPORTS 如果当前上下文存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。
  • MANDATORY 如果当前上下文中存在事务,否则抛出异常。
  • REQUIRES_NEW 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。
  • NOT_SUPPORTED 如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。
  • NEVER 如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。
  • NESTED 如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。

如果我们在手动设置propagation参数的时候,把传播特性设置错了,比如:

@Service
public class UserService {

    @Transactional(propagation = Propagation.NEVER)
    public void add(UserModel userModel) {
        saveData(userModel);
        updateData(userModel);
    }
}

 

2.6、自己吞了异常

事务不会回滚,最常见的问题是:开发者在代码中手动try...catch了异常。比如:

@Service
public class UserService {
    
    @Transactional
    public void add(UserModel userModel) {
        try {
            saveData(userModel);
            updateData(userModel);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }
}

 

2.7、手动抛了别的异常

即使开发者没有手动捕获异常,但如果抛的异常不正确,spring事务也不会回滚

public class UserService {
    
    @Transactional
    public void add(UserModel userModel) throws Exception {
        try {
             saveData(userModel);
             updateData(userModel);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new Exception(e);
        }
    }
}

上面的这种情况,开发人员自己捕获了异常,又手动抛出了异常:Exception,事务同样不会回滚。

因为spring事务,默认情况下只会回滚 RuntimeException(运行时异常)和Error(错误),对于普通的Exception(非运行时异常),它不会回滚。

 

posted @ 2021-09-26 21:59  邓维-java  阅读(308)  评论(0)    收藏  举报