基于Spring Aop+Mybatis實現(xiàn)修改記錄

一粹庞、前言

工作時有個需求豁生,記錄修改記錄。但是好幾張表關(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;
    }
  1. 執(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苹享,我是不會改了双絮,能跑就行要求不高,水平有限得问。
項目地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末囤攀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子宫纬,更是在濱河造成了極大的恐慌焚挠,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漓骚,死亡現(xiàn)場離奇詭異蝌衔,居然都是意外死亡榛泛,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門噩斟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來曹锨,“玉大人,你說我怎么就攤上這事剃允∷蚁#” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵硅急,是天一觀的道長。 經(jīng)常有香客問我佳遂,道長营袜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任丑罪,我火速辦了婚禮荚板,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吩屹。我一直安慰自己跪另,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布煤搜。 她就那樣靜靜地躺著免绿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪擦盾。 梳的紋絲不亂的頭發(fā)上嘲驾,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音迹卢,去河邊找鬼辽故。 笑死,一個胖子當(dāng)著我的面吹牛腐碱,可吹牛的內(nèi)容都是我干的誊垢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼症见,長吁一口氣:“原來是場噩夢啊……” “哼喂走!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起筒饰,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤缴啡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瓷们,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體业栅,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡秒咐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碘裕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片携取。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖帮孔,靈堂內(nèi)的尸體忽然破棺而出雷滋,到底是詐尸還是另有隱情,我是刑警寧澤文兢,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布晤斩,位于F島的核電站,受9級特大地震影響姆坚,放射性物質(zhì)發(fā)生泄漏澳泵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一兼呵、第九天 我趴在偏房一處隱蔽的房頂上張望兔辅。 院中可真熱鬧,春花似錦击喂、人聲如沸维苔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽介时。三九已至,卻和暖如春忍法,著一層夾襖步出監(jiān)牢的瞬間潮尝,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工饿序, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留勉失,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓原探,卻偏偏與公主長得像乱凿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子咽弦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 對于java中的思考的方向徒蟆,1必須要看前端的頁面,對于前端的頁面基本的邏輯型型,如果能理解最好段审,不理解也要知道幾點。 ...
    神尤魯?shù)婪?/span>閱讀 802評論 0 0
  • 1. 簡介 1.1 什么是 MyBatis 闹蒜? MyBatis 是支持定制化 SQL寺枉、存儲過程以及高級映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,425評論 0 4
  • mybatis中的sqlSession是線程安全的嗎抑淫? 鏈接:https://blog.csdn.net/qq_3...
    劉小刀tina閱讀 2,055評論 0 3
  • 這部分主要是開源Java EE框架方面的內(nèi)容,包括Hibernate姥闪、MyBatis始苇、Spring、Spring ...
    雜貨鋪老板閱讀 1,344評論 0 2
  • 想我孫女筐喳,又失眠了催式。十二點,一點避归,兩點荣月,快三點了,怎么也睡不著梳毙。我寶寶現(xiàn)在長多高了喉童,會說哪些話了,喜歡吃些什么東西...
    寒江雪810閱讀 199評論 0 0