逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。
如果你需要再查出来就不应使用逻辑删除,而是以一个状态去表示。
如: 员工离职,账号被锁定等都应该是一个状态字段,此种场景不应使用逻辑删除。
若确需查找删除数据,如老板需要查看历史所有数据的统计汇总信息,请单独手写sql。
那么用户注销App是不是应该使用逻辑删除呢?因为userId在很多表中都是外键,在做OLAP的时候经常是会使用的?如果真的进行了物理删除,那么其他表是不是出现了很多脏数据?
DefaultSqlInjector里面已集成逻辑删除功能,entity 内字段注解 {@link TableLogic} 即可开启
效果: 使用mp自带方法删除和查找都会附带逻辑删除功能 (自己写的xml不会)
where deleted = 0
使用的时候deleted set default 0
alter table tbl_employee alter deleted set default 0;
在GlobalConfig中
private IKeyGenerator keyGenerator; /** * 逻辑删除全局值(默认 1、表示已删除) */ private String logicDeleteValue = "1"; /** * 逻辑未删除全局值(默认 0、表示未删除) */ private String logicNotDeleteValue = "0";
为什么给这个deleted字段加上了@TableLogic字段后,就实现逻辑删除功能了呢?
其原理就是在AbstractSqlInjector获取到methodList之后,获取TableInfo的时候
@Override public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) { List<AbstractMethod> methodList = this.getMethodList(); if (CollectionUtils.isNotEmpty(methodList)) { TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass); // 循环注入自定义方法 methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); } }
进入到TableInfoHelper类中,调用initTableInfo方法,实体类反射获取表信息,初始化表名,表字段
public static void initTableFields(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo) { /* 数据库全局配置 */ GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig(); List<Field> list = getAllFields(clazz); // 标记是否读取到主键 boolean isReadPK = false; // 是否存在 @TableId 注解 boolean existTableId = isExistTableId(list); List<TableFieldInfo> fieldList = new ArrayList<>(); for (Field field : list) { /* * 主键ID 初始化 */ if (!isReadPK) { if (existTableId) { isReadPK = initTableIdWithAnnotation(dbConfig, tableInfo, field, clazz); } else { isReadPK = initTableIdWithoutAnnotation(dbConfig, tableInfo, field, clazz); } if (isReadPK) { continue; } } /* 有 @TableField 注解的字段初始化 */ if (initTableFieldWithAnnotation(dbConfig, tableInfo, fieldList, field, clazz)) { continue; } /* 无 @TableField 注解的字段初始化 */ fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field)); } /* 检查逻辑删除字段只能有最多一个 */ Assert.isTrue(fieldList.parallelStream().filter(TableFieldInfo::isLogicDelete).count() < 2L, String.format("annotation of @TableLogic can't more than one in class : %s.", clazz.getName())); /* 字段列表 */ tableInfo.setFieldList(fieldList); /* 未发现主键注解,提示警告信息 */ if (StringUtils.isEmpty(tableInfo.getKeyColumn())) { logger.warn(String.format("Warn: Could not find @TableId in Class: %s.", clazz.getName())); } }
检查是否有@TableId @TableField注解,
对于@TableLogic注解的field,执行
/* 有 @TableField 注解的字段初始化 */ if (initTableFieldWithAnnotation(dbConfig, tableInfo, fieldList, field, clazz)) { continue; }
之后
fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field, columns[i], els[i], tableField));
在TableFieldInfo的构造方法中
tableInfo.setLogicDelete(this.initLogicDelete(dbConfig, field));
处理@TableLogic字段
/** * 逻辑删除初始化 * * @param dbConfig 数据库全局配置 * @param field 字段属性对象 */ private boolean initLogicDelete(GlobalConfig.DbConfig dbConfig, Field field) { /* 获取注解属性,逻辑处理字段 */ TableLogic tableLogic = field.getAnnotation(TableLogic.class); if (null != tableLogic) { if (StringUtils.isNotEmpty(tableLogic.value())) { this.logicNotDeleteValue = tableLogic.value(); } else { this.logicNotDeleteValue = dbConfig.getLogicNotDeleteValue(); } if (StringUtils.isNotEmpty(tableLogic.delval())) { this.logicDeleteValue = tableLogic.delval(); } else { this.logicDeleteValue = dbConfig.getLogicDeleteValue(); } return true; } return false; }
之后在DeleteById的injectMappedStatement中
/** * 逻辑删除 */ LOGIC_DELETE_BY_ID("deleteById", "根据ID 逻辑删除一条数据", "<script>\nUPDATE %s %s WHERE %s=#{%s} %s\n</script>"), LOGIC_DELETE_BY_MAP("deleteByMap", "根据columnMap 条件逻辑删除记录", "<script>\nUPDATE %s %s %s\n</script>"), LOGIC_DELETE("delete", "根据 entity 条件逻辑删除记录", "<script>\nUPDATE %s %s %s\n</script>"), LOGIC_DELETE_BATCH_BY_IDS("deleteBatchIds", "根据ID集合,批量逻辑删除数据", "<script>\nUPDATE %s %s WHERE %s IN (%s) %s\n</script>"),
判断是否要逻辑删除,如果要,那么就选择逻辑删除对应的sql
流程图