Mybatis-Plus automatic attribute filling and custom Insert into statement order

Preface: Mybatis-Plus automatic attribute filling is used in the system to fill in the attributes of entities uniformly, and the insert into statement is used in Mapper’s xml file
id, If you judge, you will find that the attribute is empty. Obviously, the attribute has been automatically filled for the changed field. Why is Mybatis- in When splicing sql statements, it still thinks that the attribute is empty;

1 Reproduce:

1.1 Attributes are used to fill attributes in entities:

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

1.2 Attribute filling is performed in the interceptor:

@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>

After the entity id is set, the data is inserted, and it is found that only the id has no testFiled attribute in the inserted data;

2 Infer the cause of the problem:

Reason 1: The attribute filling is normal, but in the xml sql statement, in some cases, there is a problem with judgment;

Reason 2: There is a problem with the custom filling attribute, which leads to the fact that the attribute to be filled is not filled with a value, resulting in a problem with the judgment of ;

Willingness 3: There is no problem with attribute filling and tag judgment, but the timing of sql splicing is performed before attribute filling;

The tag only makes a simple empty judgment, and the possibility of a problem is unlikely. Let’s start with reason 2:
When using custom attribute filling, the process() method in the MybatisParameterHandler class will be called to complete the attribute filling call;

 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) {<!-- -->
           // padding of id when inserting
               this.populateKeys(tableInfo, metaObject, entity);
               // Here we will call our own defined interceptor to automatically fill in the attributes when inserting
               this.insertFill(metaObject, tableInfo);
           } else {<!-- -->
            // Here we will call our own defined interceptor to automatically fill in the attributes during update
               this. updateFill(metaObject, tableInfo);
           }
       }
   }

}

Through debugging, we found that the process method was indeed called when inserting data, and the attribute filling of the entity was completed, and the attribute filling was normal; so could it be caused by reason 3, the timing of attribute filling and the timing of sql splicing are different .
If the sql splicing is performed first, and the judgment is made at this time and the property is found to be empty, the splicing of the attribute will inevitably be skipped, even if the attribute is automatically filled with data later, but Since the sql has been spliced, the final executed sql does not have this attribute;

Based on this conjecture, we remove the judgment tag in xml and only keep the placeholder:

 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>

At this time, the insertion is performed again, and it is found that the insertion is successful, and the testFiled attribute also has a value;

3 View the splicing of sql statements from the Mybatis-Plus code level:

3.1 First look at the process of sql splicing:
MybatisParameterHandler is an interface used to process database operation parameters in Mybatis. Its implementation class DefaultParameterHandler is responsible for converting Java objects into parameter values required by JDBC prepared statements, and setting parameter values into prepared statements. Among them, the BoundSql object is used to encapsulate SQL statements and corresponding parameter values.

The assignment process of the BoundSql object is mainly completed by SqlSource and ParameterMapping. The specific process is as follows:

  1. Before executing the SQL statement, Mybatis will generate a MappedStatement object according to the method defined by the Mapper interface and the parameters passed in. The MappedStatement contains SQL statements, parameter mapping information and other related metadata.
  2. MappedStatement is responsible for generating BoundSql objects. When generating a BoundSql object, Mybatis will first generate a StaticSqlSource object based on the SQL statement and parameter information, and then generate a DynamicSqlSource object through it. DynamicSqlSource will dynamically generate the final SQL statement and parameter values according to the incoming parameter information and the SQL statement defined by the Mapper interface. In this process, ParameterMapping is responsible for mapping and associating the attribute values in the Java object with the placeholders in the SQL statement, and SqlSource is responsible for generating the BoundSql object based on the parameter information and the SQL statement.
  3. After generating the BoundSql object, Mybatis will set the SQL statement and parameter values in the BoundSql object to the JDBC prepared statement through ParameterHandler. The default ParameterHandler implementation class is DefaultParameterHandler, which obtains the PreparedStatement object through reflection, and calls the setXxx() method to set the parameter value into the prepared statement. In the process of setting parameter values, DefaultParameterHandler will obtain the value of the corresponding property in the Java object according to the information in ParameterMapping, and assign it to the corresponding parameter placeholder in the BoundSql object.
  4. To sum up, the assignment process of BoundSql object is mainly completed by SqlSource and ParameterMapping. They will dynamically generate the final SQL statement and parameter values according to the incoming parameter information and the SQL statement defined by the Mapper interface, and set them into the BoundSql object.

3.2 BoundSql boundSql in MybatisParameterHandler:

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;
        // Spliced sql
        this. boundSql = boundSql;
        this.configuration = mappedStatement.getConfiguration();
        this.sqlCommandType = mappedStatement.getSqlCommandType();
        // autofill for primary key id and attributes
        this.parameterObject = this.processParameter(parameter);
    }
}

It can be seen that when creating the MybatisParameterHandler object, boundSql has completed the parsing and splicing of sql, and then completed the automatic filling of the primary key id and attributes in the this.processParameter(parameter) method. From the construction method, we can see that the splicing of boundSql is first It is based on the processParameter(parameter) attribute filling method, which explains why we have already filled the modified attribute, and why the final custom insert into statement label is judged to be empty, which is essentially because of the order of the two;

3.3 Splicing of sql statements:
Enter the DynamicSqlSource class getBoundSql method:

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) {<!-- -->
    // parameter parsing
        DynamicContext context = new DynamicContext(this. configuration, parameterObject);
       // sql splicing
        this.rootSqlNode.apply(context);
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(this. configuration);
        Class<?> parameterType = parameterObject == null ? Object. class : parameterObject. getClass();
        // placeholder concatenation
        SqlSource sqlSource = sqlSourceParser. parse(context. getSql(), parameterType, context. getBindings());
        // sql splicing
        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) {<!-- -->
    // All attribute tags in xml will be judged here, and attributes will be spliced only if it is judged to be true
        this.contents.forEach((node) -> {<!-- -->
            node.apply(context);
        });
        return true;
    }
}

It can be seen that different implementations will be called according to different tags to complete the judgment:

And the attribute will be judged one by one, and the attribute splicing will be performed only when it is true:

Taking IfSqlNode as an example, it can be seen that only when the attribute is not empty, true is returned, otherwise false is returned, and only when true is returned, the attribute will be spliced later

4 Summary:

The BoundSql parsing and splicing of the Mybatis-Plus custom sql statement is performed before the attribute is filled, so if the tag is used in the custom sql statement to judge whether the attribute is not empty, It will not splice and change attributes. At this time, you need to remove the non-empty judgment of < in the custom sql and directly use #{testFiled }, so that when the data is inserted, Mybatis will dynamically replace the placeholders.