逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。
如果你需要再查出来就不应使用逻辑删除,而是以一个状态去表示。
如: 员工离职,账号被锁定等都应该是一个状态字段,此种场景不应使用逻辑删除。
若确需查找删除数据,如老板需要查看历史所有数据的统计汇总信息,请单独手写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
流程图