SpringCloud或SpringBoot+Mybatis-Plus利用mybatis插件实现数据操作记录及更新对比

引文

  本文主要介绍如何使用mybatis插件实现拦截数据库操作并根据不同需求进行数据对比分析,主要适用于系统中需要对数据操作进行记录、在更新数据时准确记录更新字段

核心:mybatis插件(拦截器)、mybatis-Plus实体规范、数据对比

1、相关技术简介

mybatis插件

  mybatis插件实际上就是官方针对4层数据操作处理预留的拦截器,使用者可以根据不同的需求进行操作拦截并处理。这边笔者不做详细描述,详细介绍请到官网了解,这里笔者就复用官网介绍。

插件(plugins)

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为如果在试图修改或重写已有方法的行为的时候,你很可能在破坏 MyBatis 的核心模块。 这些都是更低层的类和方法,所以使用插件的时候要特别当心。

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

 1 // ExamplePlugin.java
 2 @Intercepts({@Signature(
 3   type= Executor.class,
 4   method = "update",
 5   args = {MappedStatement.class,Object.class})})
 6 public class ExamplePlugin implements Interceptor {
 7   private Properties properties = new Properties();
 8   public Object intercept(Invocation invocation) throws Throwable {
 9     // implement pre processing if need
10     Object returnObject = invocation.proceed();
11     // implement post processing if need
12     return returnObject;
13   }
14   public void setProperties(Properties properties) {
15     this.properties = properties;
16   }
17 }
18 <!-- mybatis-config.xml -->
19 <plugins>
20   <plugin interceptor="org.mybatis.example.ExamplePlugin">
21     <property name="someProperty" value="100"/>
22   </plugin>
23 </plugins>

上面的插件将会拦截在 Executor 实例中所有的 “update” 方法调用, 这里的 Executor 是负责执行低层映射语句的内部对象。

提示 覆盖配置类

除了用插件来修改 MyBatis 核心行为之外,还可以通过完全覆盖配置类来达到目的。只需继承后覆盖其中的每个方法,再把它传递到 SqlSessionFactoryBuilder.build(myConfig) 方法即可。再次重申,这可能会严重影响 MyBatis 的行为,务请慎之又慎。

重点讲下4层处理,MyBatis两级缓存就是在其中两层中实现

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

    • 所有数据库操作到达底层后都由该执行器进行任务分发,主要有update(插入、更新、删除),query(查询),提交,回滚,关闭链接等
  • ParameterHandler (getParameterObject, setParameters) 
    • 参数处理器(获取参数,设置参数)
  • ResultSetHandler (handleResultSets, handleOutputParameters) 
    • 结果集处理器(结果集,输出参数)
  • StatementHandler (prepare, parameterize, batch, update, query) 
    • 声明处理器、准备链接jdbc前处理,prepare(预处理):生成sql语句,准备链接数据库进行操作

以上4层执行顺序为顺序执行

  • Executor是 Mybatis的内部执行器,它负责调用StatementHandler操作数据库,并把结果集通过 ResultSetHandler进行自动映射,另外,他还处理了二级缓存的操作。从这里可以看出,我们也是可以通过插件来实现自定义的二级缓存的。
  • ParameterHandler是Mybatis实现Sql入参设置的对象。插件可以改变我们Sql的参数默认设置。
  • ResultSetHandler是Mybatis把ResultSet集合映射成POJO的接口对象。我们可以定义插件对Mybatis的结果集自动映射进行修改。
  • StatementHandler是Mybatis直接和数据库执行sql脚本的对象。另外它也实现了Mybatis的一级缓存。这里,我们可以使用插件来实现对一级缓存的操作(禁用等等)。

MyBatis-Plus:

  MyBatis增强器,主要规范了数据实体,在底层实现了简单的增删查改,使用者不再需要开发基础操作接口,小编认为是最强大、最方便易用的,没有之一,不接受任何反驳。详细介绍请看官网

数据实体的规范让底层操作更加便捷,本例主要实体规范中的表名以及主键获取,下面上实体规范demo

 1 @Data
 2 @TableName("tb_demo")
 3 @EqualsAndHashCode(callSuper = true)
 4 public class Demo extends Model<Demo> {
 5 private static final long serialVersionUID = 1L;
 6
 7     /**
 8    *
 9    */
10     @TableId
11     private Integer id;
12     /**
13    * 名称
14    */
15     private String name;
16
17 }

2、实现

本文所要讲述的就是在第一级(Executor)进行拦截并实现数据对比记录。

本例为公共模块实现,然后在其它模块中依赖此公共模块,根据每个模块不同的需求自定义实现不同的处理。

结构目录

一、实现拦截器

DataUpdateInterceptor,根据官网demo实现拦截器,在拦截器中根据增、删、改操作去调用各个模块中自定义实现的处理方法来达到不同的操作处理。

  1 package com.erp4cloud.rerp.common.data.log;
  2
  3 import com.baomidou.mybatisplus.annotation.TableId;
  4 import com.baomidou.mybatisplus.annotation.TableName;
  5 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  6 import com.baomidou.mybatisplus.extension.activerecord.Model;
  7 import lombok.AllArgsConstructor;
  8 import org.apache.commons.lang.StringUtils;
  9 import org.apache.ibatis.executor.Executor;
 10 import org.apache.ibatis.mapping.MappedStatement;
 11 import org.apache.ibatis.mapping.SqlCommandType;
 12 import org.apache.ibatis.plugin.*;
 13 import org.springframework.scheduling.annotation.Async;
 14
 15 import javax.sql.DataSource;
 16 import java.lang.reflect.Field;
 17 import java.lang.reflect.InvocationTargetException;
 18 import java.util.Collection;
 19 import java.util.List;
 20 import java.util.Map;
 21 import java.util.Properties;
 22
 23 /**
 24  * 数据更新拦截器
 25  *
 26  * @author Tophua
 27  * @date 2019/8/2
 28  */
 29 @AllArgsConstructor
 30 @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
 31 public class DataUpdateInterceptor implements Interceptor {
 32
 33     private final DataSource dataSource;
 34     private final DataLogHandler dataLogHandler;
 35
 36     @Override
 37     public Object intercept(Invocation invocation) {
 38         Object result = null;
 39         try {
 40             this.dealData(invocation);
 41             result = invocation.proceed();
 42         } catch (InvocationTargetException e) {
 43             e.printStackTrace();
 44         } catch (IllegalAccessException e) {
 45             e.printStackTrace();
 46         }
 47         return result;
 48     }
 49
 50     @Override
 51     public Object plugin(Object target) {
 52         if (target instanceof Executor) {
 53             return Plugin.wrap(target, this);
 54         }
 55         return target;
 56     }
 57
 58     @Override
 59     public void setProperties(Properties properties) {
 60
 61     }
 62
 63     /**
 64      * 对数据库操作传入参数进行处理
 65      *
 66      * @param invocation
 67      * @return void
 68      * @author Tophua
 69      * @date 2019/8/3
 70      */
 71     public void dealData(Invocation invocation) {
 72         Object[] args = invocation.getArgs();
 73         MappedStatement mappedStatement = (MappedStatement) args[0];
 74         // 参数
 75         Object et = args[1];
 76         if (et instanceof Model) {
 77             this.doLog(mappedStatement, et);
 78         } else if (et instanceof Map) {
 79             String key = "et";
 80             String listKey = "collection";
 81             if (((Map) et).containsKey(key) && ((Map) et).get(key) instanceof Model) {
 82                 this.doLog(mappedStatement, ((Map) et).get(key));
 83             } else if (((Map) et).containsKey(listKey) && ((Map) et).get(listKey) instanceof Collection) {
 84                 List<Object> list = (List<Object>) ((Map) et).get(listKey);
 85                 for (Object obj : list) {
 86                     if (obj instanceof Model) {
 87                         this.doLog(mappedStatement, obj);
 88                     }
 89                 }
 90             }
 91         }
 92     }
 93
 94     /**
 95      * 根据不同参数及操作进行不同的日志记录
 96      *
 97      * @param mappedStatement
 98      * @param et
 99      * @return void
100      * @author Tophua
101      * @date 2019/8/3
102      */
103     public void doLog(MappedStatement mappedStatement, Object et) {
104         // 反射获取实体类
105         Class<?> clazz = et.getClass();
106         // 不含有表名的实体就默认通过
107         if (!clazz.isAnnotationPresent(TableName.class)) {
108             return;
109         }
110         // 获取表名
111         TableName tableName = clazz.getAnnotation(TableName.class);
112         String tbName = tableName.value();
113         if (StringUtils.isBlank(tbName)) {
114             return;
115         }
116         String pkName = null;
117         String pkValue = null;
118         // 获取实体所有字段
119         Field[] fields = clazz.getDeclaredFields();
120         for (Field field : fields) {
121             // 设置些属性是可以访问的
122             field.setAccessible(true);
123             if (field.isAnnotationPresent(TableId.class)) {
124                 // 获取主键
125                 pkName = field.getName();
126                 try {
127                     // 获取主键值
128                     pkValue = field.get(et).toString();
129                 } catch (Exception e) {
130                     pkValue = null;
131                 }
132
133             }
134         }
135         BasicInfo basicInfo = new BasicInfo(dataSource, (Model) et, tbName, pkName, pkValue);
136
137         // 插入
138         if (SqlCommandType.INSERT.equals(mappedStatement.getSqlCommandType())) {
139             InsertInfo insertInfo = new InsertInfo(basicInfo, et);
140             dataLogHandler.insertHandler(insertInfo);
141         }
142         // 更新
143         if (SqlCommandType.UPDATE.equals(mappedStatement.getSqlCommandType()) && StringUtils.isNotBlank(pkName) && StringUtils.isNotBlank(pkValue)) {
144             Object oldObj = this.queryData(pkName, pkValue, (Model) et);
145             if (oldObj != null) {
146                 UpdateInfo updateInfo = new UpdateInfo(basicInfo, oldObj, et);
147                 // 调用自定义处理方法
148                 dataLogHandler.updateHandler(updateInfo);
149             }
150         }
151         // 删除
152         if (SqlCommandType.DELETE.equals(mappedStatement.getSqlCommandType()) && StringUtils.isNotBlank(pkName) && StringUtils.isNotBlank(pkValue)) {
153             Object delObj = this.queryData(pkName, pkValue, (Model) et);
154             if (delObj != null) {
155                 DeleteInfo deleteInfo = new DeleteInfo(basicInfo, delObj);
156                 // 调用自定义处理方法
157                 dataLogHandler.deleteHandler(deleteInfo);
158             }
159         }
160     }
161
162     /**
163      * 根据主键和主键值查询数据
164      *
165      * @param pkName
166      * @param pkValue
167      * @param clazz
168      * @return java.lang.Object
169      * @author Tophua
170      * @date 2019/8/5
171      */
172     private Object queryData(String pkName, String pkValue, Model clazz) {
173         // 查询更新前数据
174         return clazz.selectOne(Wrappers.query().eq(pkName, pkValue));
175     }
176 }

二、配置

 1 package com.erp4cloud.rerp.common.data.log;
 2
 3 import lombok.AllArgsConstructor;
 4 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 5 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 6 import org.springframework.context.annotation.Bean;
 7 import org.springframework.context.annotation.Configuration;
 8
 9 import javax.sql.DataSource;
10
11 /**
12  * 数据更新日志处理配置(实现按需加载)
13  *
14  * @author Tophua
15  * @date 2019/8/2
16  */
17 @Configuration
18 @AllArgsConstructor
19 @ConditionalOnBean({DataSource.class, DataLogHandler.class})
20 public class DataLogConfig {
21
22     private final DataLogHandler dataLogHandler;
23     private final DataSource dataSource;
24
25     @Bean
26     @ConditionalOnMissingBean
27     public DataUpdateInterceptor dataUpdateInterceptor() {
28         return new DataUpdateInterceptor(dataSource, dataLogHandler);
29     }
30 }

提示:公共模块中需要在spring.factories(src/main/resources/META-INF/)中进行配置让Spring自动进行装配,小笔使用如下

1 org.springframework.boot.autoconfigure.EnableAutoConfiguration=2     com.erp4cloud.rerp.common.data.log.DataLogConfig

三、在其它模块实现自定义处理接口

在接口中会根据不同操作传入不同参数,各位可以根据具体方法取出数据进行所需操作。

本例仅测试使用,未实现具体操作,在具体使用中请自行编写具体逻辑。

DataLogHandler 自定义处理接口,各模块实现,注意需要将实现类作为ServiceBean使用,否则该功能无法生效。

 1 package com.erp4cloud.rerp.common.data.log;
 2
 3 /**
 4  * 数据日志处理
 5  *
 6  * @author Tophua
 7  * @date 2019/8/2
 8  */
 9 public interface DataLogHandler {
10
11     /**
12      * 插入处理
13      *
14      * @param insertInfo 插入数据信息
15      * @return void
16      * @author Tophua
17      * @date 2019/8/2
18      */
19     void insertHandler(InsertInfo insertInfo);
20
21     /**
22      * 更新处理
23      *
24      * @param updateInfo 更新数据信息
25      * @return void
26      * @author Tophua
27      * @date 2019/8/2
28      */
29     void updateHandler(UpdateInfo updateInfo);
30
31     /**
32      * 删除处理
33      *
34      * @param deleteInfo 删除数据信息
35      * @return void
36      * @author Tophua
37      * @date 2019/8/3
38      */
39     void deleteHandler(DeleteInfo deleteInfo);
40 }

实现demo

 1 package com.erp4cloud.rerp.building.log;
 2
 3 import com.erp4cloud.rerp.common.data.log.*;
 4 import com.fasterxml.jackson.databind.ObjectMapper;
 5 import lombok.AllArgsConstructor;
 6 import lombok.SneakyThrows;
 7 import org.springframework.stereotype.Service;
 8
 9 import java.util.List;
10
11 /**
12  * describe
13  *
14  * @author Tophua
15  * @date 2019/8/3
16  */
17 @Service
18 @AllArgsConstructor
19 public class DataLogDeal implements DataLogHandler {
20
21     ObjectMapper objectMapper = new ObjectMapper();
22
23     @SneakyThrows
24     @Override
25     public void insertHandler(InsertInfo insertInfo) {
26         System.out.println("插入:" + objectMapper.writeValueAsString(insertInfo.getInsertObj()));
27     }
28
29     @SneakyThrows
30     @Override
31     public void updateHandler(UpdateInfo updateInfo) {
32         List<CompareResult> cr = updateInfo.getCompareResult();
33         StringBuilder sb = new StringBuilder();
34         sb.append("更新\"");
35         sb.append(updateInfo.getBasicInfo().getTbName());
36         sb.append("\" 表 ");
37         cr.forEach(r -> {
38             String s = "把《" + r.getFieldComment() + "》从<" + r.getOldValue() + ">改成<" + r.getNewValue() + ">";
39             sb.append(s);
40         });
41         System.out.println(sb.toString());
42     }
43
44     @SneakyThrows
45     @Override
46     public void deleteHandler(DeleteInfo deleteInfo) {
47         System.out.println("删除:" + objectMapper.writeValueAsString(deleteInfo.getDeleteObj()));
48     }
49 }

四、其它代码

BaseDataLogHandler 基础处理抽象类,提供底层数据对比方法。

 1 package com.erp4cloud.rerp.common.data.log;
 2
 3 import lombok.AllArgsConstructor;
 4 import lombok.Getter;
 5
 6 import java.lang.reflect.Field;
 7 import java.util.ArrayList;
 8 import java.util.List;
 9 import java.util.Optional;
10
11 /**
12  * 数据日志基础信息及处理
13  *
14  * @author Tophua
15  * @date 2019/8/5
16  */
17 @Getter
18 @AllArgsConstructor
19 public abstract class BaseDataLogHandler {
20
21     /**
22      * 数据基础信息
23      */
24     private BasicInfo basicInfo;
25
26     /**
27      * 对比两个对象
28      *
29      * @param oldObj 旧对象
30      * @param newObj 新对象
31      * @return java.util.List<com.erp4cloud.rerp.common.data.log.CompareResult>
32      * @author Tophua
33      * @date 2019/8/5
34      */
35     protected List<CompareResult> compareTowObject(Object oldObj, Object newObj) throws IllegalAccessException {
36         List<CompareResult> list = new ArrayList<>();
37         //获取对象的class
38         Class<?> clazz1 = oldObj.getClass();
39         Class<?> clazz2 = newObj.getClass();
40         //获取对象的属性列表
41         Field[] field1 = clazz1.getDeclaredFields();
42         Field[] field2 = clazz2.getDeclaredFields();
43         //遍历属性列表field1
44         for (int i = 0; i < field1.length; i++) {
45             //遍历属性列表field2
46             for (int j = 0; j < field2.length; j++) {
47                 //如果field1[i]属性名与field2[j]属性名内容相同
48                 if (field1[i].getName().equals(field2[j].getName())) {
49                     field1[i].setAccessible(true);
50                     field2[j].setAccessible(true);
51                     if (field2[j].get(newObj) == null) {
52                         continue;
53                     }
54                     //如果field1[i]属性值与field2[j]属性值内容不相同
55                     if (!compareTwo(field1[i].get(oldObj), field2[j].get(newObj))) {
56                         CompareResult r = new CompareResult();
57                         r.setFieldName(field1[i].getName());
58                         r.setOldValue(field1[i].get(oldObj));
59                         r.setNewValue(field2[j].get(newObj));
60
61                         // 匹配字段注释
62                         Optional o = this.basicInfo.getFieldInfos().stream()
63                                 .filter(f -> r.getFieldName().equals(f.getJFieldName())).findFirst();
64                         if (o.isPresent()) {
65                             r.setFieldComment(((FieldInfo) o.get()).getComment());
66                         }
67                         list.add(r);
68                     }
69                     break;
70                 }
71             }
72         }
73         return list;
74     }
75
76     /**
77      * 对比两个数据是否内容相同
78      *
79      * @param object1,object2
80      * @return boolean类型
81      */
82     private boolean compareTwo(Object object1, Object object2) {
83
84         if (object1 == null && object2 == null) {
85             return true;
86         }
87         if (object1 == null && object2 != null) {
88             return false;
89         }
90         if (object1.equals(object2)) {
91             return true;
92         }
93         return false;
94     }
95
96 }

BasicInfo 基础信息,数据源,本表字段信息等。

 1 package com.erp4cloud.rerp.common.data.log;
 2
 3 import cn.hutool.db.Db;
 4 import com.baomidou.mybatisplus.extension.activerecord.Model;
 5 import lombok.Getter;
 6 import org.apache.commons.lang.StringUtils;
 7 import org.apache.commons.lang.WordUtils;
 8
 9 import javax.sql.DataSource;
10 import java.sql.SQLException;
11 import java.util.ArrayList;
12 import java.util.List;
13 import java.util.concurrent.ConcurrentHashMap;
14
15 /**
16  * 基础信息
17  *
18  * @author Tophua
19  * @date 2019/8/5
20  */
21 @Getter
22 public class BasicInfo {
23     private static ConcurrentHashMap<String, List<FieldInfo>> fields = new ConcurrentHashMap<>();
24
25     /**
26      * 数据源
27      */
28     private DataSource dataSource;
29     /**
30      * mybatis数据底层
31      */
32     private Model model;
33     /**
34      * 表名
35      */
36     private String tbName;
37     /**
38      * 主键名称
39      */
40     private String pkName;
41     /**
42      * 主键值
43      */
44     private String pkValue;
45
46     /**
47      * 表字段注释
48      */
49     private List<FieldInfo> fieldInfos;
50
51     public BasicInfo(DataSource dataSource, Model model, String tbName, String pkName, String pkValue) {
52         this.dataSource = dataSource;
53         this.model = model;
54         this.tbName = tbName;
55         this.pkName = pkName;
56         this.pkValue = pkValue;
57     }
58
59     public List<FieldInfo> getFieldInfos() {
60         if (!fields.containsKey(this.tbName)) {
61             String query = "select column_name fieldName, column_comment comment from information_schema.columns" +
62                     " where table_name = \"" + this.tbName + "\" and table_schema = (select database())";
63             try {
64                 this.fieldInfos = Db.use(dataSource).query(query, FieldInfo.class);
65             } catch (SQLException e) {
66                 this.fieldInfos = new ArrayList<>();
67             }
68             this.fieldInfos.forEach(f -> {
69                 String caseName = this.columnToJava(f.getFieldName());
70                 f.setJFieldName(StringUtils.uncapitalize(caseName));
71             });
72             fields.put(this.tbName, this.fieldInfos);
73         }
74         return fields.get(this.tbName);
75     }
76
77     /**
78      * 列名转换成Java属性名
79      */
80     private String columnToJava(String columnName) {
81         return WordUtils.capitalizeFully(columnName, new char[]{‘_‘}).replace("_", "");
82     }
83 }

FieldInfo 字段信息

 1 package com.erp4cloud.rerp.common.data.log;
 2
 3 import lombok.Data;
 4
 5 /**
 6  * 字段信息
 7  *
 8  * @author Tophua
 9  * @date 2019/8/5
10  */
11 @Data
12 public class FieldInfo {
13
14     /**
15      * 字段名
16      */
17     private String fieldName;
18     /**
19      * java字段名
20      */
21     private String jFieldName;
22     /**
23      * 注释
24      */
25     private String comment;
26 }

CompareResult 字段对比结果

 1 package com.erp4cloud.rerp.common.data.log;
 2
 3 import lombok.Data;
 4
 5 /**
 6  * 对比两个对象结果
 7  *
 8  * @author Tophua
 9  * @date 2019/8/5
10  */
11 @Data
12 public class CompareResult {
13
14     /**
15      * 字段名
16      */
17     private String fieldName;
18     /**
19      * 字段注释
20      */
21     private String fieldComment;
22     /**
23      * 字段旧值
24      */
25     private Object oldValue;
26     /**
27      * 字段新值
28      */
29     private Object newValue;
30 }

InsertInfo 插入信息

 1 package com.erp4cloud.rerp.common.data.log;
 2
 3 import lombok.Getter;
 4
 5 /**
 6  * 数据插入信息
 7  *
 8  * @author Tophua
 9  * @date 2019/8/5
10  */
11 @Getter
12 public class InsertInfo extends BaseDataLogHandler {
13
14     /**
15      * 插入对象
16      */
17     private Object insertObj;
18
19     public InsertInfo(BasicInfo basicInfo, Object insertObj) {
20         super(basicInfo);
21         this.insertObj = insertObj;
22     }
23
24 }

UpdateInfo 更新信息

 1 package com.erp4cloud.rerp.common.data.log;
 2
 3 import lombok.Getter;
 4
 5 import java.util.List;
 6
 7 /**
 8  * 数据更新信息
 9  *
10  * @author Tophua
11  * @date 2019/8/5
12  */
13 @Getter
14 public class UpdateInfo extends BaseDataLogHandler {
15
16     /**
17      * 更新前对象
18      */
19     private Object oldObj;
20     /**
21      * 更新对象
22      */
23     private Object newObj;
24
25     public UpdateInfo(BasicInfo basicInfo, Object oldObj, Object newObj) {
26         super(basicInfo);
27         this.oldObj = oldObj;
28         this.newObj = newObj;
29     }
30
31     public List<CompareResult> getCompareResult() throws IllegalAccessException {
32         return compareTowObject(this.oldObj, this.newObj);
33     }
34 }

DeleteInfo 删除信息

package com.erp4cloud.rerp.common.data.log;

import lombok.Getter;

/**
 * 数据删除信息
 *
 * @author Tophua
 * @date 2019/8/5
 */
@Getter
public class DeleteInfo extends BaseDataLogHandler {

    /**
     * 删除对象
     */
    private Object deleteObj;

    public DeleteInfo(BasicInfo basicInfo, Object deleteObj) {
        super(basicInfo);
        this.deleteObj = deleteObj;
    }
}

3、总结

本例主要解决多实体数据更新前后对比记录,当然也可使用AOP实现数据对比,但经笔者实现感觉还是此方法实现起来相对简单。笔者更推荐使用底层技术直接进行拦截处理,这样能保证任何数据操作都毫无遗漏,不放过任何操作。

目前本例暂未实现数据无主键更新记录,但业务中经常会出现无主键根据其它条件更新,所以本例还可进行优化提升,在此笔者就先放一段了,等后续再进行升级更新。

欢迎各位大神交流意见。。。。。。

原文地址:https://www.cnblogs.com/top-sky-hua/p/11306376.html

时间: 2024-10-13 02:47:30

SpringCloud或SpringBoot+Mybatis-Plus利用mybatis插件实现数据操作记录及更新对比的相关文章

Mysql之binlog日志说明及利用binlog日志恢复数据操作记录

众所周知,binlog日志对于mysql数据库来说是十分重要的.在数据丢失的紧急情况下,我们往往会想到用binlog日志功能进行数据恢复(定时全备份+binlog日志恢复增量数据部分),化险为夷! 废话不多说,下面是梳理的binlog日志操作解说: 一.初步了解binlogMySQL的二进制日志binlog可以说是MySQL最重要的日志,它记录了所有的DDL和DML语句(除了数据查询语句select),以事件形式记录,还包含语句所执行的消耗的时间,MySQL的二进制日志是事务安全型的. DDL-

Mybatis分页-利用Mybatis Generator插件生成基于数据库方言的分页语句,统计记录总数 (转)

众所周知,Mybatis本身没有提供基于数据库方言的分页功能,而是基于JDBC的游标分页,很容易出现性能问题.网上有很多分页的解决方案,不外乎是基于Mybatis本机的插件机制,通过拦截Sql做分页.但是在像Oracle这样的数据库上,拦截器生成的Sql语句没有变量绑定,而且每次语句的都要去拦截,感觉有点浪费性能. Mybatis Generator是Mybatis的代码生成工具,可以生成大部分的查询语句. 本文提供的分页解决方案是新增Mybatis Generator插件,在用Mybatis

利用mybatis generator插件反向生成Dao、Mapper.xml、pojo(通过maven)

直接进入主题,由于项目选择的利用maven构建,所以选择了利用maven的生成方式.(还有一种可自行百度) 一.在pom.xml中添加插件 <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.2</version> <configur

SpringBoot+Mybatis配置Pagehelper分页插件实现自动分页

SpringBoot+Mybatis配置Pagehelper分页插件实现自动分页 **SpringBoot+Mybatis使用Pagehelper分页插件自动分页,非常好用,不用在自己去计算和组装了.全部自动实现. 话不多说,直接上代码: 第一步pom文件配置添加jar: <!-- mybatis的分页插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>

SpringBoot框架之通用mapper插件(tk.mybatis)

一.Tkmybatis的好处 Tkmybatis是在mybatis框架的基础上提供了很多工具,让开发更加高效.这个插件里面封装好了我们需要用到的很多sql语句,不过这个插件是通过我们去调用它封装的各种方法来实现sql语句的效果.对于单表查询不需要写SQL语句,这样就不用像mybatis那样每次写一个接口就要写一条sql语句.这样大大减少了我们的工作量. 拓展:IDEA中使用mybatis-generator自动生成mapper和pojo文件 使用maven命令即可使用:mvn mybatis-g

【mybatis源码学习】利用maven插件自动生成mybatis代码

[一]在要生成代码的项目模块的pom.xml文件中添加maven插件 <!--mybatis代码生成器--> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.2</version> <dependencies>

利用MyBatis Generator自动创建代码

如果你使用过hibernate的eclipse插件自动创建DAO文件,那么就容易理解下面介绍的内容:如果你还没有用过hibernate也无妨.下面介绍使用mybatis 3的eclipse插件自动生成相关文件以及如何使用这些文件. eclipse插件安装地址:http://mybatis.googlecode.com/svn/sub-projects/generator/trunk/eclipse/UpdateSite/ 附件有link安装包,link安装方式参考http://maimode.i

SpringBoot 使用yml配置 mybatis+pagehelper+druid+freemarker实例

SpringBoot 使用yml配置 mybatis+pagehelper+druid+freemarker实例 这是一个简单的SpringBoot整合实例 这里是项目的结构目录 首先是pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="h

SpringBoot学习--04SpringBoot整合Mybatis(上)(配置mybatis generator,PageHelper)

陆陆续续又忙了几天,继续写. 本篇仿照着优秀的文章的书写,加上自己的理解和踩过的坑,原文地址:https://www.jianshu.com/p/5cd772c07041?utm_campaign=haruki&utm_content=note&utm_medium=reader_share&utm_source=weixin 环境/版本一览: 开发工具:eclipse springboot: 2.0.1.RELEASE jdk:1.8.0_40 maven:3.3.9 额外功能: