Springboot结合Mybatis-Plus实现业务撤销回滚功能

 更新时间:2024年12月17日 11:29:32   作者:_院长大人_  
本文介绍了如何在Springboot结合Mybatis-Plus实现业务撤销回滚功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

操作记录表

CREATE TABLE `operation_record` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增id',
  `check_id` bigint(20) NOT NULL COMMENT '检测id',
  `status` varchar(64) DEFAULT NULL COMMENT '检测old状态-检测new状态',
  `operation` varchar(255) NOT NULL COMMENT '操作事件',
  `deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除',
  `created_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `created_by_id` bigint(20) DEFAULT NULL COMMENT '创建者id',
  `created_by_name` varchar(255) DEFAULT NULL COMMENT '创建者名称',
  `old_data` json DEFAULT NULL COMMENT '存储操作前的旧数据,JSON格式',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注信息,用于记录额外的操作说明',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='操作记录';

操作记录实体类

package com.applets.manager.core.model.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Builder;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * <p>
 * 操作记录实体类
 * </p>
 *
 * @author MybatisGenerator
 * @since 2024-05-15
 */
@Data
@Builder
@TableName("check_status_record")
public class CheckStatusRecord implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 自增id
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 检测id(业务主键,可以自行修改)
     */
    @TableField("check_id")
    private Long checkId;

    /**
     * 检测状态
     */
    @TableField("status")
    private String status;

    /**
     * 操作事件
     */
    @TableField("operation")
    private String operation;

    /**
     * 是否删除
     */
    @TableField("deleted")
    private Boolean deleted;

    /**
     * 创建时间
     */
    @TableField("created_time")
    private LocalDateTime createdTime;

    /**
     * 创建者id
     */
    @TableField("created_by_id")
    private Long createdById;

    /**
     * 创建者名称
     */
    @TableField("created_by_name")
    private String createdByName;

    /**
     * 老数据
     */
    @TableField("old_data")
    private String oldData;


    /**
     * 备注
     */
    @TableField("remark")
    private String remark;

}

操作记录表数据存储实例

执行业务,存入操作记录并且存入快照数据

此处的我存在oldData的字段类型为map,这样oldData可以一次性存入多个修改记录,就比如说如下方法enterEvaluationPrice修改了ylcheck和saleConditions

  • key为YlCheck.class.getName() + “-” + “ylCheckMapper”+"-update"即(存入的对象全类名-容器中操作对应类的mapper名称-数据库操作类型)
  • value 为历史快照数据
  • 主要使用方法就是在做数据库操作之前,把要操作的历史数据查询出来,json的方法序列化,存入操作记录表中的oldData的字段,因为我这里是操作了多个表,所有我用map的方式存储多个
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result enterEvaluationPrice(EnterPriceReqVo req) {
        Integer deliveryDays = req.getDeliveryDays();

        Long id = req.getId();
        BigDecimal acquisitionPrice = req.getAcquisitionPrice();
        BigDecimal multiply = acquisitionPrice.multiply(new BigDecimal(10000));

        try {

            HashMap<String, Object> oldDataMap   = new HashMap<>();

            YlCheck ylCheck = ylCheckMapper.selectById(id);

            if (ylCheck == null) {

                return Result.failure(CommonResultStatus.VEHICLE_REPORT_NOT_FIND);
            }

            if (ObjectUtils.isNotEmpty(deliveryDays)) {
                LambdaUpdateWrapper<SaleConditions> saleConditionsLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
                LambdaUpdateWrapper<SaleConditions> qw = saleConditionsLambdaUpdateWrapper
                        .eq(SaleConditions::getCarId, ylCheck.getCheckCarId());
                saleConditionsLambdaUpdateWrapper.set(SaleConditions::getDeliveryDays, deliveryDays);
                List<SaleConditions> saleConditions = saleConditionsMapper.selectList(qw);
                oldDataMap.put(SaleConditions.class.getName() + "-" + "saleConditionsMapper"+"-update", saleConditions); //存入历史数据
                saleConditionsMapper.update(null, saleConditionsLambdaUpdateWrapper);
            }


            oldDataMap.put(YlCheck.class.getName() + "-" + "ylCheckMapper"+"-update", ylCheck);//存入历史数据
            //其他业务代码
            ylCheckMapper.update(null, new LambdaUpdateWrapper<YlCheck>()
                    .eq(YlCheck::getId, ylCheck.getId())
                    .set(YlCheck::getStatus, to.name())//变更后的状态
            );

            // 存入操作记录
            CheckStatusRecord checkStatusRecord = CheckStatusRecord.builder()
                    .checkId(ylCheck.getId())
                    .status(from.name() + "-" + to.name())
                    .operation(event.name())
                    .createdByName(localUserInfo.getUserName())
                    .createdTime(LocalDateTime.now())
                    .deleted(false)
                    .oldData(JSON.toJSONString(oldDataMap))
                    .build();
            checkStatusRecordMapper.insert(checkStatusRecord);
            //其他业务代码
    }

评估回滚请求对象

  • 此处就传了一个checkId即我这里的业务主键,可以自定义修改
package com.applets.manager.core.model.vo.req;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.List;

@Data
@Accessors(chain = true)
@ApiModel("评估回滚请求对象")
public class YlCheckRollbackReqVo extends BaseReqVo implements Serializable {
    private static final long serialVersionUID = 718144265818263634L;
    /**
     * 检测id
     */
    @ApiModelProperty("检测id")
    private Long checkId;

}

根据操作记录回滚业务数据

rollback业务方法

  • checkId 为某业务主键,可以自行改动
  • 通过checkid查询出对应的操作记录(我这里是查询最新的,读者可以自定义查询条件)
  • 查到操作记录后将historyRecord.getOldData() JSON 字符串反序列化为 Map
  • 遍历 Map,反射获取类和 Mapper
  • 通过反射获取类,提供给反序列化时使用
  • 进行反序列化
    • 检查数据是否是数组类型还是单个对象(两种反序列化的方式不同)
  • 根据 type 判断操作类型并进行相应的回滚(insert,update,delete)
    • 如果是逻辑删除的化就单独使用update回滚即可,把对应的删除标志修改一下即可
    • 如果不是逻辑删除的化insert回滚就需要反向执行删除操作;delete回滚就需要反向执行插入操作.
    • 内部也需要检查数据是否是数组类型还是单个对象(baseMapper处理数组类型和单个对象的方法不同)
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rollback(YlCheckRollbackReqVo req) {
        Long checkId = req.getCheckId();
        if (ObjectUtils.isEmpty(checkId)){
            throw new BusinessException("参数错误");
        }
        // 查询保存的历史数据最新的一条 (JSON 字符串)
        LambdaQueryWrapper<CheckStatusRecord> qw = new LambdaQueryWrapper<>();
        qw.eq(CheckStatusRecord::getCheckId, checkId)
                .orderByDesc(CheckStatusRecord::getCreatedTime) 
                .last("LIMIT 1");  // 只查询一条
        CheckStatusRecord historyRecord = checkStatusRecordMapper.selectOne(qw);
        if (historyRecord == null || historyRecord.getOldData() == null) {
            throw new BusinessException("没有找到可以回滚的历史数据");
        }

        // 1. 将 JSON 字符串反序列化为 Map
        Map<String, Object> oldDataMap = JSON.parseObject(historyRecord.getOldData(), Map.class);

        // 2. 遍历 Map,反射获取类和 Mapper
        for (Map.Entry<String, Object> entry : oldDataMap.entrySet()) {
            // 解析 key,获取类名和 Mapper 名,和操作类型
            String[] keyParts = entry.getKey().split("-");
            String className = keyParts[0];
            String mapperName = keyParts[1];
            String type = keyParts[2];  // 获取操作类型

            try {
                // 通过反射获取类
                Class<?> clazz = Class.forName(className);

                Object oldData;

                // 3. 检查数据是否是数组类型
                if (entry.getValue() instanceof JSONArray) {
                    // 如果是数组类型,使用 parseArray 进行反序列化
                    oldData = JSON.parseArray(JSON.toJSONString(entry.getValue()), clazz);
                } else {
                    // 如果是单个对象,使用 parseObject 进行反序列化
                    oldData = JSON.parseObject(JSON.toJSONString(entry.getValue()), clazz);
                }

                // 通过 Spring 应用上下文获取 Mapper 实例
                Object mapper = applicationContext.getBean(mapperName);

                // 4. 根据 type 判断操作类型并进行相应的回滚
                if ("insert".equals(type)) {
                    // 如果是新增操作,回滚时执行删除操作
                    if (oldData instanceof List) {
                        for (Object item : (List<?>) oldData) {
                            if (mapper instanceof BaseMapper) {
                                String jsonItem = JSON.toJSONString(item);
                                JSONObject jsonObjectItem = JSON.parseObject(jsonItem);
                                BaseMapper baseMapper = (BaseMapper) mapper;
                                baseMapper.deleteById(jsonObjectItem.getLong("id"));  // 删除每个元素
                            }
                        }
                    } else {
                        if (mapper instanceof BaseMapper) {
                            String jsonOldData = JSON.toJSONString(oldData);
                            JSONObject jsonObjectOldData = JSON.parseObject(jsonOldData);
                            BaseMapper baseMapper = (BaseMapper) mapper;
                            baseMapper.deleteById(jsonObjectOldData.getLong("id"));  // 删除单个对象
                        }
                    }
                } else if ("update".equals(type)) {
                    // 如果是更新操作,回滚时恢复原始数据
                    if (oldData instanceof List) {
                        for (Object item : (List<?>) oldData) {
                            if (mapper instanceof BaseMapper) {
                                BaseMapper baseMapper = (BaseMapper) mapper;
                                baseMapper.updateById(item);  // 恢复每个元素
                            }
                        }
                    } else {
                        if (mapper instanceof BaseMapper) {
                            BaseMapper baseMapper = (BaseMapper) mapper;
                            baseMapper.updateById(oldData);  // 恢复单个对象
                        }
                    }
                } else if ("delete".equals(type)) {
                    // 如果是删除操作,回滚时重新插入数据
                    if (oldData instanceof List) {
                        for (Object item : (List<?>) oldData) {
                            if (mapper instanceof BaseMapper) {
                                BaseMapper baseMapper = (BaseMapper) mapper;
                                baseMapper.insert(item);  // 插入每个元素
                            }
                        }
                    } else {
                        if (mapper instanceof BaseMapper) {
                            BaseMapper baseMapper = (BaseMapper) mapper;
                            baseMapper.insert(oldData);  // 插入单个对象
                        }
                    }
                }

            } catch (ClassNotFoundException e) {
                log.error("回滚操作失败: {}", e);
                throw new BusinessException("回滚操作失败: " + className);
            }
        }
    }

业务回滚测试测试

到此这篇关于Springboot结合Mybatis-Plus实现业务撤销回滚功能的文章就介绍到这了,更多相关Springboot Mybatis-Plus业务撤销回滚内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java并发编程-volatile可见性详解

    Java并发编程-volatile可见性详解

    这篇文章主要介绍了Java并发编程-volatile可见性详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • maven配置多个镜像的实现方法

    maven配置多个镜像的实现方法

    这篇文章主要介绍了maven配置多个镜像的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • java 容器的快速失败(fast-fail)机制

    java 容器的快速失败(fast-fail)机制

    Java容器的快速失败机制是一种在迭代过程中检测并处理集合并发修改的特性,该机制适用于ArrayList、HashMap等集合类,本文就来介绍一下java 容器的快速失败(fast-fail)机制,感兴趣的可以了解一下
    2024-11-11
  • ElasticSearch如何设置某个字段不分词浅析

    ElasticSearch如何设置某个字段不分词浅析

    最近在学习ElasticSearch官方文档过程中发现的某个问题,记录一下 希望能帮助到后面的朋友,下面这篇文章主要给大家介绍了关于ElasticSearch如何设置某个字段不分词的相关资料,需要的朋友可以参考下
    2022-04-04
  • Java实战之校园外卖点餐系统的实现

    Java实战之校园外卖点餐系统的实现

    这篇文章主要介绍了如何利用Java实现简易的校园外卖点餐系统,文中采用的技术有:JSP、Spring、SpringMVC、MyBatis 等,感兴趣的可以了解一下
    2022-03-03
  • SpringBoot实现无限级评论回复的项目实践

    SpringBoot实现无限级评论回复的项目实践

    本文主要介绍了SpringBoot实现无限级评论回复的项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-03-03
  • 对ArrayList和LinkedList底层实现原理详解

    对ArrayList和LinkedList底层实现原理详解

    今天小编就为大家分享一篇对ArrayList和LinkedList底层实现原理详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-10-10
  • SpringBoot实现文件在线预览功能的全过程

    SpringBoot实现文件在线预览功能的全过程

    我们开发业务系统的时候,经常有那种文档文件在线预览的需求,下面这篇文章主要给大家介绍了关于SpringBoot实现文件在线预览功能的相关资料,需要的朋友可以参考下
    2021-11-11
  • java实现读取txt文件中的内容

    java实现读取txt文件中的内容

    本文通过一个具体的例子向大家展示了如何使用java实现读取TXT文件里的内容的方法以及思路,有需要的小伙伴可以参考下
    2016-03-03
  • Java中Sentinel框架详解

    Java中Sentinel框架详解

    Sentinel是一个高可用、高扩展、高稳定性的开源流量控制和熔断降级框架,可以在分布式系统中实现实时的流量控制,防止系统因流量过大导致系统崩溃和服务降级,Sentinel面向所有的Java应用,本文就给大家详细介绍一下Java中Sentinel框架,需要的朋友可以参考下
    2023-06-06

最新评论