问题
今天遇到了一个问题,某个版本之后,出现了几次某张数据表被清空的情况,第一次还以为是谁手动删除的数据,但是出现第二第三次,就怀疑到代码头上了,找了一下是有人使用第三方tk.mybatis的方法不慎导致的bug,接下来讲一下分析过程和结论
Tkmybatis是基于Mybatis框架开发的一个工具,通过调用它提供的方法实现对单表的数据操作,不需要写任何sql语句,这极大地提高了项目开发效率
定位问题
全局搜了操作被清空表的地方,mapper.xml里没有delete语句,那就可能是调用了第三方方法导致不规范导致的,然后定位到下图的代码,delete()
发现是tk.mybatis包封装的方法,这就应证了猜想,入参有问题,具体源码分析放下一章,总的来说,就是传入参数是null,导致拼接的动态SQL没有where和之后的限制条件,导致全表删除
入参就一个set了主键id的对象
来找一下调用的地方,发现在controller层的一个finally块中,每次必被调用
看一下接口信息,接口传入了该参数,
而在代码中,参数必被置为null,然后被赋予新值
经过了解,这个很长的方法代码是贴过来用的,中间产生了许多无效的操作和数据库操作,所以要最后执行一下清除操作
为什么8月前后发生了几次清表操作呢,因为之前这个删除操作不在finally中,被修改后,try块中的代码只要发生错误,就会走到finally,此时临时的参数值还没有生成,导致直接执行
delete from ******表
源码分析
项目中基本所有mapper都实现了Mapper接口,下面是继承路径
Mapper接口 --tk.mybatis.mapper.common
BaseMapper接口 --tk.mybatis.mapper.common
BaseDeleteMapper接口 --tk.mybatis.mapper.common.base
DeleteMapper接口 --tk.mybatis.mapper.common.base.delete
BaseDeleteProvider类 --tk.mybatis.mapper.provider.base
在DeleteMapper接口中,@DeleteProvider注解的type属性指定了BaseDeleteProvider类
/**
* 根据实体属性作为条件进行删除,查询条件使用等号
*
* @param record
* @return
*/
@DeleteProvider(type = BaseDeleteProvider.class, method = "dynamicSQL")
int delete(T record);
BaseDeleteProvider类中,就是delete()方法本法
/**
* 通过条件删除
*
* @param ms
* @return
*/
public String delete(MappedStatement ms) {
//获取实体类
Class<?> entityClass = getEntityClass(ms);
StringBuilder sql = new StringBuilder();
//如果设置了安全删除,就不允许执行不带查询条件的 delete 方法,默认false不开启
if (getConfig().isSafeDelete()) {
sql.append(SqlHelper.notAllNullParameterCheck("_parameter", EntityHelper.getColumns(entityClass)));
}
// 如果是逻辑删除,则修改为更新表,修改逻辑删除字段的值,实际没有,所以false
if (SqlHelper.hasLogicDeleteColumn(entityClass)) {
sql.append(SqlHelper.updateTable(entityClass, tableName(entityClass)));
sql.append("<set>");
sql.append(SqlHelper.logicDeleteColumnEqualsValue(entityClass, true));
sql.append("</set>");
//修改为UPDATE
MetaObjectUtil.forObject(ms).setValue("sqlCommandType", SqlCommandType.UPDATE);
} else {
//执行的是这一行,sql = "DELETE FROM 表名 "
sql.append(SqlHelper.deleteFromTable(entityClass, tableName(entityClass)));
}
//添加条件,isNotEmpty中变量默认为不判断
sql.append(SqlHelper.whereAllIfColumns(entityClass, isNotEmpty()));
return sql.toString();
}
deleteFromTable进行了表名解析
/**
* delete tableName - 动态表名
*
* @param entityClass
* @param defaultTableName
* @return
*/
public static String deleteFromTable(Class<?> entityClass, String defaultTableName) {
StringBuilder sql = new StringBuilder();
sql.append("DELETE FROM ");
sql.append(getDynamicTableName(entityClass, defaultTableName));
sql.append(" ");
return sql.toString();
}
whereAllIfColumns中进行了参数拼接
/**
* where所有列的条件,会判断是否!=null
*
* @param entityClass
* @param empty
* @param useVersion
* @return
*/
public static String whereAllIfColumns(Class<?> entityClass, boolean empty, boolean useVersion) {
StringBuilder sql = new StringBuilder();
boolean hasLogicDelete = false;
//添加的只是mybatis的标签
sql.append("<where>");
//获取全部列
Set<EntityColumn> columnSet = EntityHelper.getColumns(entityClass);
EntityColumn logicDeleteColumn = SqlHelper.getLogicDeleteColumn(entityClass);
//当某个列有主键策略时,不需要考虑他的属性是否为空,因为如果为空,一定会根据主键策略给他生成一个值
for (EntityColumn column : columnSet) {
if (!useVersion || !column.getEntityField().isAnnotationPresent(Version.class)) {
// 逻辑删除,后面拼接逻辑删除字段的未删除条件
if (logicDeleteColumn != null && logicDeleteColumn == column) {
hasLogicDelete = true;
continue;
}
sql.append(getIfNotNull(column, " AND " + column.getColumnEqualsHolder(), empty));
}
}
//加乐观锁
if (useVersion) {
sql.append(whereVersion(entityClass));
}
if (hasLogicDelete) {
sql.append(whereLogicDelete(entityClass, false));
}
sql.append("</where>");
return sql.toString();
}
在项目启动时生成SQL模板,
delete()结果如下(举例):
DELETE FROM db_test.t_test_table
<where>
<if test="id != null">
AND id = #{id}
</if>
<if test="col != null">
AND col = #{col}
</if>
</where>
deleteByPrimaryKey()结果如下:
DELETE FROM db_test.t_test_table
<where>
AND id = #{id}
</where>
当使用的时候,就像我们拿自己写在mapper.xml中的SQL一样
至于tk mybatis怎么与mybatis整合,参数怎么拼进动态SQL,就另说了
结论
从源码和运行结果可知,delete()在参数全null的情况下回删除全表
代码中不止有delete(),还有deleteByPrimaryKey()、deleteByCondition()、updateByPrimaryKeySelective等方法,update也有把未设置值的字段更新为null的先例
建议
1、使用第三方函数时,建议看源码实现
2、如果不想参考第一点,自己写SQL比较好
3、在yml配置文件中设置safeDelete,见文档https://github.com/abel533/Mapper/wiki/3.config
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/93743.html