操作记录表
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业务撤销回滚内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论