一粹庞、前言
工作時有個需求豁生,記錄修改記錄。但是好幾張表關(guān)聯(lián)在一起供嚎,如果一張表一張表寫纤怒,造成大量的業(yè)務(wù)代碼糯而,還浪費時間。
在此基礎(chǔ)上想到一個辦法泊窘,利用AOP實現(xiàn)修改記錄熄驼。
基于環(huán)境:springboot springaop mybatis
二像寒、注意點及實現(xiàn)方案
1. 表結(jié)構(gòu)設(shè)計
CREATE TABLE `biz_update_record` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`type` char(2) DEFAULT NULL COMMENT '標(biāo)識',
`type_id` varchar(51) DEFAULT NULL COMMENT '標(biāo)識ID',
`field` varchar(255) DEFAULT NULL COMMENT '字段',
`field_remark` varchar(255) DEFAULT NULL COMMENT '字段描述',
`old_text` varchar(255) DEFAULT NULL COMMENT '舊值',
`new_text` varchar(255) DEFAULT NULL COMMENT '新值',
`action` varchar(255) DEFAULT NULL COMMENT '操作唯一標(biāo)識',
`remark` varchar(500) DEFAULT NULL COMMENT '描述',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=918 DEFAULT CHARSET=utf8mb4;
2. 思路實現(xiàn)
自定義注解 解決主表與關(guān)聯(lián)表的關(guān)系和描述,使用AOP完成攔截器的功能瓜贾。
根據(jù)不同的service 調(diào)用不同的selectXXByXXId完成舊數(shù)據(jù)的查詢诺祸。
2.1 Update注解
public @interface Update {
......
/**
* 插入的ID
*/
String id() default "id";
/**
* update查詢的主鍵
*/
String primaryKey() default "id";
/**
* 說明內(nèi)容
*/
String remark() default "";
/**
* 讀取內(nèi)容轉(zhuǎn)表達式 (如: 0=男|1=女|2=未知)
*/
String readConverterExp() default "";
......
}
主要幾個屬性:
- id:當(dāng)前表的ID
- primaryKey:主表的ID
- remark:字段的描述
- readConverterExp:支持內(nèi)容表達式(必須按照規(guī)定結(jié)構(gòu))
2.2 主要實現(xiàn)流程
- 1 aop 攔截注解
- 2 根據(jù)不同的操作類型,執(zhí)行不同handler方法祭芦。(以Update為例)
- 3 update方法中形參的Class對象筷笨,轉(zhuǎn)成默認的mapper.selectXXByXXId方法查詢舊數(shù)據(jù)。
- 4 舊數(shù)據(jù)與新數(shù)據(jù)對比后龟劲,執(zhí)行插入操作胃夏。
三、注意點及實現(xiàn)方案
1. 獲取成員屬性
/**
* 獲取類成員的名稱和注解
*/
private List<UpdateField> getClassField(Class clazz, Object obj) {
List<UpdateField> list = new ArrayList<>();
// 占位
list.add(0, null);
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
if (field.isAnnotationPresent(Update.class)) {
Update update = field.getAnnotation(Update.class);
Object value = typeFormatter(ReflectUtils.invokeGetter(obj, field.getName()));
// 解析內(nèi)容表達式
value = StringUtils.isNotEmpty(update.readConverterExp()) ? reverseByExp(value, update.readConverterExp()) : value;
if (StringUtils.isNotEmpty(update.remark())) {
UpdateField updateField = new UpdateField();
updateField.setUpdate(field.getAnnotation(Update.class));
updateField.setField(field.getName());
updateField.setProperty(value);
list.add(updateField);
}
// pojo 只允許一個field屬性 代表當(dāng)前數(shù)據(jù)的標(biāo)識
if ((update.field())) {
list.set(0, new UpdateField(field.getName(), update, value));
}
}
}
return list;
}
2. 形參轉(zhuǎn)默認mapper查詢方法
/**
*
* 根據(jù)當(dāng)前class 類名 獲取默認mapper名稱以及默認ById方法
*/
private String getName(Class clazz, String id) {
String methodName = clazz.getName().replaceAll(DEFAULT_PACKAGE_MODEL_NAME, DEFAULT_PACKAGE_MAPPER_NAME);
methodName = methodName.replaceAll(clazz.getSimpleName(), clazz.getSimpleName() + "Mapper");
methodName += ".select" + StringUtils.capitalize(clazz.getSimpleName()) + "By" + StringUtils.capitalize(id);
return methodName;
}
3. 解析表達式
/**
*
* 反向解析值
*/
private static Object reverseByExp(Object propertyValue, String converterExp) {
for (String item : converterExp.split("\\|")) {
String[] itemArray = item.split("=");
if (itemArray[0].equals(propertyValue)) {
return itemArray[1];
}
}
return propertyValue;
}
- 執(zhí)行handler( 以UPDATE操作為例)
private void handleUpdateRecord(Update update, Object newObj) {
try {
Class clazz = newObj.getClass();
//根據(jù)默認方法名稱 查詢 update之前數(shù)據(jù)
Object oldObj = sqlSession.selectOne(getName(clazz, update.primaryKey()), ReflectUtils.invokeGetter(newObj, update.primaryKey()));
Object id = ReflectUtils.invokeGetter(oldObj, update.id());
List<UpdateField> newList = getClassField(clazz, newObj);
List<UpdateField> oldList = getClassField(clazz, oldObj);
// 刪除field
oldList.remove(0);
UpdateField fieldUpdateType = newList.remove(0);
for (int i = 0; i < newList.size(); i++) {
UpdateField oldUpdateField = oldList.get(i);
UpdateField newUpdateField = newList.get(i);
UpdateRecord updateRecord = new UpdateRecord();
updateRecord.setTypeId(String.valueOf(id));
updateRecord.setType(update.updateRecordEnum().getType());
Object oldProperty = oldUpdateField.getProperty();
Object newProperty = newUpdateField.getProperty();
if (oldProperty == null) {
if (newProperty == null || (StringUtils.isEmpty(newProperty))) {
break;
}
} else if (oldProperty.getClass() == String.class && StringUtils.isEmpty(oldProperty.toString())) {
if (newProperty == null || (StringUtils.isEmpty(newProperty))) {
break;
}
} else if (!oldUpdateField.getProperty().equals(newUpdateField.getProperty())) {
updateRecord.setOldText(oldProperty.toString());
}
AsyncManager.me().execute(AsyncFactory.recordUpdate(setUpdateRecord(updateRecord, newUpdateField, fieldUpdateType)));
}
} catch (Exception e) {
e.printStackTrace();
}
}
四昌跌、使用說明
- field = true 描述的成員是不能修改的
- 由于水平有限仰禀,代碼只供參考。這代碼質(zhì)量用到項目里怕是有點難蚕愤。答恶。。
- 項目里比較亂审胸,東拼西湊的代碼亥宿,請見諒。
五砂沛、未解決問題
如果攔截的方法拋了異常烫扼。回滾的問題目前還在考慮0帧S称蟆!
六静浴、總結(jié)
總體上來說功能算是實現(xiàn)了堰氓,但是細節(jié)上得打磨一下。我估摸著如果公司項目里不出BUG苹享,我是不會改了双絮,能跑就行要求不高,水平有限得问。
項目地址