个人网站 空间 多少够,邢台同城交友,WordPress站群模版,网站建设师要求系列文章
mybatis-plus使用和原理剖析之条件构造器 文章目录一、官方说明二、使用方法三、原理剖析1.SQL注入器原理2.SelectById3.DeleteById4.总结四、风险评估一、官方说明
逻辑删除 说明: 只对自动注入的 sql 起效: 插入: 不作限制查找: 追加 where 条件过滤掉已删除数据,…系列文章
mybatis-plus使用和原理剖析之条件构造器 文章目录一、官方说明二、使用方法三、原理剖析1.SQL注入器原理2.SelectById3.DeleteById4.总结四、风险评估一、官方说明
逻辑删除 说明: 只对自动注入的 sql 起效: 插入: 不作限制查找: 追加 where 条件过滤掉已删除数据,且使用 wrapper.entity 生成的 where 条件会忽略该字段更新: 追加 where 条件防止更新到已删除数据,且使用 wrapper.entity 生成的 where 条件会忽略该字段删除: 转变为 更新 例如: 删除: update user set deleted1 where id 1 and deleted0查找: select id,name,deleted from user where deleted0 字段类型支持说明: 支持所有数据类型(推荐使用 Integer,Boolean,LocalDateTime)如果数据库字段使用datetime,逻辑未删除值和已删除值支持配置为字符串null,另一个值支持配置为函数来获取值如now() 附录: 逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案但实际就是删除。如果你需要频繁查出来看就不应使用逻辑删除而是以一个状态去表示。 二、使用方法
全局配置
mybatis-plus:global-config:db-config:logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)logic-delete-value: 1 # 逻辑已删除值(默认为 1)logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)局部配置 /*** 逻辑删除 1-已删除 0-未删除*/TableField(value is_deleted)TableLogic(value 0, delval 1)private String isDeleted;三、原理剖析 官方说明中很重要的一点是逻辑删除只对自动注入的 sql 起效也就是说XML中自定义的SQL不会自动拼接逻辑删除条件也不会将物理删除更改为逻辑删除。 为什么不可以像分页插件、多租户插件以及乐观锁插件一样通过SQL拦截器实现哪问题在于逻辑删除本质是update语句物理删除是delete语句没有见过SQL拦截器直接改变DML 语句类型的我孤陋寡闻也说不定 前面讲了逻辑删除只对自动注入的 sql 起效其中“插入”不做限制“查找”追加逻辑删除谓语片段“物理删除”改为“逻辑删除”下面主要看下SelectById和DeleteById SQL注入方法。 1.SQL注入器原理
SQL注入器
public class DefaultSqlInjector extends AbstractSqlInjector {Overridepublic ListAbstractMethod getMethodList(Class? mapperClass) {return Stream.of(new Insert(),new Delete(),new DeleteByMap(),new DeleteById(),new DeleteBatchByIds(),new Update(),new UpdateById(),new SelectById(),new SelectBatchByIds(),new SelectByMap(),new SelectOne(),new SelectCount(),new SelectMaps(),new SelectMapsPage(),new SelectObjs(),new SelectList(),new SelectPage()).collect(toList());}
}SQL方法字符串模板
package com.baomidou.mybatisplus.core.enums;/*** MybatisPlus 支持 SQL 方法*/
public enum SqlMethod {/*** 插入*/INSERT_ONE(insert, 插入一条数据选择字段插入, script\nINSERT INTO %s %s VALUES %s\n/script),UPSERT_ONE(upsert, Phoenix插入一条数据选择字段插入, script\nUPSERT INTO %s %s VALUES %s\n/script),/*** 删除*/DELETE_BY_ID(deleteById, 根据ID 删除一条数据, script\nDELETE FROM %s WHERE %s#{%s}\n/script),DELETE_BY_MAP(deleteByMap, 根据columnMap 条件删除记录, script\nDELETE FROM %s %s\n/script),DELETE(delete, 根据 entity 条件删除记录, script\nDELETE FROM %s %s %s\n/script),DELETE_BATCH_BY_IDS(deleteBatchIds, 根据ID集合批量删除数据, script\nDELETE FROM %s WHERE %s IN (%s)\n/script),/*** 逻辑删除*/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 %s\n/script),LOGIC_DELETE_BATCH_BY_IDS(deleteBatchIds, 根据ID集合批量逻辑删除数据, script\nUPDATE %s %s WHERE %s IN (%s) %s\n/script),/*** 修改*/UPDATE_BY_ID(updateById, 根据ID 选择修改数据, script\nUPDATE %s %s WHERE %s#{%s} %s\n/script),UPDATE(update, 根据 whereEntity 条件更新记录, script\nUPDATE %s %s %s %s\n/script),/*** 逻辑删除 - 修改*/LOGIC_UPDATE_BY_ID(updateById, 根据ID 修改数据, script\nUPDATE %s %s WHERE %s#{%s} %s\n/script),/*** 查询*/SELECT_BY_ID(selectById, 根据ID 查询一条数据, SELECT %s FROM %s WHERE %s#{%s} %s),SELECT_BY_MAP(selectByMap, 根据columnMap 查询一条数据, scriptSELECT %s FROM %s %s\n/script),SELECT_BATCH_BY_IDS(selectBatchIds, 根据ID集合批量查询数据, scriptSELECT %s FROM %s WHERE %s IN (%s) %s/script),SELECT_ONE(selectOne, 查询满足条件一条数据, script%s SELECT %s FROM %s %s %s\n/script),SELECT_COUNT(selectCount, 查询满足条件总记录数, script%s SELECT COUNT(%s) FROM %s %s %s\n/script),SELECT_LIST(selectList, 查询满足条件所有数据, script%s SELECT %s FROM %s %s %s\n/script),SELECT_PAGE(selectPage, 查询满足条件所有数据并翻页, script%s SELECT %s FROM %s %s %s\n/script),SELECT_MAPS(selectMaps, 查询满足条件所有数据, script%s SELECT %s FROM %s %s %s\n/script),SELECT_MAPS_PAGE(selectMapsPage, 查询满足条件所有数据并翻页, script\n %s SELECT %s FROM %s %s %s\n/script),SELECT_OBJS(selectObjs, 查询满足条件所有数据, script%s SELECT %s FROM %s %s %s\n/script);private final String method;private final String desc;private final String sql;SqlMethod(String method, String desc, String sql) {this.method method;this.desc desc;this.sql sql;}public String getMethod() {return method;}public String getDesc() {return desc;}public String getSql() {return sql;}}调用链 SqlSessionFactory 若Spring容器总注入ISqlInjector.class 类型Bean则替换默认的DefaultSqlInjector com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration#sqlSessionFactory默认的DefaultSqlInjector值来源 MybatisPlus自动配置类 com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration全局默认配置中默认的SQL注入器 private ISqlInjector sqlInjector new DefaultSqlInjector(); com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils#defaults默认的SQL注入器 重点 (怎样自定义SQL注入器) com.baomidou.mybatisplus.core.injector.DefaultSqlInjectorSQL注入器过程 com.baomidou.mybatisplus.core.injector.AbstractSqlInjector#inspectInject 参考我的另一篇文章中的“TableInfo初始化过程分析”部分 mybatis-plus使用和原理剖析之条件构造器 AbstractMethod com.baomidou.mybatisplus.core.injector.AbstractMethod#inject SelectById com.baomidou.mybatisplus.core.injector.methods.SelectById#injectMappedStatement 2.SelectById
public class SelectById extends AbstractMethod {Overridepublic MappedStatement injectMappedStatement(Class? mapperClass, Class? modelClass, TableInfo tableInfo) {SqlMethod sqlMethod SqlMethod.SELECT_BY_ID;SqlSource sqlSource new RawSqlSource(configuration, String.format(sqlMethod.getSql(),sqlSelectColumns(tableInfo, false),tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),// 拼接逻辑删除谓语条件tableInfo.getLogicDeleteSql(true, true)), Object.class);return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo);}
}SQL字符串模板 SELECT_BY_ID(selectById, 根据ID 查询一条数据, SELECT %s FROM %s WHERE %s#{%s} %s) 获取逻辑删除字段的 sql 脚本 com.baomidou.mybatisplus.core.metadata.TableInfo#getLogicDeleteSql 格式化SQL脚本 com.baomidou.mybatisplus.core.metadata.TableInfo#formatLogicDeleteSql private String formatLogicDeleteSql(boolean isWhere) {final String value isWhere ? logicDeleteFieldInfo.getLogicNotDeleteValue() : logicDeleteFieldInfo.getLogicDeleteValue();if (isWhere) {if (NULL.equalsIgnoreCase(value)) {return logicDeleteFieldInfo.getColumn() IS NULL;} else {return logicDeleteFieldInfo.getColumn() EQUALS String.format(logicDeleteFieldInfo.isCharSequence() ? %s : %s, value);}}final String targetStr logicDeleteFieldInfo.getColumn() EQUALS;if (NULL.equalsIgnoreCase(value)) {return targetStr NULL;} else {return targetStr String.format(logicDeleteFieldInfo.isCharSequence() ? %s : %s, value);}}以上代码可以看到逻辑删除字段支持所有类型其中字符串类型值使用单引号拼接其余情况直接用拼接 3.DeleteById
public class DeleteById extends AbstractMethod {Overridepublic MappedStatement injectMappedStatement(Class? mapperClass, Class? modelClass, TableInfo tableInfo) {String sql;SqlMethod sqlMethod SqlMethod.LOGIC_DELETE_BY_ID;// 逻辑删除if (tableInfo.isWithLogicDelete()) {sql String.format(sqlMethod.getSql(), tableInfo.getTableName(), sqlLogicSet(tableInfo),tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),tableInfo.getLogicDeleteSql(true, true));SqlSource sqlSource languageDriver.createSqlSource(configuration, sql, Object.class);return addUpdateMappedStatement(mapperClass, modelClass, getMethod(sqlMethod), sqlSource);} else {// 物理删除sqlMethod SqlMethod.DELETE_BY_ID;sql String.format(sqlMethod.getSql(), tableInfo.getTableName(), tableInfo.getKeyColumn(),tableInfo.getKeyProperty());SqlSource sqlSource languageDriver.createSqlSource(configuration, sql, Object.class);return this.addDeleteMappedStatement(mapperClass, getMethod(sqlMethod), sqlSource);}}
}物理删除SQL字符串模板 DELETE_BY_ID(deleteById, 根据ID 删除一条数据, \nDELETE FROM %s WHERE %s#{%s}\n) 逻辑删除SQL字符串模板 LOGIC_DELETE_BY_ID(deleteById, 根据ID 逻辑删除一条数据, \nUPDATE %s %s WHERE %s#{%s} %s\n) 4.总结 Mybatis-Plus TableInfo是一个极为重要的类它在项目工程启动时通过com.baomidou.mybatisplus.core.metadata.TableInfoHelper#initTableInfo(org.apache.ibatis.builder.MapperBuilderAssistant, java.lang.Class)工具类初始化而来。 四、风险评估 前面我们已经了解到逻辑删除的工作机制但该实现方式在极端情况下可能会存在SQL注入的风险造成数据被恶意更改、数据库被删等。 触发条件很难
允许多语句执行 jdbc.url中配置 allowMultiQueriestrue 逻辑删除值配置在不安全环境中如使用了配置中心但存在弱密码或者权限控制不当导致属性值被恶意更改
mybatis-plus:global-config:db-config:logic-delete-field: isDeleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)logic-delete-value: 1 # 逻辑已删除值(默认为 1)logic-not-delete-value: 0 or 11 # 逻辑未删除值(默认为 0)或 /*** 逻辑删除 1-已删除 0-未删除*/TableField(value is_deleted)TableLogic(value 0 or 11, delval 1)private Boolean isDeleted;