Springboot integra mybatis-plus para imprimir registros antes y después de la modificación de datos de la base de datos (implementación completa del código)

mybatis-plus imprime registros antes y después de la modificación de datos de la base de datos (implementación de código completo)

Prefacio: En producción, la impresión de registros es una de las mejores formas de solucionar problemas. Hay muchos proyectos desarrollados por springboot integrando mybatisplus, en uno de mis artículos he escrito claramente como imprimir logs de sql en proyectos de producción. Enlace del artículo: https://blog.csdn.net/qq798867485/article/details/129734277

Pero este tipo de impresión de registro todavía tiene ciertas deficiencias, es decir, ya sea que esté consultando o modificando, el sql se imprimirá, lo que consume muchos recursos del proyecto y afecta el rendimiento del proyecto. Otro punto es que para los datos modificados, solo puede ver el SQL modificado, pero no puede saber cómo son los datos antes de la modificación y no puede compararlos. Basado en estos dos puntos, en el proyecto mybatisplus de este artículo, está optimizado para imprimir solo el registro sql de dml e imprimir los datos antes y después de la modificación. La siguiente es la implementación de todo el código completo.

1. Crear anotaciones de registro de datos
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataUpdateLog {
    
    
    String[] value() default {
    
    "update","delete"};//可选insert,update,delete值
}
2. Interceptor de registro de datos
@Intercepts({
    
    @Signature(type = Executor.class, method = "update", args = {
    
    MappedStatement.class, Object.class})})
public class DataLogInterceptor implements Interceptor {
    
    

    private static final Logger logger = LoggerFactory.getLogger("SYS_DATA_LOG");
    private static final String DATA_LOG_TABLE_NAME = "sys_data_log";

    @Autowired
    DataLogConfiguration dataLogConfiguration;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    
    

        try {
    
    

            //未启用数据日志则跳过
            if (!dataLogConfiguration.isEnabled()) {
    
    
                return invocation.proceed();
            }

            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
            BoundSql boundSql = mappedStatement.getBoundSql(invocation.getArgs()[1]);
            Configuration configuration = mappedStatement.getConfiguration();
            String preparedSql = boundSql.getSql();

            //如果是新增操作且未启用新增日志,则跳过
            if (SqlCommandType.INSERT.equals(mappedStatement.getSqlCommandType()) && !dataLogConfiguration.isIncludeInsert()) {
    
    
                return invocation.proceed();
            }
            //忽略数据日志表操作
            if (preparedSql.contains(DATA_LOG_TABLE_NAME)) {
    
    
                return invocation.proceed();
            }
            //SQL语句
            String sql = DataLogUtils.getSql(configuration, boundSql);
            //表名
            Collection<String> tables = new TableNameParser(sql).tables();
            if (CollectionUtils.isEmpty(tables)) {
    
    //如果找不到表则跳过
                return invocation.proceed();
            }
            String tableName = CollectionUtils.isNotEmpty(tables) ? tables.iterator().next() : "";//多个表取第一个表名
            //实体
            TableInfo tableInfo = TableInfoHelper.getTableInfos().stream().filter(t -> t.getTableName().equals(tableName)).findFirst().orElse(null);//取第一个实体
            if (tableInfo == null) {
    
    //如果找不到实体类则跳过
                return invocation.proceed();
            }
            Class<?> entityType = tableInfo.getEntityType();
            if (entityType == null) {
    
    //如果找不到实体类则跳过
                return invocation.proceed();
            }
            if (!entityType.isAnnotationPresent(DataUpdateLog.class)) {
    
    //如果实体没有数据日志注解则跳过
                return invocation.proceed();
            }

            //保存日志(只处理使用Mybatis更新函数的数据)
            Object et = invocation.getArgs()[1];
            if (et instanceof Map) {
    
    
                String key = "et";
                String listKey = "collection";
                Map map = (Map) et;
                if (map.containsKey(key)) {
    
    
                    this.saveLog(mappedStatement.getSqlCommandType(), sql, tableInfo, map.get(key));
                } else if (map.containsKey(listKey) && map.get(listKey) instanceof Collection) {
    
    
                    List<Object> list = (List<Object>) map.get(listKey);
                    for (Object o : list) {
    
    
                        this.saveLog(mappedStatement.getSqlCommandType(), sql, tableInfo, o);
                    }
                }
            } else {
    
    
                this.saveLog(mappedStatement.getSqlCommandType(), sql, tableInfo, et);
            }


        } catch (Exception e) {
    
    
            logger.warn("数据日志保存失败", e);
        }

        return invocation.proceed();

    }

    @Override
    public Object plugin(Object target) {
    
    
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    
    
    }


    /**
     * 保存日志
     */
    private void saveLog(SqlCommandType sqlCommandType, String sql, TableInfo tableInfo, Object entity)  throws Exception{
    
    
        //日志内容
        DataLog dataLog = new DataLog();
        //操作类型
        dataLog.setType(sqlCommandType.name());
        //操作人
        String token = WebUtils.getToken();
        Long operatorId = 0L;
        String operatorName = "";
        if (StringUtils.isNotEmpty(token)) {
    
    
            operatorId = WebUtils.getId();
            operatorName = WebUtils.getName();
        }
        dataLog.setOperatorId(Optional.of(operatorId).orElse(0L));
        dataLog.setOperatorName(Optional.ofNullable(operatorName).orElse(""));
        dataLog.setCreateTime(LocalDateTime.now());
        //类名和方法名
        String[] classAndMethod = DataLogUtils.getClassAndMethod();
        dataLog.setClassName(classAndMethod[0]);
        dataLog.setMethodName(classAndMethod[1]);
        //SQL语句
        dataLog.setStatement(sql);
        //表名
        dataLog.setTableName(tableInfo.getTableName());
        //实体名
        dataLog.setEntityName(tableInfo.getEntityType().getSimpleName());
        //比较修改前后数据
        DataCompareResult dataCompareResult = DataLogUtils.getDataCompare(sqlCommandType, sql, tableInfo, entity);
        //主键
        dataLog.setTableId(Optional.ofNullable(dataCompareResult.getId()).map(String::valueOf).orElse(""));
        //数据变动
        dataLog.setDataChange(dataCompareResult.getDataChange().size() > 0 ? JsonUtils.toString(dataCompareResult.getDataChange()) : "");
        //写日志文件
        String requestUrl = WebUtils.getRequestUrl();
        requestUrl = new URL(requestUrl).getPath();
        logger.info("request url: {} ,sql update: {}", requestUrl,dataLog);
        //保存日志
        //DataLogServiceImpl sqlLogService = SpringUtils.getBean(DataLogServiceImpl.class);
        //sqlLogService.addOrEdit(dataLog);

    }

}

comparación de datos

public class DataCompareResult {
    
    

    private Long id;
    private LinkedHashMap<String,Object[]> dataChange;

    public Long getId() {
    
    
        return id;
    }

    public void setId(Long id) {
    
    
        this.id = id;
    }

    public LinkedHashMap<String, Object[]> getDataChange() {
    
    
        return dataChange;
    }

    public void setDataChange(LinkedHashMap<String, Object[]> dataChange) {
    
    
        this.dataChange = dataChange;
    }

}

objeto de registro de datos

public class DataLog implements Serializable {
    
    

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "操作类型")
    private String type;

    @ApiModelProperty(value = "操作人id")
    private Long operatorId;

    @ApiModelProperty(value = "操作人名称")
    private String operatorName;

    @ApiModelProperty(value = "类名称")
    private String className;

    @ApiModelProperty(value = "方法名称")
    private String methodName;

    @ApiModelProperty(value = "实体名称")
    private String entityName;

    @ApiModelProperty(value = "表名称")
    private String tableName;

    @ApiModelProperty(value = "表主键值")
    private String tableId;

    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @ApiModelProperty(value = "数据变化")
    private String dataChange;

    @ApiModelProperty(value = "查询语句")
    private String statement;


}

Configuración del registro de datos

@Configuration
@ConfigurationProperties(prefix = "data-log")
public class DataLogConfiguration {
    
    

    private boolean enabled=true;//是否开启全部实体操作日志
    private boolean includeInsert=false;//是否包含插入日志

    public boolean isEnabled() {
    
    
        return enabled;
    }

    public void setEnabled(boolean enabled) {
    
    
        this.enabled = enabled;
    }

    public boolean isIncludeInsert() {
    
    
        return includeInsert;
    }

    public void setIncludeInsert(boolean includeInsert) {
    
    
        this.includeInsert = includeInsert;
    }

    @Bean
    public DataLogInterceptor dataLogInterceptor() {
    
    
        return new DataLogInterceptor();
    }

}

Herramienta de registro de datos

public class DataLogUtils {
    
    

    private static final Logger logger = LoggerFactory.getLogger(DataLogUtils.class);

    /**
     * 获取SQL语句
     */
    public static String getSql(Configuration configuration, BoundSql boundSql) {
    
    
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (parameterMappings.size() == 0 && parameterObject == null) {
    
    
            return sql;
        }
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
    
    
            sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
        } else {
    
    
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            for (ParameterMapping parameterMapping : parameterMappings) {
    
    
                String propertyName = parameterMapping.getProperty();
                if (metaObject.hasGetter(propertyName)) {
    
    
                    Object obj = metaObject.getValue(propertyName);
                    sql = sql.replaceFirst("\\?", getParameterValue(obj));
                } else if (boundSql.hasAdditionalParameter(propertyName)) {
    
    
                    Object obj = boundSql.getAdditionalParameter(propertyName);
                    sql = sql.replaceFirst("\\?", getParameterValue(obj));
                }
            }
        }
        return sql;
    }

    /**
     * 获取参数值
     */
    public static String getParameterValue(Object o) {
    
    
        if (o == null) {
    
    
            return "";
        }
        if (o instanceof String) {
    
    
            return "'" + o.toString() + "'";
        }
        if (o instanceof Date) {
    
    
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            return "'" + formatter.format(o) + "'";
        }
        return o.toString();
    }

    /**
     * 获取调用的类和方法名称
     */
    public static String[] getClassAndMethod() {
    
    
        String[] result = new String[]{
    
    "", ""};
        StackTraceElement[] stackTraceElementArray = Thread.currentThread().getStackTrace();
        int n = stackTraceElementArray.length;
        for (int i = n - 1; i >= 0; i--) {
    
    
            String className = stackTraceElementArray[i].getClassName();
            if (className.contains(".service.")) {
    
    
                result[0] = getSimpleClassName(className);
                result[1] = stackTraceElementArray[i].getMethodName();
                break;
            } else if (className.contains(".controller.")) {
    
    
                result[0] = getSimpleClassName(className);
                result[1] = stackTraceElementArray[i].getMethodName();
            }
        }
        return result;
    }

    /**
     * 根据类全称获取简单名称
     */
    public static String getSimpleClassName(String className) {
    
    
        className = StringUtils.split(className, "$$")[0];
        int index = className.lastIndexOf(".");
        if (index != -1) {
    
    
            return className.substring(index + 1);
        }
        return className;
    }

    /**
     * 获取两个相同实体对象不同属性值
     */
    public static DataCompareResult getDiffValue(Object object1, Object object2) {
    
    

        DataCompareResult dataCompareResult = new DataCompareResult();

        //对象都为null,返回空数据
        if (object1 == null && object2 == null) {
    
    
            return dataCompareResult;
        }

        Long id = null;
        LinkedHashMap<String, Object[]> dataChange = new LinkedHashMap<>();
        if (object1 == null) {
    
    //旧对象为null
            Field[] object2Fields = object2.getClass().getDeclaredFields();
            for (int i = 0; i < object2Fields.length; i++) {
    
    
                object2Fields[i].setAccessible(true);
                Field field = object2Fields[i];
                try {
    
    
                    Object value2 = object2Fields[i].get(object2);
                    //忽略表不存在的字段
                    if (field.isAnnotationPresent(TableField.class) && !field.getAnnotation(TableField.class).exist()) {
    
    
                        continue;
                    }
                    //获取id主键值
                    if ("id".equals(field.getName())) {
    
    
                        id = Long.parseLong(value2.toString());
                    }
                    dataChange.put(field.getName(), new Object[]{
    
    "", value2});
                } catch (IllegalAccessException e) {
    
    
                    logger.error("非法操作", e);
                }
            }
        } else if (object2 == null) {
    
    //新对象为null
            Field[] object1Fields = object1.getClass().getDeclaredFields();
            for (int i = 0; i < object1Fields.length; i++) {
    
    
                object1Fields[i].setAccessible(true);
                Field field = object1Fields[i];
                try {
    
    
                    Object value1 = object1Fields[i].get(object1);
                    //忽略表不存在的字段
                    if (field.isAnnotationPresent(TableField.class) && !field.getAnnotation(TableField.class).exist()) {
    
    
                        continue;
                    }
                    //获取id主键值
                    if ("id".equals(field.getName())) {
    
    
                        id = Long.parseLong(value1.toString());
                    }
                    dataChange.put(field.getName(), new Object[]{
    
    value1, ""});
                } catch (IllegalAccessException e) {
    
    
                    logger.error("非法操作", e);
                }
            }
        } else {
    
    //旧对象和新对象都不为null
            Field[] object1Fields = object1.getClass().getDeclaredFields();
            Field[] object2Fields = object2.getClass().getDeclaredFields();
            for (int i = 0; i < object1Fields.length; i++) {
    
    
                object1Fields[i].setAccessible(true);
                object2Fields[i].setAccessible(true);
                Field field = object1Fields[i];

                try {
    
    
                    Object value1 = object1Fields[i].get(object1);
                    Object value2 = object2Fields[i].get(object2);
                    //忽略表不存在的字段
                    if (field.isAnnotationPresent(TableField.class) && !field.getAnnotation(TableField.class).exist()) {
    
    
                        continue;
                    }
                    //获取id主键值
                    if ("id".equals(field.getName())) {
    
    
                        id = Long.parseLong(value1.toString());
                    }
                    //新值为null处理
                    if (value2 == null) {
    
    
                        if (!field.isAnnotationPresent(TableField.class) || !field.getAnnotation(TableField.class).updateStrategy().equals(FieldStrategy.IGNORED)) {
    
    
                            continue;
                        }
                    }
                    if (!Objects.equals(value1, value2)) {
    
    
                        dataChange.put(field.getName(), new Object[]{
    
    value1, value2});
                    }
                } catch (IllegalAccessException e) {
    
    
                    logger.error("非法操作", e);
                }
            }
        }

        //返回数据
        dataCompareResult.setId(id);
        dataCompareResult.setDataChange(dataChange);

        return dataCompareResult;

    }

    /**
     * 查询旧数据(只获取第一条数据)
     */
    public static Object selectOldData(String sql, TableInfo tableInfo) {
    
    
        String selectSql = "AND " + sql.substring(sql.toUpperCase().lastIndexOf("WHERE") + 5);
        Map<String, Object> map = new HashMap<>(1);
        map.put(Constants.WRAPPER, Wrappers.query().eq("1", 1).last(selectSql));
        SqlSessionFactory sqlSessionFactory = SqlHelper.sqlSessionFactory(tableInfo.getEntityType());
        SqlSession sqlSession = sqlSessionFactory.openSession();
        List<?> oldData;
        try {
    
    
            oldData = sqlSession.selectList(tableInfo.getSqlStatement(SqlMethod.SELECT_LIST.getMethod()), map);
        } finally {
    
    
            SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
        }
        return oldData != null && oldData.size() > 0 ? oldData.get(0) : null;
    }

    /**
     * 比较修改前后数据
     */
    public static DataCompareResult getDataCompare(SqlCommandType sqlCommandType, String sql, TableInfo tableInfo, Object entity) {
    
    
        DataCompareResult dataCompareResult = new DataCompareResult();
        if (SqlCommandType.INSERT.equals(sqlCommandType)) {
    
    //新增
            dataCompareResult = DataLogUtils.getDiffValue(null, entity);
        } else if (SqlCommandType.UPDATE.equals(sqlCommandType)) {
    
    //修改
            dataCompareResult = DataLogUtils.getDiffValue(selectOldData(sql, tableInfo), entity);
        } else if (SqlCommandType.DELETE.equals(sqlCommandType)) {
    
    //删除
            dataCompareResult = DataLogUtils.getDiffValue(selectOldData(sql, tableInfo), null);
        }
        return dataCompareResult;
    }

}
3. Cree un nuevo comentario de registro de datos,
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataUpdateLog {
    
    
    String[] value() default {
    
    "update","delete"};//可选insert,update,delete值
}
Cuarto, aplique la anotación a la clase de entidad y tendrá efecto
@DataUpdateLog
@TableName("uc_user")
public class User implements Serializable {
    
    
    ....
}
V. Resumen

Si su servidor lo admite y su proyecto de producción requiere mantener estos registros de modificación, puede crear una tabla para mantener estos datos. Pero personalmente, no se recomienda guardar estos datos en la base de datos, ya que la cantidad de estos datos es muy grande, lo que afecta en gran medida el rendimiento. La declaración de creación de una tabla se publica a continuación, si desea usarla, también puede usarla.

CREATE TABLE `sys_data_log` (
    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',
    `type` varchar(10) NOT NULL DEFAULT '' COMMENT '操作类型',
    `operator_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '操作人id',
    `operator_name` varchar(20) NOT NULL DEFAULT '' COMMENT '操作人名称',
    `class_name` varchar(200) NOT NULL DEFAULT '' COMMENT '类名称',
    `method_name` varchar(200) NOT NULL DEFAULT '' COMMENT '方法名称',
    `entity_name` varchar(200) NOT NULL DEFAULT '' COMMENT '实体名称',
    `table_name` varchar(200) NOT NULL DEFAULT '' COMMENT '表名称',
    `table_id` varchar(20) NOT NULL DEFAULT '' COMMENT '表主键值',
    `create_time` datetime DEFAULT NULL COMMENT '创建时间',
    `data_change` mediumtext COMMENT '数据变化',
    `statement` mediumtext COMMENT '查询语句',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB COMMENT='数据库日志'

Supongo que te gusta

Origin blog.csdn.net/qq798867485/article/details/130129722
Recomendado
Clasificación