This is the first day of my participation in the August More Text challenge


1. Introduction

In front of the article said that MyBatis when executing SQL, you need to first by MappedStatement. GetBoundSql BoundSql (), this method will be completed in the XML in the SQL statement to replace # ${} / {}. ${} is a simple string substitution based on the argument value. #{} is replaced with a placeholder. BoundSql contains the SQL that can be executed.

The following SQL example is displayed:

select * from user where id = #{id}
Copy the code

BoundSql BoundSql = BoundSql

select * from user where id = ?
Copy the code

Once you have the SQL Statement to execute, the Executor calls the prepareStatement() method to get the executable Statement, which automatically sets Timeout and FetchSize properties while creating the Statement.

Now that you have your SQL Statement, you need one more step to execute the Statement. To set parameters, the corresponding JDBC interface is:

Preparedstatement.setxxx (parameter subscript, parameter value)Copy the code

StatementHandler is the interface provided by MyBatis to handle statements. Parameterize () is used to set parameters. It has three main implementation classes, as the name indicates:

  1. SimpleStatementHandler processes Statement.
  2. PreparedStatementHandler Processes a PreparedStatement.
  3. CallableStatementHandler processes CallableStatement.

For the Statement, is not support set parameters, thus SimpleStatementHandler. Parameterize () method is empty, do nothing.

Only PreparedStatement and CallableStatement are required to set parameters. CallableStatement is used to call stored procedures. We will skip this step and focus on setting parameters in PreparedStatement.

2. DefaultParameterHandler

The source code for Parameterize () in PreparedStatement is very simple. It delegates parameter setting to ParameterHandler. ParameterHandler is the interface provided by MyBatis to set parameters in a PreparedStatement.

interface ParameterHandler {

  /** * MyBatis will convert the parameter to ParamMap */ when the Mapper interface method is called
  Object getParameterObject(a);

  /** * Set parameters * for PreparedStatement@param ps
   * @throws SQLException
   */
  void setParameters(PreparedStatement ps);

}
Copy the code

The ParameterHandler interface is simple and has clear responsibilities. It consists of two methods: obtaining a parameter object and setting parameters to a PreparedStatement.

DefaultParameterHandler is the only implementation class provided by MyBatis.

  1. TypeHandlerRegistry is the registry of TypeHandler. For example, if your parameter is Long, the corresponding implementation is LongTypeHandler. MyBatis relies on TypeHandler to set the parameters of a PreparedStatement.
  2. The MappedStatement is the description object of the SQL node. As mentioned earlier, its only function here is to log against the StatementID.
  3. ParameterObject is a ParameterObject parsed by MyBatis. ParameterObject is a mapping of parameter names and parameter values.
  4. BoundSql is the associated SQL object by which you know what the SQL statement to execute is.
  5. Configuration is a global Configuration object. You need to rely on it to create MetaObject metadata.

With attributes out of the way, constructors are next. The constructor for DefaultParameterHandler is a simple assignment, so I won’t post any code here.

The next thing to look at is the most important method for DefaultParameterHandler, setParameters().

It first reports to ErrorContext that it is setting parameters so that it can locate the problem if an exception occurs. It then fetches the ParameterMapping list through BoundSql. ParameterMapping is a description object for #{ID} parameters in XML. It records the attribute names, JavaType, JdbcType, and so on.

The following is an example:

#{id,javaType=Long,jdbcType=BIGINT}
Copy the code

In most cases, we can omit javaType and jdbcType, MyBatis will automatically deduce the type.

ParameterMapping lets you know what parameters you need to set for SQL statements, such as parameter names and parameter types. The next step is to extract the parameters from parameterObject, find the corresponding TypeHandler based on JavaType, and set the parameters using TypeHandler.

void setParameters(PreparedStatement ps) {
  // Report that you are setting parameters
  // Get the parameter mapping
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  / / traverse
  if(parameterMapping.getMode() ! = ParameterMode.OUT) {// Filter output type parameters
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          // Parameter types In TypeHandlerRegistry, for example, when the parameter has only one and is of a simple type, such as Long, the parameter value is parameterObject itself
          value = parameterObject;
        } else {
          // Get object metadata
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          /** * Gets the value based on the property name * For ParamMap, call the get(key) method directly * corresponding to the custom Bean, reflection calls the getter method of the property */
          value = metaObject.getValue(propertyName);
        }
        // Get TypeHandler. Our argument is Long, so it's LongTypeHandler
        JdbcType jdbcType = parameterMapping.getJdbcType();
        if (value == null && jdbcType == null) {// JdbcType is not configured
          /* MyBatis can infer the JdbcType from the Java type when the argument is not empty. Jdbctype. OTHER In the Oracle database, if jdbctype. OTHER is NULL, an exception is reported :JdbcType */ is invalid
          jdbcType = configuration.getJdbcTypeForNull();
        }
        // Set parameters
        typeHandler.setParameter(ps, i + 1, value, jdbcType); }}}Copy the code

As mentioned earlier, MyBatis does not encapsulate the parameter as ParamMap if the method has only one parameter and does not annotate it with @param.

There are two cases in which the parameter itself is returned: 1. The parameter is of a simple type, such as Long, and TypeHandlerRegistry already has a corresponding TypeHandler. 2. The parameter is a custom Bean, and there is no corresponding TypeHandler in The TypeHandlerRegistry.

In the first case, when parameterObject is directly assigned to value, MyBatis already has the ability to set parameters.

In the second case, a custom complex object whose attributes we use in XML, MyBatis creates a MetaObject object. MetaObject is a bit complicated, so I won’t expand it here. All you need to know is that it represents the metadata of the object, through which you can read and write properties of the object. The bottom layer is realized by reflection.

The following is an example:

List<User> select(User user);

<select id="select">
select * from user where id = {id} and age = #{age}
</select>
Copy the code

MyBatis uses #{id}/#{age} as its parameter. This is done by calling the getId() and getAge() methods of the User object with reflection to get the property values and then setting the parameters.

Now that you know which value to set for a parameter, the next step is to find the corresponding TypeHandler based on the parameter type. As mentioned earlier, you can omit JavaType and JdbcType, and MyBatis will automatically infer from the object type, like this.

If JavaType and JdbcType are not configured, the TypeHandler is UnknownTypeHandler, which stands for UnknownTypeHandler. It deduces based on the Java type of the argument:

// TypeHandler is automatically derived from the parameter object type
TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
// Set parameters
Copy the code

The resolveTypeHandler() method deduces TypeHandler based on the Java type.

// TypeHandlerRegistry has containers that record TypeHandlers corresponding to JavaType
handler = typeHandlerRegistrySupplier.get().getTypeHandler(parameter.getClass(), jdbcType);
Copy the code

In the example code, our argument is Long ID, so TypeHandler is LongTypeHandler. Let’s look at the code that sets the argument, which simply calls a JDBC native method.

// Set the I parameter to a value of type Long
ps.setLong(i, parameter);
Copy the code

At this point, the parameter setting process is complete, and the PreparedStatement can be executed by calling the execute() method.

If the parameter is NULL, MyBatis will not get the parameter type and will not be able to derive the JdbcType and TypeHandler. In this case, you can only set JdbcType to jdbctype.other. Under Oracle, passing NULL in this case will throw an exception, so you must specify the parameter’s JdbcType in the XML.

3. Summary

ParameterHandler is used to set parameters in a PreparedStatement. When MyBatis pars SQL tag nodes in an XML file, ParameterHandler will parse #{} parameters into ParameterMapping objects. It lets you know the parameter name, JavaType, JdbcType, and so on. Then parses the parameter values in ParamMap according to the parameter names, finds the corresponding TypeHandler according to the JavaType of the parameter, and sets the parameters of the corresponding type using TypeHandler.

By default, we do not need to specify the type of the parameter in XML. MyBatis will perform reflection inference based on the type of the object, but only if the object is not allowed to be NULL. Once the object is NULL, we cannot obtain its Class object, and we do not know the Java object type, which indirectly causes the JDBC type to be derived.