tk.mybatis 中 delete () 和 deleteByPrimaryKey () 的区别及源码解析
出现问题
在使用 tk.mybatis 的过程中,在拿到对象后,需要删除时,贪图方便直接调用了 delete () 删除方法,而当对象为空时,缺失判断导致删除了全表,在此对问题进行排查分析。
源码分析
删除接口 DeleteMapper
tk.mybatis 中删除接口如下所示:
package tk.mybatis.mapper.common.base.delete;
import org.apache.ibatis.annotations.DeleteProvider;
import tk.mybatis.mapper.annotation.RegisterMapper;
import tk.mybatis.mapper.provider.base.BaseDeleteProvider;
/**
* 通用Mapper接口,删除
*
* @param <T> 不能为空
* @author liuzh
*/
@RegisterMapper
public interface DeleteMapper<T> {
/**
* 根据实体属性作为条件进行删除,查询条件使用等号
*
* @param record
* @return
*/
@DeleteProvider(type = BaseDeleteProvider.class, method = "dynamicSQL")
int delete(T record);
}
在 DeleteMapper 接口中,@DeleteProvider 注解的 type 属性指定了 BaseDeleteProvider 类。
删除实现 DeleteProvider
package tk.mybatis.mapper.provider.base;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import tk.mybatis.mapper.mapperhelper.EntityHelper;
import tk.mybatis.mapper.mapperhelper.MapperHelper;
import tk.mybatis.mapper.mapperhelper.MapperTemplate;
import tk.mybatis.mapper.mapperhelper.SqlHelper;
import tk.mybatis.mapper.util.MetaObjectUtil;
/**
* BaseDeleteMapper实现类,基础方法实现类
*
* @author liuzh
*/
public class BaseDeleteProvider extends MapperTemplate {
public BaseDeleteProvider(Class<?> mapperClass, MapperHelper mapperHelper) {
super(mapperClass, mapperHelper);
}
/**
* 通过条件删除
*
* @param ms
* @return
*/
public String delete(MappedStatement ms) {
Class<?> entityClass = getEntityClass(ms);
StringBuilder sql = new StringBuilder();
//如果设置了安全删除,就不允许执行不带查询条件的 delete 方法
if (getConfig().isSafeDelete()) {
sql.append(SqlHelper.notAllNullParameterCheck("_parameter", EntityHelper.getColumns(entityClass)));
}
// 如果是逻辑删除,则修改为更新表,修改逻辑删除字段的值
if (SqlHelper.hasLogicDeleteColumn(entityClass)) {
sql.append(SqlHelper.updateTable(entityClass, tableName(entityClass)));
sql.append("<set>");
sql.append(SqlHelper.logicDeleteColumnEqualsValue(entityClass, true));
sql.append("</set>");
MetaObjectUtil.forObject(ms).setValue("sqlCommandType", SqlCommandType.UPDATE);
} else {
sql.append(SqlHelper.deleteFromTable(entityClass, tableName(entityClass)));
}
sql.append(SqlHelper.whereAllIfColumns(entityClass, isNotEmpty()));
return sql.toString();
}
/**
* 通过主键删除
*
* @param ms
*/
public String deleteByPrimaryKey(MappedStatement ms) {
final Class<?> entityClass = getEntityClass(ms);
StringBuilder sql = new StringBuilder();
if (SqlHelper.hasLogicDeleteColumn(entityClass)) {
sql.append(SqlHelper.updateTable(entityClass, tableName(entityClass)));
sql.append("<set>");
sql.append(SqlHelper.logicDeleteColumnEqualsValue(entityClass, true));
sql.append("</set>");
MetaObjectUtil.forObject(ms).setValue("sqlCommandType", SqlCommandType.UPDATE);
} else {
sql.append(SqlHelper.deleteFromTable(entityClass, tableName(entityClass)));
}
sql.append(SqlHelper.wherePKColumns(entityClass));
return sql.toString();
}
}
可以看出,在 DeleteProvider 类中,分为两个实现方法:
- delete ():通过条件删除
- deleteByPrimaryKey ():通过主键删除
以上两个方法均使用 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();
}
delete () 条件判断
其中,delete () 方法会判断对象中的所有字段。
位于 tk.mybatis.mapper.mapperhelper.SqlHelper 中的 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;
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();
}
deleteByPrimaryKey () 条件判断
其中,deleteByPrimaryKey () 方法会判断对象中的主键字段。
位于 tk.mybatis.mapper.mapperhelper.SqlHelper 中的 wherePKColumns () 方法:
/**
* where主键条件
*
* @param entityClass
* @param entityName
* @param useVersion
* @return
*/
public static String wherePKColumns(Class<?> entityClass, String entityName, boolean useVersion) {
StringBuilder sql = new StringBuilder();
boolean hasLogicDelete = hasLogicDeleteColumn(entityClass);
sql.append("<where>");
//获取全部列
Set<EntityColumn> columnSet = EntityHelper.getPKColumns(entityClass);
//当某个列有主键策略时,不需要考虑他的属性是否为空,因为如果为空,一定会根据主键策略给他生成一个值
for (EntityColumn column : columnSet) {
sql.append(" AND ").append(column.getColumnEqualsHolder(entityName));
}
if (useVersion) {
sql.append(whereVersion(entityClass));
}
if (hasLogicDelete) {
sql.append(whereLogicDelete(entityClass, false));
}
sql.append("</where>");
return sql.toString();
}
以上会在项目启动时生成对应的 SQL 模板。
实际示例
准备工作
新建表名
delimiter $$
CREATE TABLE `book` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`author` varchar(50) NOT NULL,
`description` varchar(1000) NOT NULL,
`isbn` varchar(10) NOT NULL,
`title` varchar(250) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1$$
对象类:
@Data
@Table(name="book")
public class Book {
@Id
@GeneratedValue(generator = "JDBC")
private Long id;
private String author;
private String description;
private String title;
}
删除模板
则在项目启动时生成对应的 SQL 删除模板。
通过断点我们可以得到如下结果。
delete () 删除模板如下:
DELETE FROM book
<where>
<if test="id != null"> AND id = #{id}</if>
<if test="author != null"> AND author = #{author}</if>
<if test="description != null"> AND description = #{description}</if>
<if test="title != null"> AND title = #{title}</if>
</where>
deleteByPrimaryKey () 删除模板如下:
DELETE FROM book
<where>
AND id = #{id}
</where>
结论
从源码和运行结果可知:
- delete () 在参数全 null 的情况下回删除全表!在使用时需要特别注意!
- deleteByPrimaryKey () 方法在传入空参数时,则不存在该问题。
deleteByPrimaryKey 执行空参数日志如下:
c.j.m.BookMapper.deleteByPrimaryKey : ==> Preparing: DELETE FROM book WHERE id = ?
c.j.m.BookMapper.deleteByPrimaryKey : ==> Parameters: null
c.j.m.BookMapper.deleteByPrimaryKey : <== Updates: 0
相关文章