美文网首页
基于Spring Aop+Mybatis实现修改记录

基于Spring Aop+Mybatis实现修改记录

作者: 天不错啊 | 来源:发表于2019-12-30 20:27 被阅读0次

    一、前言

    工作时有个需求,记录修改记录。但是好几张表关联在一起,如果一张表一张表写,造成大量的业务代码,还浪费时间。
    在此基础上想到一个办法,利用AOP实现修改记录。

    基于环境:springboot springaop mybatis

    二、注意点及实现方案

    1. 表结构设计

    CREATE TABLE `biz_update_record` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `type` char(2) DEFAULT NULL COMMENT '标识',
      `type_id` varchar(51) DEFAULT NULL COMMENT '标识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 '操作唯一标识',
      `remark` varchar(500) DEFAULT NULL COMMENT '描述',
      `create_time` datetime DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=918 DEFAULT CHARSET=utf8mb4;
    

    2. 思路实现

    自定义注解 解决主表与关联表的关系和描述,使用AOP完成拦截器的功能。
    根据不同的service 调用不同的selectXXByXXId完成旧数据的查询。

    2.1 Update注解

    public @interface Update {
        ......
        /**
         * 插入的ID
         */
        String id() default "id";
    
        /**
         * update查询的主键
         */
        String primaryKey() default "id";
    
        /**
         * 说明内容
         */
        String remark() default "";
    
        /**
         * 读取内容转表达式 (如: 0=男|1=女|2=未知)
         */
        String readConverterExp() default "";
        ......
    }
    

    主要几个属性:

    • id:当前表的ID
    • primaryKey:主表的ID
    • remark:字段的描述
    • readConverterExp:支持内容表达式(必须按照规定结构)

    2.2 主要实现流程

    • 1 aop 拦截注解
    • 2 根据不同的操作类型,执行不同handler方法。(以Update为例)
    • 3 update方法中形参的Class对象,转成默认的mapper.selectXXByXXId方法查询旧数据。
    • 4 旧数据与新数据对比后,执行插入操作。

    三、注意点及实现方案

    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()));
                    // 解析内容表达式
                    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属性 代表当前数据的标识
                    if ((update.field())) {
                        list.set(0, new UpdateField(field.getName(), update, value));
                    }
                }
            }
            return list;
        }
    

    2. 形参转默认mapper查询方法

        /**
         *
         * 根据当前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. 执行handler( 以UPDATE操作为例)
    private void handleUpdateRecord(Update update, Object newObj) {
            try {
                Class clazz = newObj.getClass();
    
                //根据默认方法名称 查询 update之前数据
                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 描述的成员是不能修改的
    • 由于水平有限,代码只供参考。这代码质量用到项目里怕是有点难。。。
    • 项目里比较乱,东拼西凑的代码,请见谅。

    五、未解决问题

    如果拦截的方法抛了异常。回滚的问题目前还在考虑!!!

    六、总结

    总体上来说功能算是实现了,但是细节上得打磨一下。我估摸着如果公司项目里不出BUG,我是不会改了,能跑就行要求不高,水平有限。
    项目地址

    相关文章

      网友评论

          本文标题:基于Spring Aop+Mybatis实现修改记录

          本文链接:https://www.haomeiwen.com/subject/wxtvoctx.html