MyBatis-plus 转化处理 SQL 语句的源码分析

前言

MyBatis-plus 自定义通用方法及其实现原理 中笔者介绍了 MyBatis-plus 添加通用方法的实现方式,但是其中还有一些细节需要澄清,下文笔者将详细分析

  1. MyBatis-plus 对 SQL 语句脚本的构建,以及将其嵌入MappedStatement 的过程
  2. MyBatis 使用 SqlSource 构建可执行的 SQL 语句

在这里插入图片描述

1. MyBatis-plus 对 SQL 语句脚本的处理

我们都知道 MyBatis-plus 干掉了繁琐的 XML 文件,使 MyBatis 框架的易用度、好用度大幅上升。在MyBatis-plus 源码解析 中笔者提到过,MyBatis-plus 实际是将 Mapper 方法映射为了对应的 SQL 语句脚本,这个步骤的核心就是 AbstractMethod#injectMappedStatement() 的子类实现,本文以 SelectOne#injectMappedStatement() 为例进行分析,其主要处理分为两个部分:

  1. SQL 语句脚本的构建
  2. 解析 SQL 语句脚本,将其转化为 SqlSource 封装到 MappedStatement

1.1 SQL 语句脚本的构建

  1. SelectOne#injectMappedStatement() 方法如下,SQL 语句脚本的构建实际在一个以 SqlMethod.SELECT_ONE 为主体的 String#format() 拼接操作中,重要调用的如下:

    1. 通过 sqlMethod.getSql() 调用 SqlMethod.SELECT_ONE#getSql() 方法获得 SQL 脚本主体字符串
    2. 调用 AbstractMethod#sqlFirst() 构建 SQL 脚本在正式 SQL 语句之前的部分,这里会使用字符串替换 ${ew.sqlFirst}
    3. 调用 AbstractMethod#sqlSelectColumns() 构建 SQL 语句 SELECT 查询的字段相关的脚本部分,此处会使用字符串替换 ${ew.sqlSelect}
    4. 调用 TableInfo#getTableName() 获取实际的要查询的表名
    5. 调用 AbstractMethod#sqlWhereEntityWrapper() 构建 SQL 语句 WHERE 条件相关的脚本部分,此处会使用字符串替换 ${ew.sqlSegment}
    6. 调用 AbstractMethod#sqlComment() 构建 SQL 语句尾部注释相关的脚本部分,此处会使用字符串替换 ${ew.sqlComment}
    @Override
     public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
         SqlMethod sqlMethod = SqlMethod.SELECT_ONE;
         SqlSource sqlSource = languageDriver.createSqlSource(configuration, String.format(sqlMethod.getSql(),
             sqlFirst(), sqlSelectColumns(tableInfo, true), tableInfo.getTableName(),
             sqlWhereEntityWrapper(true, tableInfo), sqlComment()), modelClass);
         return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo);
     }
    
  2. SqlMethod.SELECT_ONE#getSql() 方法实际只是个获取操作,只要看下这个枚举的定义就知道这里拿到的是个使用 %s 占位的脚本字符串

    public enum SqlMethod {
     
     ......
    
     SELECT_ONE("selectOne", "查询满足条件一条数据", "<script>%s SELECT %s FROM %s %s %s\n</script>"),
    
     private final String method;
     private final String desc;
     private final String sql;
    
     SqlMethod(String method, String desc, String sql) {
         this.method = method;
         this.desc = desc;
         this.sql = sql;
     }
    
     public String getMethod() {
         return method;
     }
    
     public String getDesc() {
         return desc;
     }
    
     public String getSql() {
         return sql;
     }
    }
    
  3. AbstractMethod#sqlFirst() 的实现很简单,可以看到这里实际就是使用 SqlScriptUtils#convertChoose() 工具类拼接 SQL 语句脚本 标签的过程,经过处理这里可以得到的脚本片段如下

    <choose>
         <when test="ew != null and ew.sqlFirst != null">
             ${ew.sqlFirst}
         </when>
         <otherwise></otherwise>
     </choose>
    
      protected String sqlFirst() {
         return SqlScriptUtils.convertChoose(String.format("%s != null and %s != null", WRAPPER, Q_WRAPPER_SQL_FIRST),
             SqlScriptUtils.unSafeParam(Q_WRAPPER_SQL_FIRST), EMPTY);
     }
    
  4. SqlScriptUtils#convertChoose() 方法如下,显然就是标签字符串的构造,没有特别操作

      public static String convertChoose(final String whenTest, final String whenSqlScript, final String otherwise) {
         return "<choose>" + NEWLINE
             + "<when test=\"" + whenTest + QUOTE + RIGHT_CHEV + NEWLINE
             + whenSqlScript + NEWLINE + "</when>" + NEWLINE
             + "<otherwise>" + otherwise + "</otherwise>" + NEWLINE
             + "</choose>";
     }
    
  5. AbstractMethod#sqlSelectColumns() 的处理大同小异,其实就是指定查询表的字段,此处可以得到如下脚本片段

    <choose>
      <when test="ew != null and ew.sqlSelect != null">
          ${ew.sqlSelect}
      </when>
      <otherwise>id,name,type</otherwise>
    </choose>
    
    protected String sqlSelectColumns(TableInfo table, boolean queryWrapper) {
         /* 假设存在 resultMap 映射返回 */
         String selectColumns = ASTERISK;
         if (table.getResultMap() == null || (table.getResultMap() != null && table.isInitResultMap())) {
             /* 普通查询 */
             selectColumns = table.getAllSqlSelect();
         }
         if (!queryWrapper) {
             return selectColumns;
         }
         return SqlScriptUtils.convertChoose(String.format("%s != null and %s != null", WRAPPER, Q_WRAPPER_SQL_SELECT),
             SqlScriptUtils.unSafeParam(Q_WRAPPER_SQL_SELECT), selectColumns);
     }
    
  6. AbstractMethod#sqlWhereEntityWrapper() 的处理稍显复杂,不过原理和以上方法是一样的,此处可以获得如下脚本片段

       <if test="ew != null">
        <where>
            <if test="ew.entity != null">
                <if test="ew.entity.id != null">id=#{ew.entity.id}</if>
                <if test="ew.entity['name'] != null">AND name=#{ew.entity.name}</if>
                <if test="ew.entity['type'] != null">AND type=#{ew.entity.type}</if>
            </if>
            <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfWhere">
                <if test="ew.nonEmptyOfEntity and ew.nonEmptyOfNormal">AND</if>
                ${ew.sqlSegment}
            </if>
        </where>
        <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.emptyOfWhere">
            ${ew.sqlSegment}
        </if>
    </if>
    
    protected String sqlWhereEntityWrapper(boolean newLine, TableInfo table) {
         if (table.isLogicDelete()) {
             String sqlScript = table.getAllSqlWhere(true, true, WRAPPER_ENTITY_DOT);
             sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", WRAPPER_ENTITY),
                 true);
             sqlScript += (NEWLINE + table.getLogicDeleteSql(true, true) + NEWLINE);
             String normalSqlScript = SqlScriptUtils.convertIf(String.format("AND ${%s}", WRAPPER_SQLSEGMENT),
                 String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT,
                     WRAPPER_NONEMPTYOFNORMAL), true);
             normalSqlScript += NEWLINE;
             normalSqlScript += SqlScriptUtils.convertIf(String.format(" ${%s}", WRAPPER_SQLSEGMENT),
                 String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT,
                     WRAPPER_EMPTYOFNORMAL), true);
             sqlScript += normalSqlScript;
             sqlScript = SqlScriptUtils.convertChoose(String.format("%s != null", WRAPPER), sqlScript,
                 table.getLogicDeleteSql(false, true));
             sqlScript = SqlScriptUtils.convertWhere(sqlScript);
             return newLine ? NEWLINE + sqlScript : sqlScript;
         } else {
             String sqlScript = table.getAllSqlWhere(false, true, WRAPPER_ENTITY_DOT);
             sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", WRAPPER_ENTITY), true);
             sqlScript += NEWLINE;
             sqlScript += SqlScriptUtils.convertIf(String.format(SqlScriptUtils.convertIf(" AND", String.format("%s and %s", WRAPPER_NONEMPTYOFENTITY, WRAPPER_NONEMPTYOFNORMAL), false) + " ${%s}", WRAPPER_SQLSEGMENT),
                 String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT,
                     WRAPPER_NONEMPTYOFWHERE), true);
             sqlScript = SqlScriptUtils.convertWhere(sqlScript) + NEWLINE;
             sqlScript += SqlScriptUtils.convertIf(String.format(" ${%s}", WRAPPER_SQLSEGMENT),
                 String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT,
                     WRAPPER_EMPTYOFWHERE), true);
             sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", WRAPPER), true);
             return newLine ? NEWLINE + sqlScript : sqlScript;
         }
     }
    
  7. AbstractMethod#sqlComment() 拿到的脚本片段如下,不做更多解释

    <choose>
         <when test="ew != null and ew.sqlComment != null">
             ${ew.sqlComment}
         </when>
         <otherwise></otherwise>
     </choose>
    
     protected String sqlComment() {
         return SqlScriptUtils.convertChoose(String.format("%s != null and %s != null", WRAPPER, Q_WRAPPER_SQL_COMMENT),
             SqlScriptUtils.unSafeParam(Q_WRAPPER_SQL_COMMENT), EMPTY);
     }
    

经过以上步骤,SQL 语句脚本各个关键的片段都已经构建完毕,最终得到的脚本如下所示。接下来的处理就是解析这个脚本,通过 LanguageDriver#createSqlSource() 方法将脚本转化为 SqlSource 对象,这个对象是决定最终执行的 SQL 语句的重中之重

<script>
    <choose>
        <when test="ew != null and ew.sqlFirst != null">
            ${ew.sqlFirst}
        </when>
        <otherwise></otherwise>
    </choose>
    SELECT
    <choose>
        <when test="ew != null and ew.sqlSelect != null">
            ${ew.sqlSelect}
        </when>
        <otherwise>id,name,type</otherwise>
    </choose>
    FROM node
    <if test="ew != null">
        <where>
            <if test="ew.entity != null">
                <if test="ew.entity.id != null">id=#{ew.entity.id}</if>
                <if test="ew.entity['name'] != null">AND name=#{ew.entity.name}</if>
                <if test="ew.entity['type'] != null">AND type=#{ew.entity.type}</if>
            </if>
            <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfWhere">
                <if test="ew.nonEmptyOfEntity and ew.nonEmptyOfNormal">AND</if>
                ${ew.sqlSegment}
            </if>
        </where>
        <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.emptyOfWhere">
            ${ew.sqlSegment}
        </if>
    </if>
    <choose>
        <when test="ew != null and ew.sqlComment != null">
            ${ew.sqlComment}
        </when>
        <otherwise></otherwise>
    </choose>
</script>

1.2 SqlSource 的转化

  1. LanguageDriver#createSqlSource() 是接口方法,MyBatis 使用 XML 来定义 SQL 语句配置的,实际调用到 XMLLanguageDriver#createSqlSource(),可以看到这里最终调用 XMLScriptBuilder#parseScriptNode() 开始解析 XML 脚本

     public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
     // issue #3
     if (script.startsWith("<script>")) {
       XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
       return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
     } else {
       // issue #127
       script = PropertyParser.parse(script, configuration.getVariables());
       TextSqlNode textSqlNode = new TextSqlNode(script);
       if (textSqlNode.isDynamic()) {
         return new DynamicSqlSource(configuration, textSqlNode);
       } else {
         return new RawSqlSource(configuration, script, parameterType);
       }
     }
    }
    
    @Override
    public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
     XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
     return builder.parseScriptNode();
    }
    
  2. XMLScriptBuilder#parseScriptNode() 方法如下,可以看到核心是调用 XMLScriptBuilder#parseDynamicTags() 解析 XML 脚本中的各个节点,最终返回一个 SqlSource 对象

    此处注意,解析脚本时会判断 TextSqlNode 保存的文本内容,只要含有字符串拼接符 ${} 就需要进行动态拼接处理,因此返回一个 DynamicSqlSource 对象

    public SqlSource parseScriptNode() {
     MixedSqlNode rootSqlNode = parseDynamicTags(context);
     SqlSource sqlSource;
     if (isDynamic) {
       sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
     } else {
       sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
     }
     return sqlSource;
    }
    
    protected MixedSqlNode parseDynamicTags(XNode node) {
     List<SqlNode> contents = new ArrayList<>();
     NodeList children = node.getNode().getChildNodes();
     for (int i = 0; i < children.getLength(); i++) {
       XNode child = node.newXNode(children.item(i));
       if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
         String data = child.getStringBody("");
         TextSqlNode textSqlNode = new TextSqlNode(data);
         if (textSqlNode.isDynamic()) {
           contents.add(textSqlNode);
           isDynamic = true;
         } else {
           contents.add(new StaticTextSqlNode(data));
         }
       } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
         String nodeName = child.getNode().getNodeName();
         NodeHandler handler = nodeHandlerMap.get(nodeName);
         if (handler == null) {
           throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
         }
         handler.handleNode(child, contents);
         isDynamic = true;
       }
     }
     return new MixedSqlNode(contents);
    }
    
  3. 经过以上步骤,构建的DynamicSqlSource对象与 SQL 语句脚本的对应关系如下所示,后续就是将这个 SqlSource 封装到 MappedStatement 中的处理,也就是调用 AbstractMethod#addSelectMappedStatementForTable() 方法
    在这里插入图片描述

  4. AbstractMethod#addSelectMappedStatementForTable() 方法其实只是入口,这里是通过一个个方法调用逐步组装适配 MyBatis API 的数据对象,最终通过 Mybatis 内部的 MapperBuilderAssistant#addMappedStatement() 方法构建封装了关键 SQL 信息的 MappedStatement 对象,并将其添加到 Configuration 中缓存起来

     protected MappedStatement addSelectMappedStatementForTable(Class<?> mapperClass, String id, SqlSource sqlSource,
                                                                TableInfo table) {
         String resultMap = table.getResultMap();
         if (null != resultMap) {
             /* 返回 resultMap 映射结果集 */
             return addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.SELECT, null,
                 resultMap, null, new NoKeyGenerator(), null, null);
         } else {
             /* 普通查询 */
             return addSelectMappedStatementForOther(mapperClass, id, sqlSource, table.getEntityType());
         }
     }
    
     protected MappedStatement addSelectMappedStatementForOther(Class<?> mapperClass, String id, SqlSource sqlSource,
                                                                Class<?> resultType) {
         return addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.SELECT, null,
             null, resultType, new NoKeyGenerator(), null, null);
     }
    
     protected MappedStatement addMappedStatement(Class<?> mapperClass, String id, SqlSource sqlSource,
                                                  SqlCommandType sqlCommandType, Class<?> parameterType,
                                                  String resultMap, Class<?> resultType, KeyGenerator keyGenerator,
                                                  String keyProperty, String keyColumn) {
         String statementName = mapperClass.getName() + DOT + id;
         if (hasMappedStatement(statementName)) {
             logger.warn(LEFT_SQ_BRACKET + statementName + "] Has been loaded by XML or SqlProvider or Mybatis's Annotation, so ignoring this injection for [" + getClass() + RIGHT_SQ_BRACKET);
             return null;
         }
         /* 缓存逻辑处理 */
         boolean isSelect = false;
         if (sqlCommandType == SqlCommandType.SELECT) {
             isSelect = true;
         }
         return builderAssistant.addMappedStatement(id, sqlSource, StatementType.PREPARED, sqlCommandType,
             null, null, null, parameterType, resultMap, resultType,
             null, !isSelect, isSelect, false, keyGenerator, keyProperty, keyColumn,
             configuration.getDatabaseId(), languageDriver, null);
     }
    

2. MyBatis 对 SqlSource 的使用

  1. MyBatis 缓存实现源码分析 时序图中,笔者详细描绘了上层 Mapper 方法触发到底层数据库操作的流程,可以看到在缓存执行器执行 CachingExecutor#query() 方法时会将 SQL 语句需要的参数包装为 ParamMap 对象传入,并调用 MappedStatement#getBoundSql() 进行对应 SQL 语句的构建

     @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
     BoundSql boundSql = ms.getBoundSql(parameterObject);
     CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
     return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
    
  2. MappedStatement#getBoundSql() 源码如下,参考上一节的内容可以知道此处的核心是调用 DynamicSqlSource#getBoundSql() 方法创建封装了 SQL 执行语句全部所需信息的 BoundSql 对象

     public BoundSql getBoundSql(Object parameterObject) {
     BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
     List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
     if (parameterMappings == null || parameterMappings.isEmpty()) {
       boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
     }
    
     // check for nested result maps in parameter mappings (issue #30)
     for (ParameterMapping pm : boundSql.getParameterMappings()) {
       String rmId = pm.getResultMapId();
       if (rmId != null) {
         ResultMap rm = configuration.getResultMap(rmId);
         if (rm != null) {
           hasNestedResultMaps |= rm.hasNestedResultMaps();
         }
       }
     }
    
     return boundSql;
    }
    
  3. DynamicSqlSource#getBoundSql() 方法的处理主要分为了以下几步:

    1. rootSqlNode.apply() 调用 MixedSqlNode#apply() 方法进入每个 SQL 节点的处理,这个过程会将传入参数值送入 SQL 节点中进行判断,从而决定那个节点最终的 sql 片段,最后将其拼接起来缓存到 DynamicContext 内部
    2. SqlSourceBuilder#parse() 方法处理 SQL 语句中的 #{} 占位符,将其替换为 PreparedStatement 需要的入参形式 “?”
    public BoundSql getBoundSql(Object parameterObject) {
     DynamicContext context = new DynamicContext(configuration, parameterObject);
     rootSqlNode.apply(context);
     SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
     Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
     SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
     BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
     context.getBindings().forEach(boundSql::setAdditionalParameter);
     return boundSql;
    }
    
  4. MixedSqlNode#apply() 方法如下,可以看到就是遍历其内部保存的实际节点,调用其 SqlNode#apply() 方法,此处可以对照上文解析 SQL 语句脚本的示意图理解,本文以 IfSqlNode#apply() 方法为例

    @Override
    public boolean apply(DynamicContext context) {
     contents.forEach(node -> node.apply(context));
     return true;
    }
    
  5. IfSqlNode#apply() 方法的处理比较简练,可以看到重要步骤:

    1. 使用内部的评估器 ExpressionEvaluator 对 Mapper 方法调用时的入参进行检验,此处实际用到了 OGNL表达式 相关 API,有兴趣的读者可以自行了解,本文不再深入
    2. 如果表达式成立,则继续处理当前节点包含的其他节点,此处以处理 ${} 字符串拼接符的TextSqlNode#apply() 为例
     @Override
    public boolean apply(DynamicContext context) {
     if (evaluator.evaluateBoolean(test, context.getBindings())) {
       contents.apply(context);
       return true;
     }
     return false;
    }
    
  6. TextSqlNode#apply() 的逻辑如下,可以看到进行的关键操作如下,有关字符串拼接符的解析及替换本文暂不展开,有兴趣的读者可从此处深入

    1. 调用 TextSqlNode#createParser() 创建解析器,用以解析当前节点中保存的字符串拼接符 ${ew.sqlSegment}
    2. 调用 GenericTokenParser#parse() 方法通过 OGNL 表达式 从参数值中取得对应字符串,例如拼接字符串${ew.sqlSegment}会通过反射调用到 MyBatis-plus 中的 AbstractWrapper#getSqlSegment() 方法完成 SQL 条件语句的构建,取到拼接字符串的替换值后通过 DynamicContext#appendSql() 方法将其追加到 SQL 字符串尾部
     @Override
    public boolean apply(DynamicContext context) {
     GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
     context.appendSql(parser.parse(text));
     return true;
    }
    
    private GenericTokenParser createParser(TokenHandler handler) {
     return new GenericTokenParser("${", "}", handler);
    }
    
  7. 经过以上步骤,SQL 语句脚本已经被处理为了如下的字符串,脚本中之前预存的 MyBatis-plus 的替换字符串 ${ew.xxx} 已经被替换掉,同时 WHERE 条件中已经填上了 MyBatis-plus 中 Mapper 方法入参的 QueryWrapper 构建的 sql 片段,可以看到 SQL 语句 WHERE 条件的实际参数位置已经被 #{ew.paramNameValuePairs.xxx} 占位符 占住

    /*bind_db*/SELECT  id,name,type  FROM node 
     WHERE (edge_id = #{ew.paramNameValuePairs.MPGENVAL1} AND status = #{ew.paramNameValuePairs.MPGENVAL2} AND deleted = #{ew.paramNameValuePairs.MPGENVAL3}) 
    
  8. 回到本节步骤3第二个处理阶段, SqlSourceBuilder#parse() 方法会返回一个新的 StaticSqlSource 对象,并将 SQL 语句中的 #{} 占位符内部的字符串处理为获取参数值的参数映射对象 ParameterMapping,至此 SQL 语句已经更新为如下形式:

    /* bind_db */SELECT id,name,type FROM node WHERE (edge_id = ? AND status = ? AND deleted = ?)

      public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
     ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
     GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
     String sql;
     if (configuration.isShrinkWhitespacesInSql()) {
       sql = parser.parse(removeExtraWhitespaces(originalSql));
     } else {
       sql = parser.parse(originalSql);
     }
     return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
    }
    
  9. StaticSqlSource#getBoundSql() 方法其实就是新建一个 BoundSql 对象返回,可以看到这个对象中已经包含了预处理的 sql 语句,实际参数值的缓存对象 ParamMap,以及取得参数值的映射路径 ParameterMapping 列表,通过参数值的映射路径即可有方法取得对应参数值
    在这里插入图片描述

     @Override
    public BoundSql getBoundSql(Object parameterObject) {
     return new BoundSql(configuration, sql, parameterMappings, parameterObject);
    }
    
  10. 实际对预处理的 sql 语句对应的 PreparedStatement 的参数填充在参数处理器 ParameterHandler#setParameters() 子类实现中完成,这个处理由 StatementHandler#parameterize()触发,最终调用到 DefaultParameterHandler#setParameters() 方法

    可以看到此处的处理是从 ParameterMapping 获取参数值的取值路径,然后使用这个路径借助 MetaObject 从参数缓存对象 parameterObject 中取得参数值,最后调用 TypeHandler#setParameter() 将参数值注入到 PreparedStatement 对象中

    public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
    }
    
  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值