@Transactional失效,springboot事务失效

一、Spring支持的7种传播特性 

使用@Transactional注解时,可以指定propagation参数。 

Spring支持7种事务传播特性,如下:

事务行为 说明
required 默认值,支持当前事务;如果没有事务,新建一个事务
supports 支持当前事务;如果没有事务,以非事务方式执行
mandatory 支持当前事务;如果没有事务,抛异常
requires_new 新建事务;如果当前存在事务,则挂起
not_supported 以非事务方式运行;如果当前存在事务,则挂起
never 以非事务方式运行;如果当前存在事务,抛异常
nested 如果当前存在事务,则在嵌套事务内执行;无则创建新的事务

        目前只有这三种传播特性才会创建新事务:required,requires_new,nested。

        不支持事务的传播机制:supports、not_supported、never。如果配置了这三种传播方式的话,在发生异常的时候,事务是不会回滚的。

二、@Transactional失效场景

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

    @Transactional
    private void createUserPrivate(User user){
        userRepository.save(user);
    }

2、必须通过spring代理对象直接调用事务方法才能生效

由于使用Spring AOP代理实现的,因为只有当事务方法被spring代理对象直接调用时,才会由Spring生成的代理对象来管理。

@Component
public class TestServiceImpl implements TestService {
    @Resource
    TestMapper testMapper;
 
    @Transactional
    public void insertTestInnerInvoke() {
        testMapper.insert(new Test(210,20,30));
        int i = 1/0;
    }
 
 
    public void testInnerInvoke(){
        insertTestInnerInvoke();
    }
 
}

测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
 
   @Resource
   TestServiceImpl testService;
 

   @Test
   public void  testInnerInvoke(){

       // 正常回滚
      //testService.insertTestInnerInvoke();

       // 不会回滚
      testService.testInnerInvoke();

   }
}

testService.insertTestInnerInvoke();正常回滚,因为 调用 insertTestInnerInvoke事务方法 是spring代理对象(@Resource、@Autowired等注入) testService 直接调用的。
testService.testInnerInvoke(); 不会回滚,因为 调用 insertTestInnerInvoke事务方法 是一个普通方法中 间接通过 this 对象调用的。

3、事务方法异常被捕获,未向外抛异常

    @Transactional
    public void insertTestInnerInvoke() {
        try {
            testMapper.insert(new Test(210,20,30));
            int i = 1/0;
            } catch (Exception e) {
                e.printStackTrace();
            }
            
    }
 

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

事务的传播行为,默认值为 Propagation.REQUIRED。

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

TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

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

@Transactional注解默认只会对RuntimeException、Error及其子类进行回滚。

@Transactional 相当于 @Transactional(rollbackFor=RuntimeException.class) ,只对抛出的 RuntimeException 异常,才会事务回滚。

也可以配置多个异常

@Transactional(rollbackFor = {RuntimeException.class, Exception.class})
    public void insertTestInnerInvoke() {
        try {
            testMapper.insert(new Test(210,20,30));
            int i = 1/0;
            } catch (Exception e) {
                e.printStackTrace();
                throw new Exception();
            }
            
    }
 

6、事务方法里面有ddl语句

因为DDL操作会隐式提交。

DML(Data Manipulation Language)数据操作语言,用于操作数据库对象中包含的数据,操作的对象是记录。
主要命令:insert、delete、update。

DDL(Data Definition Language)数据定义语言,主要用于定义或改变表结构。操作对象包括数据库本身以及数据库对象,如表、视图等等。
主要命令:create、alter、drop、truncate。

 @Transactional(rollbackFor = Exception.class)
    @Override
    public void add(User user) throws Exception {
        // 步骤1
        userMapper.insertUser(user);
        // 步骤2
        userMapper.createTable(tableName,columnList);
        // 步骤3
        userMapper.insertUserRole(user.getRole());
    }

结论:如果步骤2的DDL语句错误,步骤1不会回滚,因为DDL操作隐式提交。

简单逻辑解决方案:(调整执行顺序)将DDL操作放在最后

最终解决方案:可以将步骤3和步骤1放到别的类中以嵌套事物的方式,内部事务的回滚不影响外部事务(DDL不支持事务,需要自己使用逻辑回滚比如步骤4),代码如下:

方法一:(嵌套事务

@Service
public class UserServiceImpl implements userService {
 
    @Autowired
    private UserMapper userMapper;
    
    @Autowired
    private RoleService roleService;
 
    @Transactional(rollbackFor = Exception.class)
    public void add(User user) throws Exception {
        // 步骤2
        userMapper.createTable(tableName,columnList);
        try{
            roleService.doOtherThing();
        }catch(Exception e){
                // 步骤4 删除步骤2创建的表
                userMapper.dropTable(tableName);

            // 防止异常被catch掉事务失效
            throw new RuntimeException("操作错误");
        }
    }
}
 
@Service
public class RoleServiceImpl implements RoleService{
    @Autowired
    private UserMapper userMapper;
 
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
    public void doOtherThing() {
        // 步骤1
        userMapper.insertUser(user);
        // 步骤3
        userMapper.insertUserRole(user.getRole());
    }
}

方法二:(新建事务

@Service
public class UserServiceImpl implements userService {
 
    @Autowired
    private UserMapper userMapper;
    
    @Autowired
    private RoleService roleService;
 
    @Transactional(rollbackFor = Exception.class)
    public void add(User user) throws Exception {
        // 步骤2
        userMapper.createTable(tableName,columnList);
        try {
            roleService.doOtherThing();
        } catch(Exception e){
            // 步骤4 删除步骤2创建的表
            userMapper.dropTable(tableName);
            // 防止异常被catch掉事务失效
            throw new RuntimeException("操作错误");
        }
    }
}
 
@Service
public class RoleServiceImpl implements RoleService{
    @Autowired
    private UserMapper userMapper;
 
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    @Override
    public void doOtherThing() {
        // 步骤1
        userMapper.insertUser(user);
        // 步骤3
        userMapper.insertUserRole(user.getRole());
    }
}

参考:

当@Transactional遇上DDL的事务回滚问题_ddl事务回滚_W@Lucky的博客-CSDN博客

你可能感兴趣的:(java,数据库,事务,spring事务)