精尽MyBatis源码分析 - MyBatis初始化(四)之 SQL 初始化(下)

初始化(四)之SQL初始化(下)

在上一篇文档中详细地讲述了MyBatis在解析<select /> <insert /> <update /> <delete /> 节点的过程中,是如何解析SQL语句的,如何实现动态SQL语句的,最终会生成一个org.apache.ibatis.mapping.SqlSource对象的,那么接下来我们来看看SqlSource到底是什么

主要包路径:org.apache.ibatis.mapping、org.apache.ibatis.builder

主要涉及到的类:

org.apache.ibatis.builder.SqlSourceBuilder:继承了BaseBuilder抽象类,SqlSource构建器,负责将SQL语句中的#{}替换成相应的?占位符,并获取该?占位符对应的 ParameterMapping对象

org.apache.ibatis.builder.ParameterExpression:继承了HashMap<String, String>,参数表达式处理器,在SqlSourceBuilder处理#{}的内容时,需要通过其解析成key-value键值对

org.apache.ibatis.mapping.ParameterMapping:保存#{}中配置的属性参数信息

org.apache.ibatis.mapping.SqlSource:SQL 资源接口,用于创建BoundSql对象(包含可执行的SQL语句与参数信息)

org.apache.ibatis.mapping.BoundSql:用于数据库可执行的SQL语句的最终封装对象

org.apache.ibatis.scripting.defaults.DefaultParameterHandler:实现了ParameterHandler接口,用于将入参设置到java.sql.PreparedStatement预编译对象中

用于将入参设置到java.sql.PreparedStatement预编译对象中

我们先来回顾一下org.apache.ibatis.scripting.xmltags.XMLScriptBuilder的parseScriptNode()方法,将 SQL 脚本(XML或者注解中定义的 SQL )解析成 SqlSource 对象

代码如下:

public SqlSource parseScriptNode() {

    // 解析 XML 或者注解中定义的 SQL

    MixedSqlNode rootSqlNode = parseDynamicTags(context);

    SqlSource sqlSource;

    if (isDynamic) {

        // 动态语句,使用了 ${} 也算

        sqlSource = new DynamicSqlSource(configuration, rootSqlNode);

    } else {

        sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);

    }

    return sqlSource;

}

如果是动态 SQL 语句,使用了 MyBatis 的自定义标签(<if /> <foreach />等)或者使用了 ${} 都是动态 SQL 语句,则会创建DynamicSqlSource对象

否则就是静态 SQL 语句,创建 RawSqlSource 对象

SqlSource接口的实现类如下图所示:

SqlSource

SqlSourceBuilder

org.apache.ibatis.builder.SqlSourceBuilder:继承了BaseBuilder抽象类,SqlSource构建器,负责将SQL语句中的#{}替换成相应的?占位符,并获取该?占位符对应的 org.apache.ibatis.mapping.ParameterMapping 对象

构造方法

public class SqlSourceBuilder extends BaseBuilder {


private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";

public SqlSourceBuilder(Configuration configuration) {

super(configuration);

}

}

其中PARAMETER_PROPERTIES字符串定义了#{}中支持定义哪些属性,在抛异常的时候用到

parse方法

解析原始的SQL(仅包含#{}定义的参数),转换成StaticSqlSource对象

因为在DynamicSqlSource调用该方法前会将MixedSqlNode进行处理,调用其apply方法进行应用,根据DynamicContext上下文对MyBatis的自定义标签或者包含${}的SQL生成的SqlNode进行逻辑处理或者注入值,生成一个SQL(仅包含#{}定义的参数)

代码如下:

/**

* 执行解析原始 SQL ,成为 SqlSource 对象

*

* @param originalSql          原始 SQL

* @param parameterType        参数类型

* @param additionalParameters 上下文的参数集合,包含附加参数集合(通过 <bind /> 标签生成的,或者`<foreach />`标签中的集合的元素)

*                            RawSqlSource传入空集合

*                            DynamicSqlSource传入 {@link org.apache.ibatis.scripting.xmltags.DynamicContext#bindings} 集合

* @return SqlSource 对象

*/

public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {

    // <1> 创建 ParameterMappingTokenHandler 对象

    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);

    // <2> 创建 GenericTokenParser 对象

    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);

    /*

    * <3> 执行解析

    * 将我们在 SQL 定义的所有占位符 #{content} 都替换成 ?

    * 并生成对应的 ParameterMapping 对象保存在 ParameterMappingTokenHandler 中

    */

    String sql = parser.parse(originalSql);

    // <4> 创建 StaticSqlSource 对象

    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());

}

该方法的入参originalSql为原始的SQL,也就是其所有的SqlNode节点已经应用了,也就是都调用了apply方法

包含的${}也已经注入了对应的值,所以这里只剩#{}定义的入参了

创建ParameterMappingTokenHandler处理器对象handler

创建GenericTokenParser对象,用于处理#{}中的内容,通过handler将其转换成?占位符,并创建对应的ParameterMapping对象

执行解析,获取最终的 SQL 语句

创建StaticSqlSource对象

ParameterMappingTokenHandler

org.apache.ibatis.builder.SqlSourceBuilder的内部类,用于解析#{}的内容,创建ParameterMapping对象,并将其替换成?占位符

代码如下:

private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {

    /**

    * 我们在 SQL 语句中定义的占位符对应的 ParameterMapping 数组,根据顺序来的

    */

    private List<ParameterMapping> parameterMappings = new ArrayList<>();

    /**

    * 参数类型

    */

    private Class<?> parameterType;

    /**

    * additionalParameters 参数的对应的 MetaObject 对象

    */

    private MetaObject metaParameters;

    public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {

        super(configuration);

        this.parameterType = parameterType;

        // 创建 additionalParameters 参数的对应的 MetaObject 对象

        this.metaParameters = configuration.newMetaObject(additionalParameters);

    }

    public List<ParameterMapping> getParameterMappings() {

        return parameterMappings;

    }

    @Override

    public String handleToken(String content) {

        // <1> 构建 ParameterMapping 对象,并添加到 parameterMappings 中

        parameterMappings.add(buildParameterMapping(content));

        // <2> 返回 ? 占位符

        return "?";

    }

    /**

    * 根据内容构建一个 ParameterMapping 对象

    *

    * @param content 我们在 SQL 语句中定义的占位符

    * @return ParameterMapping 对象

    */

    private ParameterMapping buildParameterMapping(String content) {

        // <1> 将字符串解析成 key-value 键值对保存

        // 其中有一个key为"property",value就是对应的属性名称

        Map<String, String> propertiesMap = parseParameterMapping(content);

        // <2> 获得属性的名字和类型

        String property = propertiesMap.get("property"); // 名字

        Class<?> propertyType; // 类型

        if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params

            propertyType = metaParameters.getGetterType(property);

        } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) { // 有对应的类型处理器,例如java.lang.string

            propertyType = parameterType;

        } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) { // 设置的 Jdbc Type 是游标

            propertyType = java.sql.ResultSet.class;

        } else if (property == null || Map.class.isAssignableFrom(parameterType)) { // 是 Map 集合

            propertyType = Object.class;

        } else { // 类对象

            MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());

            if (metaClass.hasGetter(property)) {

              // 通过反射获取到其对应的 Java Type

                propertyType = metaClass.getGetterType(property);

            } else {

                propertyType = Object.class;

            }

        }

        // <3> 创建 ParameterMapping.Builder 构建者对象

        ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);

        // <3.1> 初始化 ParameterMapping.Builder 对象的属性

        Class<?> javaType = propertyType;

        String typeHandlerAlias = null;

        // 遍历 SQL 配置的占位符信息,例如这样配置:"name = #{name, jdbcType=VARCHAR}"

        for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {

            String name = entry.getKey();

            String value = entry.getValue();

            if ("javaType".equals(name)) {

                javaType = resolveClass(value);

                builder.javaType(javaType);

            } else if ("jdbcType".equals(name)) {

                builder.jdbcType(resolveJdbcType(value));

            } else if ("mode".equals(name)) {

                builder.mode(resolveParameterMode(value));

            } else if ("numericScale".equals(name)) {

                builder.numericScale(Integer.valueOf(value));

            } else if ("resultMap".equals(name)) {

                builder.resultMapId(value);

            } else if ("typeHandler".equals(name)) {

                typeHandlerAlias = value;

            } else if ("jdbcTypeName".equals(name)) {

                builder.jdbcTypeName(value);

            } else if ("property".equals(name)) {

                // Do Nothing

            } else if ("expression".equals(name)) {

                throw new BuilderException("Expression based parameters are not supported yet");

            } else {

                throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}.  Valid properties are " + PARAMETER_PROPERTIES);

            }

        }

        // <3.2> 如果 TypeHandler 类型处理器的别名非空

        if (typeHandlerAlias != null) {

            builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));

        }

        // <3.3> 创建 ParameterMapping 对象

        return builder.build();

    }

    private Map<String, String> parseParameterMapping(String content) {

        try {

            return new ParameterExpression(content);

        } catch (BuilderException ex) {

            throw ex;

        } catch (Exception ex) {

            throw new BuilderException("Parsing error was found in mapping #{" + content

                    + "}.  Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);

        }

    }

}

构造方法:创建additionalParameters对应的MetaObject对象,便于操作上下文的参数集合,包含附加参数集合(通过 <bind /> 标签生成的,或者<foreach />标签中的集合的元素)

handleToken(String content)方法:

调用buildParameterMapping(content)方法,解析#{}的内容创建ParameterMapping对象

直接返回?占位符

buildParameterMapping(content)方法:

将字符串解析成 key-value 键值对,通过org.apache.ibatis.builder.ParameterExpression进行解析,其中有一个key为"property",value就是对应的属性名称

获得属性的名字和类型

创建ParameterMapping.Builder构建者对象,设置参数的名称与Java Type

将上面第1步解析到key-value键值对设置到Builder中

如果TypeHandler类型处理器的别名非空,则尝试获取其对应的类型处理器并设置到Builder中

通过Builder创建ParameterMapping对象,如果没有配置TypeHandler类型处理器,则根据参数Java Type和Jdbc Type从TypeHandlerRegistry注册中心获取并赋值到该对象中

ParameterExpression

org.apache.ibatis.builder.ParameterExpression:继承了HashMap<String, String>,参数表达式处理器,在ParameterMappingTokenHandler处理#{}的内容时需要通过其解析成key-value键值对

构造方法:

public class ParameterExpression extends HashMap<String, String> {

private static final long serialVersionUID = -2417552199605158680L;

    /**

    * 从类的注释中可以看出我们可以这样定义占位符

    * 1. #{propertyName, javaType=string, jdbcType=VARCHAR}

    * 2. #{(expression), javaType=string, jdbcType=VARCHAR}

    *

    * @param expression 我们定义的占位符表达式

    */

public ParameterExpression(String expression) {

parse(expression);

}

}

在构造函数中调用其parse(String expression)方法

private void parse(String expression) {

    // 跳过前面的非法字符(ASCII 小于33),目的是去除空格,还有非法的字符,可以参照 ASCII 字符代码表看看

    int p = skipWS(expression, 0);

    if (expression.charAt(p) == '(') {

        // 属于第二种方式,我在官方没有看到介绍,这里也不做介绍了

        expression(expression, p + 1);

    } else {

        // 将整个字符串转换成 key-value 保存至 Map.Entry

        property(expression, p);

    }

}

先出去前面的空格或者非法字符,然后调用property(String expression, int left)方法

// #{propertyName, javaType=string, jdbcType=VARCHAR}

private void property(String expression, int left) {

    if (left < expression.length()) {

        // 获取到逗号或者冒号第一个位置,也就是分隔符

        int right = skipUntil(expression, left, ",:");

    // 从内容中截取第一个逗号前面的字符串,也上面第 1 种方式的 "name"

    put("property", trimmedStr(expression, left, right));

    // 解析字符串一个逗号后面的字符串,也就是该属性的相关配置

    jdbcTypeOpt(expression, right);

    }

}

如果left开始位置小于字符串的长度,那么开始解析

调用skipUntil方法,获取从left开始,或者:第一个位置,也就是分隔符的位置

这里第一次进入的话就会先获取第一个,的位置,那么调用trimmedStr方法截取前面的字符串,也就是属性名称,然后存放一个键值对(key为property,value为属性名称)

调用jdbcTypeOpt(String expression, int p)方法,继续解析后面的字符串,也就是该属性的相关配置

private void jdbcTypeOpt(String expression, int p) {

    p = skipWS(expression, p);

    if (p < expression.length()) {

        if (expression.charAt(p) == ':') { // 属于上面第 2 种方式,不做分析

            jdbcType(expression, p + 1);

        } else if (expression.charAt(p) == ',') {

            // 将第一个 , 后面的字符串解析成 key-value 保存

            option(expression, p + 1);

        } else {

            throw new BuilderException("Parsing error in {" + expression + "} in position " + p);

        }

    }

}

如果p(第一个,的位置)后面还有字符串

则调用option(String expression, int p)方法将一个,后面的字符串解析成key-value键值对保存

/**

* 将字符串生成转换成key-value的形式

* 例如 expression = "name, jdbcType = VARCHAR, javaType = string" 设置 p = 6

* 这样将会往 Map 中保存两个键值对:"jdbcType"->"VARCHAR" "javaType"->"string"

*

* @param expression 字符串

* @param p 字符串从哪个位置转换

*/

private void option(String expression, int p) {

    int left = skipWS(expression, p);

    if (left < expression.length()) {

        // 获取 = 的位置

        int right = skipUntil(expression, left, "=");

        // 截取 = 前面的字符串,对应的 key

        String name = trimmedStr(expression, left, right);

        left = right + 1;

        // 获取 , 的位置

        right = skipUntil(expression, left, ",");

        // 截取 = 到 , 之间的字符串,也就是对应的 value

        String value = trimmedStr(expression, left, right);

        // 将 key-value 保存

        put(name, value);

        // 继续遍历后面的字符串

        option(expression, right + 1);

    }

}

逐步解析,将字符串解析成key-value键值对保存,这里保存的都是属性的相关配置,例如JdbcType配置

ParameterMapping

org.apache.ibatis.mapping.ParameterMapping:保存#{}中配置的属性参数信息,一个普通的实体类,代码如下:

/**

* SQL 语句中 ? 占位符对应的对象

*

* @author Clinton Begin

*/

public class ParameterMapping {

  /**

  * 全局配置对象

  */

  private Configuration configuration;

  /**

  * 属性名称

  */

  private String property;

  /**

  * 参数模式

  */

  private ParameterMode mode;

  /**

  * 属性的 Java Type

  * 一般可以直接通过入参对象知道,但是如果入参是 Map,需要显式指定,以确保使用正确的类型处理器

  */

  private Class<?> javaType = Object.class;

  /**

  * 属性的 Jdbc Type

  */

  private JdbcType jdbcType;

  /**

  * 对于数值类型,指定小数点后保留的位数

  */

  private Integer numericScale;

  /**

  * 类型处理器

  */

  private TypeHandler<?> typeHandler;

  /**

  * 如果 {@link mode} 为 OUT 或者 INOUT,且{@link jdbcType} 为 CURSOR(也就是 Oracle 的 REFCURSOR)

  * 必须指定一个 resultMap 引用来将结果集 ResultMap 映射到参数的类型上

  */

  private String resultMapId;

  /**

  * Jdbc Type 名称

  */

  private String jdbcTypeName;

  private String expression;

  private ParameterMapping() {

  }

}

SqlSource

org.apache.ibatis.mapping.SqlSource:SQL 资源接口,用于创建BoundSql对象(包含可执行的SQL语句与参数信息),代码如下:

/**

* Represents the content of a mapped statement read from an XML file or an annotation.

* It creates the SQL that will be passed to the database out of the input parameter received from the user.

*

* @author Clinton Begin

*/

public interface SqlSource {

    /**

    * 根据传入的参数对象,返回 BoundSql 对象

    *

    * @param parameterObject 参数对象

    * @return BoundSql 对象

    */

    BoundSql getBoundSql(Object parameterObject);

}

StaticSqlSource

org.apache.ibatis.builder.StaticSqlSource:实现 SqlSource 接口,静态的 SqlSource 实现类,代码如下:

public class StaticSqlSource implements SqlSource {

/**

* 解析后的 SQL 语句,数据库能执行

*/

private final String sql;

/**

* 上面 SQL 语句中占位符对应的 ParameterMapping 参数集合

*/

private final List<ParameterMapping> parameterMappings;

    /**

    * 全局配置对象

    */

private final Configuration configuration;

public StaticSqlSource(Configuration configuration, String sql) {

this(configuration, sql, null);

}

public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {

this.sql = sql;

this.parameterMappings = parameterMappings;

this.configuration = configuration;

}

@Override

public BoundSql getBoundSql(Object parameterObject) {

return new BoundSql(configuration, sql, parameterMappings, parameterObject);

}

}

在SqlSourceBuilder构建的SqlSource类型就是StaticSqlSource,用于获取最终的静态 SQL 语句

RawSqlSource

org.apache.ibatis.scripting.defaults.RawSqlSource:实现了SqlSource接口,静态SQL语句对应的SqlSource对象,用于创建静态 SQL 资源,代码如下:

public class RawSqlSource implements SqlSource {

  private final SqlSource sqlSource;

  public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {

    /*

    * 因为静态的 SQL 语句可以直接拿来解析,不需要根据入参就可以应用

    * 所以调用 getSql 方法获取静态的 SQL 语句

    */

    this(configuration, getSql(configuration, rootSqlNode), parameterType);

  }

  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {

    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);

    Class<?> clazz = parameterType == null ? Object.class : parameterType;

    // 通过 SqlSourceBuilder 将这个静态的 SQL 进行转换,变量替换成 ? 占位符,并生成对应的 ParameterMapping 集合

    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());

  }

  private static String getSql(Configuration configuration, SqlNode rootSqlNode) {

    DynamicContext context = new DynamicContext(configuration, null);

    // 调用 StaticTextSqlNode 将 SQL 语句拼接起来

    rootSqlNode.apply(context);

    return context.getSql();

  }

  @Override

  public BoundSql getBoundSql(Object parameterObject) {

    return sqlSource.getBoundSql(parameterObject);

  }

}

在构造函数中我们可以看到,会先调用getSql方法直接创建SqlSource

因为静态的 SQL 语句,不需要根据入参来进行逻辑上的判断处理,所以这里在构造函数中就先初始化好 SqlSource,后续需要调用Mapper接口执行SQL的时候就减少了一定的时间

getSql方法:

创建一个上下文对象DynamicContext,入参信息为null

调用StaticTextSqlNode的apply方法,将所有的SQL拼接在一起

返回拼接好的SQL语句

构造方法:

创建SqlSourceBuilder构建对象sqlSourceParser

调用sqlSourceParser的parse方法对该SQL语句进行转换,#{}全部替换成?占位符,并创建对应的ParameterMapping对象

第2步返回的StaticSqlSource对象设置到自己的sqlSource属性中

getBoundSql方法:直接通过StaticSqlSource创建BoundSql对象

DynamicSqlSource

org.apache.ibatis.scripting.defaults.DynamicSqlSource:实现了SqlSource接口,动态SQL语句对应的SqlSource对象,用于创建静态 SQL 资源,代码如下:

public class DynamicSqlSource implements SqlSource {

private final Configuration configuration;

/**

* 根 SqlNode 对象

*/

private final SqlNode rootSqlNode;

public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {

this.configuration = configuration;

this.rootSqlNode = rootSqlNode;

}

@Override

public BoundSql getBoundSql(Object parameterObject) {

// <1> 创建本次解析的动态 SQL 语句的上下文

DynamicContext context = new DynamicContext(configuration, parameterObject);

// <2> 根据上下文应用整个 SqlNode

rootSqlNode.apply(context);

// <3> 创建 SqlSourceBuilder 对象

SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);

Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();

        // <4> 通过 SqlSourceBuilder 将应用后的 SQL 进行转换,变量替换成 ? 占位符,并生成对应的 ParameterMapping 集合

SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());

// <5> 创建 BoundSql 对象

BoundSql boundSql = sqlSource.getBoundSql(parameterObject);

// <6> 添加附加参数到 BoundSql 对象中,因为上一步创建的`BoundSql`对象时候传入的仅是入参信息,没有添加附加参数

context.getBindings().forEach(boundSql::setAdditionalParameter);

return boundSql;

}

}

在构造函数中仅仅是赋值,不像RawSqlSource的构造函数一样直接可创建对应的SqlSource对象,因为动态SQL语句需要根据入参信息,来解析SqlNode节点,所以这里在getBoundSql方法中每次都会创建StaticSqlSource对象

getBoundSql方法:

创建本次解析的动态 SQL 语句的上下文,设置入参信息

根据上下文应用整个 SqlNode,内部包含的所有SqlNode都会被应用,最终解析后的SQL会保存上下文中

创建 SqlSourceBuilder 构建对象sqlSourceParser

调用sqlSourceParser的parse方法对第2步解析后的SQL语句进行转换,#{}全部替换成?占位符,并创建对应的ParameterMapping对象

通过第4步返回的StaticSqlSource对象创建BoundSql对象

添加附加参数到BoundSql对象中,因为上一步创建的BoundSql对象时候传入的仅是入参信息,没有添加附加参数(通过<bind />标签生成的,或者<foreach />标签中的集合的元素)

BoundSql

org.apache.ibatis.mapping.BoundSql:用于数据库可执行的SQL语句的最终封装对象,一个普通的实体类,代码如下:

public class BoundSql {

  /**

  * SQL 语句

  */

  private final String sql;

  /**

  * 占位符 ? 对应的入参信息

  */

  private final List<ParameterMapping> parameterMappings;

  /**

  * 入参对象

  */

  private final Object parameterObject;

  /**

  * 附加参数集合

  */

  private final Map<String, Object> additionalParameters;

  /**

  * 附加参数的 MetaObject 对象,便于操作

  */

  private final MetaObject metaParameters;

  public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {

    this.sql = sql;

    this.parameterMappings = parameterMappings;

    this.parameterObject = parameterObject;

    this.additionalParameters = new HashMap<>();

    this.metaParameters = configuration.newMetaObject(additionalParameters);

  }

  public String getSql() {

    return sql;

  }

  public List<ParameterMapping> getParameterMappings() {

    return parameterMappings;

  }

  public Object getParameterObject() {

    return parameterObject;

  }

  public boolean hasAdditionalParameter(String name) {

    String paramName = new PropertyTokenizer(name).getName();

    return additionalParameters.containsKey(paramName);

  }

  public void setAdditionalParameter(String name, Object value) {

    metaParameters.setValue(name, value);

  }

  public Object getAdditionalParameter(String name) {

    return metaParameters.getValue(name);

  }

}

DefaultParameterHandler

org.apache.ibatis.scripting.defaults.DefaultParameterHandler:实现了ParameterHandler接口,默认实现类,仅提供这个实现类,用于将入参设置到java.sql.PreparedStatement预编译对象中

回看到org.apache.ibatis.scripting.xmltags.XMLLanguageDriver语言驱动类中,实现了createParameterHandler方法,返回的参数处理器就是该对象

代码如下:

public class DefaultParameterHandler implements ParameterHandler {

private final TypeHandlerRegistry typeHandlerRegistry;

    /**

    * MappedStatement 对象

    */

private final MappedStatement mappedStatement;

    /**

    * 入参

    */

private final Object parameterObject;

    /**

    * BoundSql 对象,实际的 SQL 语句

    */

private final BoundSql boundSql;

    /**

    * 全局配置对象

    */

private final Configuration configuration;

public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {

this.mappedStatement = mappedStatement;

this.configuration = mappedStatement.getConfiguration();

this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();

this.parameterObject = parameterObject;

this.boundSql = boundSql;

}

@Override

public Object getParameterObject() {

return parameterObject;

}

@Override

public void setParameters(PreparedStatement ps) {

ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());

// 获取 SQL 的参数信息 ParameterMapping 对象

List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

if (parameterMappings != null) {

// 遍历所有参数

for (int i = 0; i < parameterMappings.size(); i++) {

ParameterMapping parameterMapping = parameterMappings.get(i);

                /*

                * OUT 表示参数仅作为出参,非 OUT 也就是需要作为入参

                */

if (parameterMapping.getMode() != ParameterMode.OUT) {

Object value;

// 获取入参的属性名

String propertyName = parameterMapping.getProperty();

/*

* 获取入参的实际值

*/

if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params

  // 在附加参数集合(<bind />标签生成的)中获取

value = boundSql.getAdditionalParameter(propertyName);

} else if (parameterObject == null) {

  // 入参为 null 则该属性也定义为 null

value = null;

} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {

  // 有类型处理器,则直接获取入参对象

value = parameterObject;

} else {

  // 创建入参对应的 MetaObject 对象并获取该属性的值

MetaObject metaObject = configuration.newMetaObject(parameterObject);

value = metaObject.getValue(propertyName);

}

// 获取定义的参数类型处理器

TypeHandler typeHandler = parameterMapping.getTypeHandler();

// 获取定义的 Jdbc Type

JdbcType jdbcType = parameterMapping.getJdbcType();

if (value == null && jdbcType == null) {

// 如果没有则设置成 'OTHER'

jdbcType = configuration.getJdbcTypeForNull();

}

try {

// 通过定义的 TypeHandler 参数类型处理器将 value 设置到对应的占位符

typeHandler.setParameter(ps, i + 1, value, jdbcType);

} catch (TypeException | SQLException e) {

throw new TypeException(

"Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);

}

}

}

}

}

}

往PreparedStatement中设置参数的大致逻辑如下:

获取SQL的参数信息ParameterMapping对象的集合,然后对其遍历

如果参数的模式不为ParameterMode.OUT(默认为ParameterMode.IN),也就是说需要作为入参,那么开始接下来的赋值

获取该参数对应的属性名称,并通过其获取到对应的值

获取到TypeHandler类型处理器(在ParameterMapping构建的时候会创建对应的TypeHandler)

获取到Jdbc Type

通过TypeHandler类型处理器,根据参数位置和Jdbc Type将属性值设置到PreparedStatement中

这样就完成对PreparedStatement的赋值,然后通过它执行SQL语句

总结

在MyBatis初始化的过程中,会将XML映射文件中的<select /> <insert /> <update /> <delete />节点解析成MappedStatement对象,其中会将节点中定义的SQL语句通过XMLLanguageDriver语言驱动类创建一个SqlSource对象,本文就是对该对象进行分析

通过SqlSource这个对象根据入参可以获取到对应的BoundSql对象,BoundSql对象中包含了数据库需要执行的SQL语句、ParameterMapping参数信息、入参对象和附加的参数(通过<bind />标签生成的,或者<foreach />标签中的集合的元素等等)

亚马逊测评 www.yisuping.com

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容