引文
本文主要介紹如何使用Spring AOP + mybatis外掛實現攔截資料庫操作並根據不同需求進行資料對比分析,主要適用於系統中需要對資料操作進行記錄、在更新資料時準確記錄更新欄位
核心:AOP、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 介面,並指定想要攔截的方法簽名即可。
// ExamplePlugin.java @Intercepts({@Signature( type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})}) public class ExamplePlugin implements Interceptor { private Properties properties = new Properties(); public Object intercept(Invocation invocation) throws Throwable { // implement pre processing if need Object returnObject = invocation.proceed(); // implement post processing if need return returnObject; } public void setProperties(Properties properties) { this.properties = properties; } } <!-- mybatis-config.xml --> <plugins> <plugin interceptor="org.mybatis.example.ExamplePlugin"> <property name="someProperty" value="100"/> </plugin> </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
package com.lith.datalog.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.extension.activerecord.Model; import lombok.Data; import lombok.EqualsAndHashCode; /** * <p> * 使用者表 * </p> * * @author Tophua * @since 2020/5/7 */ @Data @EqualsAndHashCode(callSuper = true) public class User extends Model<User> { /** * 主鍵id */ @TableId(value = "id", type = IdType.AUTO) private Integer id; private String name; private Integer age; private String email; }
2、實現
一、配置
1 package com.lith.datalog.config;
2
3 import com.lith.datalog.handle.DataUpdateInterceptor;
4 import org.mybatis.spring.annotation.MapperScan;
5 import org.springframework.context.annotation.Bean;
6 import org.springframework.context.annotation.Configuration;
7 import org.springframework.context.annotation.Profile;
8 import org.springframework.transaction.annotation.EnableTransactionManagement;
9
10 import javax.sql.DataSource;
11
12 /**
13 * <p>
14 * Mybatis-Plus配置
15 * </p>
16 *
17 * @author Tophua
18 * @since 2020/5/7
19 */
20 @Configuration
21 @EnableTransactionManagement
22 @MapperScan("com.lith.**.mapper")
23 public class MybatisPlusConfig {
24
25 /**
26 * <p>
27 * SQL執行效率外掛 設定 dev test 環境開啟
28 * </p>
29 *
30 * @return cn.rc100.common.data.mybatis.EplusPerformanceInterceptor
31 * @author Tophua
32 * @since 2020/3/11
33 */
34 @Bean
35 @Profile({"dev","test"})
36 public PerformanceInterceptor performanceInterceptor() {
37 return new PerformanceInterceptor();
38 }
39
40 /**
41 * <p>
42 * 資料更新操作處理
43 * </p>
44 *
45 * @return com.lith.datalog.handle.DataUpdateInterceptor
46 * @author Tophua
47 * @since 2020/5/11
48 */
49 @Bean
50 @Profile({"dev","test"})
51 public DataUpdateInterceptor dataUpdateInterceptor(DataSource dataSource) {
52 return new DataUpdateInterceptor(dataSource);
53 }
54 }
二、實現攔截器
DataUpdateInterceptor,根據官網demo實現攔截器,在攔截器中根據增、刪、改操作去呼叫各個模組中自定義實現的處理方法來達到不同的操作處理。
1 package com.lith.datalog.handle; 2 3 import cn.hutool.db.Db; 4 import com.baomidou.mybatisplus.core.metadata.TableInfo; 5 import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; 6 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; 7 import com.baomidou.mybatisplus.core.toolkit.PluginUtils; 8 import com.baomidou.mybatisplus.core.toolkit.StringPool; 9 import com.baomidou.mybatisplus.core.toolkit.TableNameParser; 10 import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler; 11 import com.lith.datalog.aspect.DataLogAspect; 12 import com.lith.datalog.aspect.DataTem; 13 import lombok.AllArgsConstructor; 14 import lombok.extern.slf4j.Slf4j; 15 import org.apache.ibatis.executor.statement.StatementHandler; 16 import org.apache.ibatis.mapping.MappedStatement; 17 import org.apache.ibatis.mapping.SqlCommandType; 18 import org.apache.ibatis.plugin.Interceptor; 19 import org.apache.ibatis.plugin.Intercepts; 20 import org.apache.ibatis.plugin.Invocation; 21 import org.apache.ibatis.plugin.Signature; 22 import org.apache.ibatis.reflection.MetaObject; 23 import org.apache.ibatis.reflection.SystemMetaObject; 24 25 import javax.sql.DataSource; 26 import java.lang.reflect.Proxy; 27 import java.sql.Statement; 28 import java.util.*; 29 30 /** 31 * <p> 32 * 資料更新攔截器 33 * </p> 34 * 35 * @author Tophua 36 * @since 2020/5/11 37 */ 38 @Slf4j 39 @AllArgsConstructor 40 @Intercepts({@Signature(type = StatementHandler.class, method = "update", args = {Statement.class})}) 41 public class DataUpdateInterceptor extends AbstractSqlParserHandler implements Interceptor { 42 private final DataSource dataSource; 43 44 @Override 45 public Object intercept(Invocation invocation) throws Throwable { 46 // 獲取執行緒名,使用執行緒名作為同一次操作記錄 47 String threadName = Thread.currentThread().getName(); 48 // 判斷是否需要記錄日誌 49 if (!DataLogAspect.hasThread(threadName)) { 50 return invocation.proceed(); 51 } 52 Statement statement; 53 Object firstArg = invocation.getArgs()[0]; 54 if (Proxy.isProxyClass(firstArg.getClass())) { 55 statement = (Statement) SystemMetaObject.forObject(firstArg).getValue("h.statement"); 56 } else { 57 statement = (Statement) firstArg; 58 } 59 MetaObject stmtMetaObj = SystemMetaObject.forObject(statement); 60 try { 61 statement = (Statement) stmtMetaObj.getValue("stmt.statement"); 62 } catch (Exception e) { 63 // do nothing 64 } 65 if (stmtMetaObj.hasGetter("delegate")) { 66 //Hikari 67 try { 68 statement = (Statement) stmtMetaObj.getValue("delegate"); 69 } catch (Exception ignored) { 70 71 } 72 } 73 74 String originalSql = statement.toString(); 75 originalSql = originalSql.replaceAll("[\\s]+", StringPool.SPACE); 76 int index = indexOfSqlStart(originalSql); 77 if (index > 0) { 78 originalSql = originalSql.substring(index); 79 } 80 81 StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget()); 82 MetaObject metaObject = SystemMetaObject.forObject(statementHandler); 83 this.sqlParser(metaObject); 84 MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); 85 86 // 獲取執行Sql 87 String sql = originalSql.replace("where", "WHERE"); 88 // 插入 89 if (SqlCommandType.INSERT.equals(mappedStatement.getSqlCommandType())) { 90 } 91 // 更新 92 if (SqlCommandType.UPDATE.equals(mappedStatement.getSqlCommandType())) { 93 try { 94 // 使用mybatis-plus 工具解析sql獲取表名 95 Collection<String> tables = new TableNameParser(sql).tables(); 96 if (CollectionUtils.isEmpty(tables)) { 97 return invocation.proceed(); 98 } 99 String tableName = tables.iterator().next(); 100 // 使用mybatis-plus 工具根據表名找出對應的實體類 101 Class<?> entityType = TableInfoHelper.getTableInfos().stream().filter(t -> t.getTableName().equals(tableName)) 102 .findFirst().orElse(new TableInfo(null)).getEntityType(); 103 104 DataTem dataTem = new DataTem(); 105 dataTem.setTableName(tableName); 106 dataTem.setEntityType(entityType); 107 // 設定sql用於執行完後查詢新資料 108 dataTem.setSql("SELECT * FROM " + tableName + " WHERE id in "); 109 String selectSql = "SELECT * FROM " + tableName + " " + sql.substring(sql.lastIndexOf("WHERE")); 110 // 查詢更新前資料 111 List<?> oldData = Db.use(dataSource).query(selectSql, entityType); 112 dataTem.setOldData(oldData); 113 DataLogAspect.put(threadName, dataTem); 114 } catch (Exception e) { 115 e.printStackTrace(); 116 } 117 } 118 // 刪除 119 if (SqlCommandType.DELETE.equals(mappedStatement.getSqlCommandType())) { 120 } 121 return invocation.proceed(); 122 } 123 124 /** 125 * 獲取sql語句開頭部分 126 * 127 * @param sql ignore 128 * @return ignore 129 */ 130 private int indexOfSqlStart(String sql) { 131 String upperCaseSql = sql.toUpperCase(); 132 Set<Integer> set = new HashSet<>(); 133 set.add(upperCaseSql.indexOf("SELECT ")); 134 set.add(upperCaseSql.indexOf("UPDATE ")); 135 set.add(upperCaseSql.indexOf("INSERT ")); 136 set.add(upperCaseSql.indexOf("DELETE ")); 137 set.remove(-1); 138 if (CollectionUtils.isEmpty(set)) { 139 return -1; 140 } 141 List<Integer> list = new ArrayList<>(set); 142 list.sort(Comparator.naturalOrder()); 143 return list.get(0); 144 } 145 }
二、AOP
使用AOP主要是考慮到一個方法中會出現多次資料庫操作,而這些操作在記錄中只能算作使用者的一次操作,故使用AOP進行操作隔離,將一個方法內的所有資料庫操作合併為一次記錄。
此外AOP還代表著是否需要記錄日誌,有切點才會進行記錄。
AOP 切點註解
1 package com.lith.datalog.annotation; 2 3 import java.lang.annotation.ElementType; 4 import java.lang.annotation.Retention; 5 import java.lang.annotation.RetentionPolicy; 6 import java.lang.annotation.Target; 7 8 /** 9 * <p> 10 * 資料日誌 11 * </p> 12 * 13 * @author Tophua 14 * @since 2020/7/15 15 */ 16 @Target(ElementType.METHOD) 17 @Retention(RetentionPolicy.RUNTIME) 18 public @interface DataLog { 19 }
三、AOP切面實現
採用方法執行前後進行處理
1 package com.lith.datalog.aspect; 2 3 import cn.hutool.core.collection.CollUtil; 4 import cn.hutool.core.util.ObjectUtil; 5 import cn.hutool.core.util.StrUtil; 6 import cn.hutool.db.Db; 7 import cn.hutool.json.JSONUtil; 8 import com.lith.datalog.annotation.DataLog; 9 import com.lith.datalog.handle.CompareResult; 10 import lombok.AllArgsConstructor; 11 import lombok.SneakyThrows; 12 import org.aspectj.lang.annotation.After; 13 import org.aspectj.lang.annotation.Aspect; 14 import org.aspectj.lang.annotation.Before; 15 import org.springframework.core.annotation.Order; 16 import org.springframework.scheduling.annotation.Async; 17 import org.springframework.stereotype.Component; 18 19 import javax.sql.DataSource; 20 import java.lang.reflect.Field; 21 import java.lang.reflect.Method; 22 import java.sql.SQLException; 23 import java.util.*; 24 import java.util.concurrent.ConcurrentHashMap; 25 import java.util.stream.Collectors; 26 27 /** 28 * <p> 29 * DataLog切面 30 * </p> 31 * 32 * @author Tophua 33 * @since 2020/7/15 34 */ 35 @Aspect 36 @Order(99) 37 @Component 38 @AllArgsConstructor 39 public class DataLogAspect { 40 41 private final DataSource dataSource; 42 43 private static final Map<String, List<DataTem>> TEM_MAP = new ConcurrentHashMap<>(); 44 45 /** 46 * <p> 47 * 判斷執行緒是否需要記錄日誌 48 * </p> 49 * 50 * @param threadName threadName 51 * @return boolean 52 * @author Tophua 53 * @since 2020/7/15 54 */ 55 public static boolean hasThread(String threadName) { 56 return TEM_MAP.containsKey(threadName); 57 } 58 59 /** 60 * <p> 61 * 增加執行緒資料庫操作 62 * </p> 63 * 64 * @param threadName threadName 65 * @param dataTem dataTem 66 * @return void 67 * @author Tophua 68 * @since 2020/7/15 69 */ 70 public static void put(String threadName, DataTem dataTem) { 71 if (TEM_MAP.containsKey(threadName)) { 72 TEM_MAP.get(threadName).add(dataTem); 73 } 74 } 75 76 /** 77 * <p> 78 * 切面前執行 79 * </p> 80 * 81 * @param dataLog dataLog 82 * @return void 83 * @author Tophua 84 * @since 2020/7/15 85 */ 86 @SneakyThrows 87 @Before("@annotation(dataLog)") 88 public void before(DataLog dataLog) { 89 // 獲取執行緒名,使用執行緒名作為同一次操作記錄 90 String threadName = Thread.currentThread().getName(); 91 TEM_MAP.put(threadName, new LinkedList<>()); 92 } 93 94 /** 95 * <p> 96 * 切面後執行 97 * </p> 98 * 99 * @param dataLog dataLog 100 * @return void 101 * @author Tophua 102 * @since 2020/7/15 103 */ 104 @SneakyThrows 105 @After("@annotation(dataLog)") 106 public void after(DataLog dataLog) { 107 // 獲取執行緒名,使用執行緒名作為同一次操作記錄 108 String threadName = Thread.currentThread().getName(); 109 List<DataTem> list = TEM_MAP.get(threadName); 110 if (CollUtil.isEmpty(list)) { 111 return; 112 } 113 list.forEach(dataTem -> { 114 List<?> oldData = dataTem.getOldData(); 115 if (CollUtil.isEmpty(oldData)) { 116 return; 117 } 118 String ids = oldData.stream().map(o -> { 119 try { 120 Method method = o.getClass().getMethod("getId"); 121 return method.invoke(o).toString(); 122 } catch (Exception e) { 123 e.printStackTrace(); 124 return null; 125 } 126 }).filter(ObjectUtil::isNotNull).collect(Collectors.joining(",")); 127 String sql = dataTem.getSql() + "(" + ids + ")"; 128 try { 129 List<?> newData = Db.use(dataSource).query(sql, dataTem.getEntityType()); 130 dataTem.setNewData(newData); 131 System.out.println("oldData:" + JSONUtil.toJsonStr(dataTem.getOldData())); 132 System.out.println("newData:" + JSONUtil.toJsonStr(dataTem.getNewData())); 133 134 } catch (SQLException e) { 135 e.printStackTrace(); 136 } 137 }); 138 // 非同步對比存庫 139 this.compareAndSave(list); 140 } 141 142 /** 143 * <p> 144 * 對比儲存 145 * </p> 146 * 147 * @param list list 148 * @return void 149 * @author Tophua 150 * @since 2020/7/15 151 */ 152 @Async 153 public void compareAndSave(List<DataTem> list) { 154 StringBuilder sb = new StringBuilder(); 155 list.forEach(dataTem -> { 156 List<?> oldData = dataTem.getOldData(); 157 List<?> newData = dataTem.getNewData(); 158 // 按id排序 159 oldData.sort(Comparator.comparingLong(d -> { 160 try { 161 Method method = d.getClass().getMethod("getId"); 162 return Long.parseLong(method.invoke(d).toString()); 163 } catch (Exception e) { 164 e.printStackTrace(); 165 } 166 return 0L; 167 })); 168 newData.sort(Comparator.comparingLong(d -> { 169 try { 170 Method method = d.getClass().getMethod("getId"); 171 return Long.parseLong(method.invoke(d).toString()); 172 } catch (Exception e) { 173 e.printStackTrace(); 174 } 175 return 0L; 176 })); 177 178 for (int i = 0; i < oldData.size(); i++) { 179 final int[] finalI = {0}; 180 sameClazzDiff(oldData.get(i), newData.get(i)).forEach(r -> { 181 if (finalI[0] == 0) { 182 sb.append(StrUtil.LF); 183 sb.append(StrUtil.format("修改表:【{}】", dataTem.getTableName())); 184 sb.append(StrUtil.format("id:【{}】", r.getId())); 185 } 186 sb.append(StrUtil.LF); 187 sb.append(StrUtil.format("把欄位[{}]從[{}]改為[{}]", r.getFieldName(), r.getOldValue(), r.getNewValue())); 188 finalI[0]++; 189 }); 190 } 191 }); 192 if (sb.length() > 0) { 193 sb.deleteCharAt(0); 194 } 195 // 存庫 196 System.err.println(sb.toString()); 197 } 198 199 /** 200 * <p> 201 * 相同類對比 202 * </p> 203 * 204 * @param obj1 obj1 205 * @param obj2 obj2 206 * @return java.util.List<com.lith.datalog.handle.CompareResult> 207 * @author Tophua 208 * @since 2020/7/15 209 */ 210 private List<CompareResult> sameClazzDiff(Object obj1, Object obj2) { 211 List<CompareResult> results = new ArrayList<>(); 212 Field[] obj1Fields = obj1.getClass().getDeclaredFields(); 213 Field[] obj2Fields = obj2.getClass().getDeclaredFields(); 214 Long id = null; 215 for (int i = 0; i < obj1Fields.length; i++) { 216 obj1Fields[i].setAccessible(true); 217 obj2Fields[i].setAccessible(true); 218 Field field = obj1Fields[i]; 219 try { 220 Object value1 = obj1Fields[i].get(obj1); 221 Object value2 = obj2Fields[i].get(obj2); 222 if ("id".equals(field.getName())) { 223 id = Long.parseLong(value1.toString()); 224 } 225 if (!ObjectUtil.equal(value1, value2)) { 226 CompareResult r = new CompareResult(); 227 r.setId(id); 228 r.setFieldName(field.getName()); 229 // 獲取註釋 230 r.setFieldComment(field.getName()); 231 r.setOldValue(value1); 232 r.setNewValue(value2); 233 results.add(r); 234 } 235 } catch (IllegalAccessException e) { 236 e.printStackTrace(); 237 } 238 } 239 return results; 240 } 241 242 }
3、測試及結果
經過測試,不管怎麼使用資料更新操作,結果都可以進行攔截記錄,完美達到預期。
小筆這裡並沒有將記錄儲存在資料庫,由大家自行儲存。
測試demo
1 package com.lith.datalog.controller; 2 3 import com.baomidou.mybatisplus.core.toolkit.Wrappers; 4 import com.lith.datalog.annotation.DataLog; 5 import com.lith.datalog.entity.User; 6 import com.lith.datalog.mapper.UserMapper; 7 import com.lith.datalog.service.UserService; 8 import lombok.AllArgsConstructor; 9 import org.springframework.transaction.annotation.Transactional; 10 import org.springframework.web.bind.annotation.*; 11 12 /** 13 * <p> 14 * UserController 15 * </p> 16 * 17 * @author Tophua 18 * @since 2020/5/7 19 */ 20 @RestController 21 @AllArgsConstructor 22 @RequestMapping("/user") 23 public class UserController { 24 25 private final UserService userService; 26 private final UserMapper userMapper; 27 28 @GetMapping("{id}") 29 public User getById(@PathVariable Integer id) { 30 return userService.getById(id); 31 } 32 33 @DataLog 34 @PostMapping 35 public Boolean save(@RequestBody User user) { 36 return userService.save(user); 37 } 38 39 @DataLog 40 @PutMapping 41 @Transactional(rollbackFor = Exception.class) 42 public Boolean updateById(@RequestBody User user) { 43 User nUser = new User(); 44 nUser.setId(2); 45 nUser.setName("程式碼更新"); 46 nUser.updateById(); 47 userService.update(Wrappers.<User>lambdaUpdate() 48 .set(User::getName, "批量") 49 .in(User::getId, 3, 4)); 50 userMapper.updateTest(); 51 return userService.updateById(user); 52 } 53 54 @DeleteMapping("{id}") 55 public Boolean removeById(@PathVariable Integer id) { 56 return userService.removeById(id); 57 } 58 }
結果顯示:
Time:2 ms - ID:com.lith.datalog.mapper.UserMapper.updateById Execute SQL:UPDATE user SET name='程式碼更新' WHERE id=2 Time:2 ms - ID:com.lith.datalog.mapper.UserMapper.update Execute SQL:UPDATE user SET name='批量' WHERE (id IN (3,4)) Time:2 ms - ID:com.lith.datalog.mapper.UserMapper.updateTest Execute SQL:update user set age = 44 where id in (5,6) Time:0 ms - ID:com.lith.datalog.mapper.UserMapper.updateById Execute SQL:UPDATE user SET name='4564', age=20, email='dsahkdhkashk' WHERE id=1 oldData:[{"name":"1","id":2,"age":10,"email":"dsahkdhkashk"}] newData:[{"name":"程式碼更新","id":2,"age":10,"email":"dsahkdhkashk"}] oldData:[{"name":"1","id":3,"age":10,"email":"dsahkdhkashk"},{"name":"1","id":4,"age":10,"email":"dsahkdhkashk"}] newData:[{"name":"批量","id":3,"age":10,"email":"dsahkdhkashk"},{"name":"批量","id":4,"age":10,"email":"dsahkdhkashk"}] oldData:[{"name":"1","id":5,"age":10,"email":"dsahkdhkashk"},{"name":"1","id":6,"age":10,"email":"dsahkdhkashk"}] newData:[{"name":"1","id":5,"age":44,"email":"dsahkdhkashk"},{"name":"1","id":6,"age":44,"email":"dsahkdhkashk"}] oldData:[{"name":"1","id":1,"age":10,"email":"dsahkdhkashk"}] newData:[{"name":"4564","id":1,"age":20,"email":"dsahkdhkashk"}] 修改表:【user】id:【2】 把欄位[name]從[1]改為[程式碼更新] 修改表:【user】id:【3】 把欄位[name]從[1]改為[批量] 修改表:【user】id:【4】 把欄位[name]從[1]改為[批量] 修改表:【user】id:【5】 把欄位[age]從[10]改為[44] 修改表:【user】id:【6】 把欄位[age]從[10]改為[44] 修改表:【user】id:【1】 把欄位[name]從[1]改為[4564] 把欄位[age]從[10]改為[20]
4、總結
本次綜合前車經驗,優化設計思想,改為從底層具體執行的 sql 語句入手,通過解析表名及更新條件來構造資料更新前後的查詢sql,再使用Spring AOP對方法執行前後進行處理,記錄更新前後的資料。最後再使用java反射機制將資料更新前後進行對比記錄。
注:
使用AOP涉及到一點,就是需要保證AOP與Spring 資料庫事務之間的執行順序,如果AOP先執行然後再提交事務,那結果則是資料無變化。
在此小筆已將AOP處理級別放到最後,保證先提交事務再去查詢更新後的資料,這樣才能得出正確的結果。
歡迎各路大神交流意見。。。。。。
最後附上原始碼地址: