Public search “code road mark”, point of concern not lost!

The title of this article should have been “ParameterHandler for Mybatis Source code”, but that’s not enough to cover the content of this article. After reading it, you’ll know that ParameterHandler is only a tiny part of the setup process.

According to my understanding, the parameter setting process of Mybatis is divided into several steps: parameter parsing, dynamic SQL to static SQL, precompiled parameter assignment, among which the dynamic SQL to static SQL process will involve precompiled ${} parameter assignment and precompiled parameter parsing. This article will start from a simple example to understand the whole process of Mybatis parameter setting, the article will involve some common questions:

  • The ${}Placeholder processing flow;
  • # {}Placeholder processing flow;
  • ParameterHandler#setParameters

Starting from the Demo

This demo is modified on the basis of the original, but it is only the mapper.xml part, you first have a simple impression, convenient for subsequent articles to analyze.

  • CompanyMapper.xml

There is only one SELECT query statement in XML. To analyze the difference between ${} and #{} placeholders, I set the parameter ID in two different ways in one statement.


      
<! DOCTYPEmapper PUBLIC "- / / mybatis.org//DTD Mapper / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.raysonxin.dao.CompanyDao">
    <resultMap id="baseResultMap" type="com.raysonxin.dataobject.CompanyDO">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="cpy_type" property="cpyType"/>
    </resultMap>

    <select id="selectById" resultMap="baseResultMap">
        select id,name,cpy_type from company
        <where>
            <if test="id ! = null">
                id = ${id} or id = #{id}
            </if>
        </where>
    </select>
</mapper>
Copy the code
  • CompanyDao.java
public interface CompanyDao {
    /** * Query CompanyDO ** by ID@paramCmpId Primary key ID *@returnQuery result */
    CompanyDO selectById(@Param("id") Integer cmpId);
}
Copy the code
  • Program.java
public class Program {
    public static void main(String[] args) throws IOException {
        // Mybatis configuration file
        String path = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(path);
        / / get SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        / / create a SqlSession
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
            CompanyDao dao = sqlSession.getMapper(CompanyDao.class);
            CompanyDO companyDO = dao.selectById(1); System.out.println(companyDO); }}}Copy the code

The following figure is from the previous article “Mybatis source SQL execution process”, after the above analysis, we know that the method of Mapper interface is through the dynamic proxy proxy class MapperProxy delegated to SqlSession corresponding interface execution, The mapperxyinvoke #invoke is the real entry point to the Mapper method, and this is where the parameter setting process I will examine begins (specifically, the MapperMethod#execute method called by the mapperxy# invoke method).

The parameter setting process of Mybatis is a little different from the parameter setting of the call method in our normal sense. Because we call a method on the Mapper interface, its parameters, though defined in the interface, are actually applied to SQL statements, which preoccupy the parameters with placeholders (${} and #{}).

Here in fact, there may be a certain difference, Mapper interface method defined by the parameter name, number, type, order and so on must match the REQUIREMENTS of SQL statement? If yes, how does Mybatis set the parameters of the SQL statement as expected with the given parameters? With these questions, we start Mybatis parameter setting process analysis!

Argument parsing

Mapper interface methods call MapperMethod when executed, and their execution depends on the parameter requirements of SqlSource in MappedStatement. Therefore, Mybatis needs to resolve Mapper interface parameter names into a common and understandable way before the method is executed. At the same time, some features are introduced to enable developers to customize parameters.

With the sample code, we use the select statement, which will execute the select command, and MapperMethod#execute only posts the code associated with the select for illustration.

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      // omit INSERT, UPDATE, DELTE, FLUSH...
      // Just look at the SELECT command
      case SELECT:
        // Has no return value or a ResultHandler
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } 
        // Return the list
        else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } 
        / / returns a Map
        else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } 
        / / returns the Cursor
        else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } 
        Return a single result
        else {
          // Parameter conversion: convert parameters to SQL command parameters
          Object param = method.convertArgsToSqlCommandParam(args);
          // call sqlssession #selectOne
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional() &&
              (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
    }
    if (result == null&& method.getReturnType().isPrimitive() && ! method.returnsVoid()) {throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

Copy the code

In the SELECT command last else code block (line 27), calls the MethodSignature# convertArgsToSqlCommandParam method, is used to put our Mapper interfaces of the incoming parameters into the SQL command. Use the breakpoint to see what happens when the method executes:In the example, we pass the interface selectById id=1 (but the parameter id doesn’t exist). This gives us a ParamMap object named param, which contains two key-value pairs: id=1,param1=1; We then hand off param to the sqlssession #selectOne method to execute. Before you worry about why, let’s take a look at the downconversion process (I’ve nicely plotted the timing of this process).Follow the code to get to ParamNameResolver#getNamedParams, having instantiated ParamNameResolver in the MapperMethod constructor. So let’s take a look at the ParamNameResolver constructor first, and then look at the getNamedParams implementation. (Same class, code together, omitting some irrelevant methods).

public class ParamNameResolver {
  // Common parameter name prefix
  private static final String GENERIC_NAME_PREFIX = "param";

  /**
   * <p>
   * The key is the index and the value is the name of the parameter.

   * The name is obtained from {@link Param} if specified. When {@link Param} is not specified,
   * the parameter index is used. Note that this index could be different from the actual index
   * when the method has special parameters (i.e. {@link RowBounds} or {@link ResultHandler}).
   * </p>
   * <ul>
   * <li>aMethod(@Param("M") int a, @Param("N") int b) -&gt; {{0, "M"}, {1, "N"}}</li>
   * <li>aMethod(int a, int b) -&gt; {{0, "0"}, {1, "1"}}</li>
   * <li>aMethod(int a, RowBounds rb, int b) -&gt; {{0, "0"}, {2, "1"}}</li>
   * </ul>
   */
  private final SortedMap<Integer, String> names;

  private boolean hasParamAnnotation;

  public ParamNameResolver(Configuration config, Method method) {
    // Gets the parameter type of the method, in this case Integer
    finalClass<? >[] paramTypes = method.getParameterTypes();// Get a list of @param annotations for method parameters
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<>();
    int paramCount = paramAnnotations.length;
    // get names from @Param annotations
    // Get the parameter name from @param
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
      Special parameters are RowBounds and ResultHandler
      if (isSpecialParameter(paramTypes[paramIndex])) {
        // skip special parameters
        continue;
      }
      String name = null;
      // Get annotations in parameter order
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        // check if @param is annotated
        if (annotation instanceof Param) {
          // The tag has the @param annotation
          hasParamAnnotation = true;
          // Get the parameter name in the @param annotation, in this case "cmpId"
          name = ((Param) annotation).value();
          // Stop when you get the parameters in the annotation
          break; }}// No parameter name was retrieved from the annotation
      if (name == null) {
        // @Param was not specified.
        // undefined @param is allowed to use the actual parameter name, the id in our example
        if (config.isUseActualParamName()) {
          // Get the actual parameter names in the method
          name = getActualParamName(method, paramIndex);
        }
        // Take the index order of the parameters as their names, such as "0", "1"...
        if (name == null) {
          // use the parameter index as the name ("0", "1", ...)
          // gcode issue #71name = String.valueOf(map.size()); }}// The parameter index sequence is key, and the name obtained in the above procedure is value, which is stored in map
      // In the example: 0->id
      map.put(paramIndex, name);
    }
    names = Collections.unmodifiableSortedMap(map);
  }
    
  / /...
    
  /** * 

* A single non-special parameter is returned without a name. * Multiple parameters are named using the naming rule. * In addition to the default names, this method also adds the generic names (param1, param2, * ...) . *

*/
public Object getNamedParams(Object[] args) { final int paramCount = names.size(); // If args is null or paramCount=0, null is returned if (args == null || paramCount == 0) { return null; } // Returns the first element of args without @param annotation and with only one argument else if(! hasParamAnnotation && paramCount ==1) { return args[names.firstKey()]; } // Use @param annotations or have multiple arguments else { final Map<String, Object> param = new ParamMap<>(); int i = 0; / / traverse the names for (Map.Entry<Integer, String> entry : names.entrySet()) { // Add to param with the value of names as the key and args as the value param.put(entry.getValue(), args[entry.getKey()]); // add generic param names (param1, param2, ...) // Add general parameters, such as param1 and param2.. final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1); // ensure not to overwrite parameter named with @Param // To prevent duplication, overwrite existing parameters if(! names.containsValue(genericParamName)) { param.put(genericParamName, args[entry.getKey()]); } i++; }returnparam; }}}Copy the code

The ParamNameResolver constructor retrieves the parameter name, which bypasses special types of parameters, such as RowBounds and ResultHandler, to be stored in SortedMap in K-V format. The priority rules for obtaining parameter names are:

  • First, get the name of the parameter set in the @param annotation, such as id in the example;
  • Second, use the parameter names in the interface method definition, such as cmpId in the example;
  • Finally, use parameter index numbers as names, such as “0”, “1”…

The getNamedParams method is used to construct the mapping relationship between parameter names and parameter values. ** first **** stores the parameter names and parameter values obtained in the constructor into the map, and then adds the relationship between the generic parameter names and parameter values. ** Thus, in cases where the parameter name in the interface method signature or @param annotation does not conflict with the generic parameter, each parameter value corresponds to two keys, as shown in the debugging screenshot above.

Although it takes a lot of space, but the process of parameter parsing is relatively simple, Mybatis combined with global configuration, through reflection technology, @param annotation and other ways, the interface input parameter into a Map structure, the next call SqlSession related interface to complete the execution.

Converting dynamic SQL to static SQL [Optional]

Dynamic SQL refers to DynamicSqlSource and static SQL refers to StaticSqlSource. My understanding of both is as follows:

  • DynamicSqlSource: The SQL statement has dynamic elements, which need to be converted into a structured SQL statement that can be directly assigned values at run time.
  • StaticSqlSource: Contains an exampleselect * from tb_test where id=?Such SQL as well as parameter mapping list, just need to put the?Replace the parameter with the specified parameter.

If a dynamic element is used in a SQL statement (e.g. If tag, ${} placeholder, etc.) MappedStatement#SqlSource type is DynamicSqlSource, otherwise RawSqlSource. RawSqlSource encapsulates a StaticSqlSource, which has been statically processed and parameters extracted from SQL statements. In our example, our SQL statement uses tags like WHERE and if, which is clearly dynamic SQL. So, let’s follow the flow of dynamic SQL to analyze.

The main process

In the “parameter resolution” section, the code calls the sqlssession #selectOne method. As the code executes, we can go to a key method, MappedStatement#getBoundSql, which is used to get a BoundSql object. Let’s take a look at the execution logic of this code:

  public BoundSql getBoundSql(Object parameterObject) {
    // Get the BoundSql object from SqlSource
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    // Get the parameter mapping list in BoundSql
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();  
    // If parameterMappings is empty, the parameter mapping configuration in XML is used.
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // This is the first time I've seen it
    return boundSql;
  }
Copy the code

GetBoundSql is used to obtain BoundSql objects from SqlSource. If parameterMappings are not obtained, ParameterMap in Configuration (that is, the XML Configuration file) is considered as parameterMappings. We will focus on the process of obtaining from SqlSource: the example is dynamic SQL, so the SqlSource here is an instance of DynamicSqlSource.

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;
  }
//org.apache.ibatis.scripting.xmltags.DynamicSqlSource#getBoundSql
  public BoundSql getBoundSql(Object parameterObject) {
    // Create a dynamic context object, which contains mainly the parsed results of the parameters obtained in section 1
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    // A MixedSqlNode instance is a MixedSqlNode instance, which consists of a variety of different SQLNodes. Apply calls the APPLY method of SqlNode in turn
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = newSqlSourceBuilder(configuration); Class<? > parameterType = parameterObject ==null ? Object.class : parameterObject.getClass();
    // The dynamic SQL conversion process is here.
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    // Create a BoundSql object
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    returnboundSql; }}Copy the code
  • The DynamicContext object stores the parameter information needed in this stage, provides the tool method for concatenating parsed SQL statements, and stores processed SQL statements. It runs through the whole process of SqlSource parsing.
  • RootSqlNode is an instance of MixedSqlNode, which stores the SqlNode list contained in THE SQL statement through contents field. The apply method calls each SqlNode#apply method in turn to complete the calculation of SqlNode expression and result output. For example, determine whether the SQL in the tag meets the execution condition by evaluating the result of the expression. Because there are many types of SqlNode and different processing methods, the specific types need to be analyzed.
  • SqlSourceBuilder is the utility class used to build SqlSource, specifically the Build class for StaticSqlSource. It is through this process that the dynamic to static process is completed, extracting the SQL statement through# {}Placeholder preset parameter mapping information.

DynamicSqlSource#getBoundSql method execution process is more clear, the whole is divided into two steps: according to the input parameters of MixedSqlNode each child SqlNode static, and then parse SQL statement# {}The parameter mapping information defined by the placeholder and the?Instead, create the StaticSqlSource object and BoundSql. First, the following figure summarizes the execution process of this stage, where the yellow notes need to be expanded in detail.

MixedSqlNode#apply

MixedSqlNode code is shown as follows, contents stores all SQLNodes contained in SQL, while apply iterates through SQLnodes in MixedSqlNode#contents and calls apply.

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

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

  @Override
  public boolean apply(DynamicContext context) {
    for (SqlNode sqlNode : contents) {
      sqlNode.apply(context);
    }
    return true; }}Copy the code

The contents in the example are shown below, with two StaticTextsQLNodes and an IfSqlNode (which also stores a MixedSqlNode with a TextSqlNode inside).The MixedSqlNode#apply method can be easily analyzed by following the StaticTextSqlNode, IfSqlNode, and TextSqlNode implementations of apply.

  • StaticTextSqlNode: static text, no dynamic element or parameter handling, just concatenated SQL statements;
  //org.apache.ibatis.scripting.xmltags.StaticTextSqlNode#apply
  public boolean apply(DynamicContext context) {
    context.appendSql(text);
    return true;
  }

  // org.apache.ibatis.scripting.xmltags.DynamicContext#appendSql
  public void appendSql(String sql) {
    sqlBuilder.append(sql);
    sqlBuilder.append("");
  }
Copy the code
  • IfSqlNode: involves the judgment of ONGL expression, when the expression is established, then call SqlNode#apply method in IfSqlNode.
  //org.apache.ibatis.scripting.xmltags.IfSqlNode#apply
  public boolean apply(DynamicContext context) {
    // Continue with the expression.
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      contents.apply(context);
      return true;
    }
    return false;
  }
Copy the code
  • At this point, the SqlNode type is MixedSqlNode, similar to the logic above, so it will execute to TextSqlNode.

**** TextSqlNode#apply**** Although the code is only two lines, ** this process is a bit more complicated, involving the ${} placeholder parsing process, we focus on. The code is as follows:

  // org.apache.ibatis.scripting.xmltags.TextSqlNode#apply
  public boolean apply(DynamicContext context) {
    // Create a ${} placeholder parser, where GenericTokenParser is generic and BindingTokenParser is a placeholder replacement implementation
    GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
    // parse ${} to complete the substitution and concatenate the result into an SQL statement
    context.appendSql(parser.parse(text));
    return true;
  }

  private GenericTokenParser createParser(TokenHandler handler) {
    // GenericTokenParser is created to open and close contents contained in '${' and'} '
    return new GenericTokenParser("${"."}", handler);
  }
Copy the code

GenericTokenParser is a generic SQL statement placeholder parser class that matches expressions contained between openToken (e.g. ${, #{) and closeToken (e.g.}) from start to finish. If an expression is matched, it is processed through the TokenHandler interface object. BindingTokenParser is used to handle ${}.

In the example, the text entry to the GenericTokenParser#parse method is (as shown above in TextSqlNode) :

id = ${id} or id = #{id}
Copy the code

The GenericTokenParser parse method is long but simple. Let’s focus on the BindingTokenParser#handleToken method when an expression is matched:

  private static class BindingTokenParser implements TokenHandler {
    // Context object containing parameters and SQL statement parsing results
    private DynamicContext context;
    private Pattern injectionFilter;

    public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
      this.context = context;
      this.injectionFilter = injectionFilter;
    }

    @Override
    // The input parameter content is an expression matched by GenericTokenParser, in this case "id"
    public String handleToken(String content) {
      // Get the input parameter object. The example is ParamMap, which contains "id"->1 and "param1"->1
      Object parameter = context.getBindings().get("_parameter");
      if (parameter == null) {
        context.getBindings().put("value".null);
      } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
        context.getBindings().put("value", parameter);
      }
      // Use ongl to get the corresponding value of the expression.
      Object value = OgnlCache.getValue(content, context.getBindings());
      String srtValue = value == null ? "" : String.valueOf(value); // issue #274 return "" instead of "null"
      // SQL intrusion detection
      checkInjection(srtValue);
      return srtValue;
    }

    private void checkInjection(String value) {
      if(injectionFilter ! =null && !injectionFilter.matcher(value).matches()) {
        throw new ScriptingException("Invalid input. Please conform to regex"+ injectionFilter.pattern()); }}}Copy the code

As we walk down the code, we know that the BindingTokenParser#handleToken method completes the substitution of GenericTokenParser#parse to the expression matching the id with the corresponding value 1. Therefore, the SQL statement changes as follows:

# GenericTokenParser#parse input parameter id= ${id} or id =#{id} # GenericTokenParser#parse output result id= 1 or id = #{id}
Copy the code

The results are saved in DynamicContext and MixedSqlNode#apply is executed. The SqlNode list in MixedSqlNode#contents is also treated as a concise SQL statement, as follows:

select id,name,cpy_type from company WHERE id = 1 or id = #{id} 
Copy the code

SqlSourceBuilder#parse

As mentioned earlier, SqlSourceBuilder#parse matches #{} placeholders in SQL statements and parses matched expressions into a list of parameter mappings. Let’s look at the process in code:

  //org.apache.ibatis.builder.SqlSourceBuilder#parse
  public SqlSource parse(String originalSql, Class
        parameterType, Map
       
         additionalParameters)
       ,> {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    GenericTokenParser parser = new GenericTokenParser("# {"."}", handler);
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }
Copy the code

SqlSourceBuilder#parse handles #{} from TextSqlNode#apply. And use the ParameterMappingTokenHandler this TokenHandler implementation class. So, we just need to know about ParameterMappingTokenHandler# handleToken process can:

    public String handleToken(String content) {
      parameterMappings.add(buildParameterMapping(content));
      return "?";
    }
Copy the code

HandleToken creates ParameterMapping using the buildParameterMapping method and adds it to the parameterMappings ParameterMapping list; And return at the same time? Replaces expressions that GenericTokenParser matched. As we say, arguments in #{} will be? Replacement.

The buildParameterMapping method is long and complex, and I don’t fully understand it yet, nor have I encountered any cases in it, so I will supplement it later when I understand it. Although not fully understood, but does not affect our grasp of the whole process. In our example, it turns the expression into ParameterMapping using the constructor parametermapping.builder, which looks for TypeHandler required for ParameterMapping for subsequent parameter processing.

Dynamic SQL into static SQL this process is analyzed, in fact, is mainly two things: one is ONGL through the input parameters of SQL node SQL fragment into static SQL, two is the parsing of SQL statement parameter placeholder ${} and #{}, Mybatis to the two processing is very different. For ${}, replace the expression with the corresponding parameter value. For #{}, extract the parameter information to ParameterMapping and replace the original expression with? .

Precompile parameter assignment

Finally the last link! After BoundSql is retrieved, Mybatis will execute SQL from CachingExecutor all the way to PreparedStatementHandler#parameterize. It performs parameter assignments to precompiled SQL using DefaultParameterHandler, which is the default implementation of ParameterHandler. When the method runs here, the key fields of DefaultParameterHandler look like this:With boundSql and other fields in the figure above, let’s look at the execution flow of the setParameters method:

 public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // Use BoundSql to get the parameter mapping list
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    // Only the parameter mapping list is not null
    if(parameterMappings ! =null) {
      // Iterate over the parameter mapping list
      for (int i = 0; i < parameterMappings.size(); i++) {
        // Get the current parameter mapping
        ParameterMapping parameterMapping = parameterMappings.get(i);
        // Only if the parameter type is not OUT, as IN
        if(parameterMapping.getMode() ! = ParameterMode.OUT) { Object value;// Get the parameter name
          String propertyName = parameterMapping.getProperty();
          // Whether the additional argument contains the current attribute, currently _parameter and _databaseId
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } 
          // Type Indicates whether the processor contains parameter types. The current parameter type is ParamMap
          else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            // Create a MetaObject
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            // Get the corresponding value of the attribute
            value = metaObject.getValue(propertyName);
          }
          // Get the type handler for the parameter mapping. Here is the UnknownTypeHandler
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          / / jdbcType
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            // Assign the ps parameter through the type handler
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

Copy the code

The execution process of the setParameters method is clear: iterating through the parameter mapping list, obtaining the corresponding values of the parameters from the parameterObject, and then assigning values to the parameters specified in the index position of the PreparedStatement through the type handler.

Note here that when assigning a PreparedStatement value via TypeHandler, Mybatis looks through the TypeHandler registry to find the TypeHandler that matches the current parameter. Mybatis provides UnknownTypeHandler for ParameterMapping by default, because ParameterMapping is initialized by “UnknownTypeHandler” (” UnknownTypeHandler “, “UnknownTypeHandler”). The default parameter type is Object. Therefore, in our example, we will first use UnknownTypeHandler, then use resolveTypeHandler to find the correct type processor IntegerTypeHandler to complete the assignment for the PreparedStatement.

conclusion

Mybatis parameter setting process is completed here, thank you very much for your patience to read here! To sum up, the parameter setting process of Mybatis is not only about parameter setting, it involves the conversion processing of Mybatis input parameters, the parameter requirements of MappedStatement, and different processing methods for different placeholders to meet the requirements of various scenarios. Finally, the parameter assignment for the PreparedStatement is completed using DefaultParameterHandler.

Our example is relatively simple, contains only one parameter, and does not involve such a complex tag, you can also open the source code to see.

This is the end of this article, hope it was useful to you, if you find it useful, click a like, ^_^! My level is limited, if you find any mistakes or improper place, welcome to criticize and correct. You can also follow my wechat official account “Xi Yi Ang Ba”.

Public search “code road mark”, point of concern not lost!