美文网首页
基于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