当@Transactional遇上DDL的事务回滚问题

1 相关知识简介

1.1 DML和DDL的简介

1.1.1 DML简介

  • DML(Data Manipulation Language)数据操纵语言,用于操作数据库对象中包含的数据,操作的对象是记录。

  • 主要命令:insert、delete、update。

1.1.2 DDL简介

  • DDL(Data Definition Language)数据定义语言,主要用于定义或改变表结构。用于定义和管理数据库中的所有对象的语言,对数据库中的某些对象(例如database,table)进行管理。操作对象包括数据库本身以及数据库对象,如表、视图等等。DDL操作是隐式提交的,不能回滚。

  • 主要命令:create、alter、drop、truncate。

注意:DDL操作是隐性提交的!不能rollback

1.2 Spring支持的7种传播特性

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

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

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

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

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

1.3 嵌套事务

1.3.1 propagation = Propagation.NESTED

        嵌套事务内部事务和外部事务是有关联的,内部事务的提交和回滚依赖与外部事务,与外部事务是一个整体,即核心思想就是子事务不会独立提交,而是取决于父事务,当父事务提交,那么子事务才会随之提交;如果父事务回滚,那么子事务也回滚。

        如果想实现内部事务回滚,外部事务顺利进行。可以将内部嵌套事务放在try/catch中,并且不继续往上抛异常。

1.3.2 propagation = Propagation.REQUIRES_NEW

         Propagation.REQUIRES_NEW 内层事务执行完就立即提交,与外层事务不是一个整体。

可以参考以下内容:

        (1)spring中12种@Transactional的失效场景(小结)_java_脚本之家

        (2)【spring】spring 的事务(transaction) 四 嵌套事务PROPAGATION_NESTED_云川之下的博客-CSDN博客_嵌套事务

2 代码示例

示例代码:

@Service
public class UserServiceImpl implements userService {

    @Autowired
    private UserMapper userMapper;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void add(User user) throws Exception {
        // 步骤1
        userMapper.insertUser(user);
        String tableName = "user_1";
        List<Attribute> columnList = new ArrayList<>();
        columnList.addAll(user.getCode());
        // 步骤2
        userMapper.createTable(tableName,columnList);
        // 步骤3
        userMapper.insertUserRole(user.getRole());
    }
}

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

?解决方案:(调整执行顺序)

@Service
public class UserServiceImpl implements userService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RoleService roleService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void add(User user) throws Exception {
        String tableName = "user_1";
        List<Attribute> columnList = new ArrayList<>();
        columnList.addAll(user.getCode());
        // 步骤2
        userMapper.createTable(tableName,columnList);
        // 步骤1
        userMapper.insertUser(user);
        // 步骤3
        userMapper.insertUserRole(user.getRole());
    }
}

步骤1和步骤2交换顺序后,如果步骤3出现错误,步骤1是可以回滚的,步骤2隐式提交。。。如何解决呢?

1. 手动补偿步骤2,代码如下:

@Service
public class UserServiceImpl implements userService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RoleService roleService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void add(User user) throws Exception {
        String tableName = "user_1";
        List<Attribute> columnList = new ArrayList<>();
        columnList.addAll(user.getCode());
        try{
            // 步骤2
            userMapper.createTable(tableName,columnList);
            // 步骤1
            userMapper.insertUser(user);
            // 步骤3
            userMapper.insertUserRole(user.getRole());
        catch(Exception e){
            // 查询表是否存在
            int count = userMapper.selectTable(tableName);
            if(count>0){
                // 步骤4 删除步骤2创建的表
                userMapper.dropTable(tableName);
            }
            // 解决异常被catch,造成事务失效
            throw new RuntimeException("操作失败");
        }
    }
}

如果步骤3出错,步骤2被步骤4手动补偿掉,步骤1无法回滚,因为步骤4是DDL语句提交了事务,该如何解决呢?如下:

√最终解决方案:

可以将步骤3和步骤1放到别的类中以嵌套事物的方式,内部事务的回滚不影响外部事务(DDL不支持事务),代码如下:

方法一:(嵌套事务)

@Service
public class UserServiceImpl implements userService {

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

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void add(User user) throws Exception {
        String tableName = "user_1";
        List<Attribute> columnList = new ArrayList<>();
        columnList.addAll(user.getCode());
        // 步骤2
        userMapper.createTable(tableName,columnList);
        try{
            roleService.doOtherThing();
        catch(Exception e){
            // 查询表是否存在
            int count = userMapper.selectTable(tableName);
            if(count>0){
                // 步骤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)
    @Override
    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)
    @Override
    public void add(User user) throws Exception {
        String tableName = "user_1";
        List<Attribute> columnList = new ArrayList<>();
        columnList.addAll(user.getCode());
        // 步骤2
        userMapper.createTable(tableName,columnList);
        try{
            roleService.doOtherThing();
        catch(Exception e){
            // 查询表是否存在
            int count = userMapper.selectTable(tableName);
            if(count>0){
                // 步骤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());
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_48568302/article/details/129156366