java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Mybatis-Plus自动属性填充与自定义Insert into语句顺序

Mybatis-Plus自动属性填充与自定义Insert into语句顺序详解

作者:拽着尾巴的鱼儿

本文通过调试发现,Mybatis-Plus在拼接SQL语句时未使用自定义属性填充的值,原因是语句拼接在属性填充之前,因此,需要在自定义SQL语句中去除`<if>`的非空判断,直接使用`#{testFiled}`,这样最终在进行数据插入时,Mybatis会动态的替换掉该占位符

前言

系统中使用了Mybatis-Plus 自动属性填充为实体统一进行属性的填值,在Mapper的xml 文件中 insert into 语句 使用<if test="id != null">id,</if> 进行判断会发现该属性是空的,明明已经为改字段进行了属性的自动填充,为什么Mybatis- 在拼接sql 语句时依然认为 改属性是空的呢;

1 问题重现

1.1 在实体中使用了属性填充属性:

 @TableField(fill = FieldFill.INSERT)
 private String testFiled;

1.2 在拦截器里进行了属性填充:

@Override
public void insertFill(MetaObject metaObject) {
	this.setFieldValByName("testFiled", "test", metaObject);
}

1.3 mapper xml :

 insert into ${prefix}knowledge_authority
 <trim prefix="(" suffix=")" suffixOverrides=",">
   <if test="id != null">id,</if>
   <if test="testFiled != null">test_filed,</if>
  </trim>
 <trim prefix="values (" suffix=")" suffixOverrides=",">
   <if test="id != null">#{id},</if>
    <if test="testFiled != null">#{testFiled },</if>
  </trim>

在对实体id 设置完成之后,进行数据的插入,发现插入的数据中只有id 没有testFiled 属性;

2 推断问题产生的原因

<if> 标签只进行简单的空判断,出问题的可能性不大,从原因2 入手:

使用自定义属性填充时,会调用MybatisParameterHandler 类中的process()方法完成属性填充的调用;

 private void process(Object parameter) {
  if (parameter != null) {
       TableInfo tableInfo = null;
       Object entity = parameter;
       if (parameter instanceof Map) {
           Map<?, ?> map = (Map)parameter;
           if (map.containsKey("et")) {
               Object et = map.get("et");
               if (et != null) {
                   entity = et;
                   tableInfo = TableInfoHelper.getTableInfo(et.getClass());
               }
           }
       } else {
           tableInfo = TableInfoHelper.getTableInfo(parameter.getClass());
       }

       if (tableInfo != null) {
           MetaObject metaObject = this.configuration.newMetaObject(entity);
           if (SqlCommandType.INSERT == this.sqlCommandType) {
           		// 插入时 id 的填充
               this.populateKeys(tableInfo, metaObject, entity);
               // 这里会在insert 时 调用我们自己定义的拦截器进行属性的自动填充
               this.insertFill(metaObject, tableInfo);
           } else {
            // 这里会在update 时 调用我们自己定义的拦截器进行属性的自动填充
               this.updateFill(metaObject, tableInfo);
           }
       }
   }

}

通过debug 我们发现,在插入数据时确实调用了process 方法,并对实体完成了属性的填充,属性填充是正常的;所以会不会是原因3 ,属性填充的时机和sql 拼接的时机不同造成的。

如果 先进行了sql的拼接,此时进行 <if> 判断时 发现改属性为空,必然会跳过了该属性的拼接,即使后面自动填充为属性填充了数据,但是由于sql已经完成了拼接,最终执行的sql 也是没有该属性的;

基于此猜想,我们将xml 中 判断标签去掉,只保留占位符:

 insert into ${prefix}knowledge_authority
 <trim prefix="(" suffix=")" suffixOverrides=",">
   <if test="id != null">id,</if>
  	test_filed,
  </trim>
 <trim prefix="values (" suffix=")" suffixOverrides=",">
   <if test="id != null">#{id},</if>
     #{testFiled },
  </trim>

此时在次进行插入,发现插入成功,并且testFiled 属性也是有值的;

3 从Mybatis-Plus 代码层面查看sql 语句的拼接

3.1 先看下sql 拼接的流程:

MybatisParameterHandler 是 Mybatis 中用于处理数据库操作参数的接口,它的实现类 DefaultParameterHandler 负责将 Java 对象转换为 JDBC 预处理语句需要的参数值,以及将参数值设置到预处理语句中。其中,BoundSql 对象就是用于封装 SQL 语句和对应的参数值的。

BoundSql 对象的赋值过程主要由 SqlSource 和 ParameterMapping 来完成,具体流程如下:

3.2 MybatisParameterHandler 中的 BoundSql boundSql:

public class MybatisParameterHandler implements ParameterHandler {
    private final TypeHandlerRegistry typeHandlerRegistry;
    private final MappedStatement mappedStatement;
    private final Object parameterObject;
    private final BoundSql boundSql;
    private final Configuration configuration;
    private final SqlCommandType sqlCommandType;

    public MybatisParameterHandler(MappedStatement mappedStatement, Object parameter, BoundSql boundSql) {
        this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
        this.mappedStatement = mappedStatement;
        // 拼接好的sql
        this.boundSql = boundSql;
        this.configuration = mappedStatement.getConfiguration();
        this.sqlCommandType = mappedStatement.getSqlCommandType();
        // 主键id 和 属性的自动填充
        this.parameterObject = this.processParameter(parameter);
    }
}

可以看到在创建MybatisParameterHandler 对象时,boundSql 已经完成了sql 的解析和拼接,然后在this.processParameter(parameter) 方法完成了主键id 和 属性的自动填充,从构造方法可以看到,boundSql 的拼接是先于processParameter(parameter) 属性填充的方法的,这就解释了为什么我们明明已经为改属性进行了填充,为什么 最终自定义的insert into 语句 标签判断是空的,本质就是因为两者的顺序问题;

3.3 sql 语句的拼接:

进入DynamicSqlSource 类getBoundSql 方法:

public class DynamicSqlSource implements SqlSource {
    private final Configuration configuration;
    private final SqlNode rootSqlNode;

    public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
        this.configuration = configuration;
        this.rootSqlNode = rootSqlNode;
    }

    public BoundSql getBoundSql(Object parameterObject) {
    	// 参数解析
        DynamicContext context = new DynamicContext(this.configuration, parameterObject);
       	// sql 拼接
        this.rootSqlNode.apply(context);
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(this.configuration);
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        // 占位符拼接
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        // sql 拼接
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        context.getBindings().forEach(boundSql::setAdditionalParameter);
        return boundSql;
    }
}

this.rootSqlNode.apply(context):

public class MixedSqlNode implements SqlNode {
    private final List<SqlNode> contents;

    public MixedSqlNode(List<SqlNode> contents) {
        this.contents = contents;
    }

    public boolean apply(DynamicContext context) {
    	// 这里会判断xml 中的所有属性标签,只有判断为true ,才进行属性的拼接
        this.contents.forEach((node) -> {
            node.apply(context);
        });
        return true;
    }
}

可以看到这里会根据标签不同调用不同的实现完成判断:

并且会逐个进行属性的判断,只有为true 才进行属性拼接:

以IfSqlNode 为例,可以看出只有当属性不为空时,才返回true 否则返回false,只有在返回true 时后续才会对改属性进行拼接

4 总结

Mybatis-Plus 自定义的sql 语句其BoundSql的解析和拼接是在属性填充之前进行的,所以如果在自定义sql 语句中使用了<if>标签进行属性的非空判断,就不会拼接改属性,此时需要在自定义的sql 中去除<<if>的非空判断直接使用#{testFiled },这样最终在进数据插入时,Mybatis会动态的替换掉改占位符。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文