MyBatis 解析CRUD语句 - litter-fish/ReadSource GitHub Wiki

MyBatis 解析CRUD语句解析时序图

解析CRUD语句

解析映射插入、查询、更新、删除语句

// org/apache/ibatis/builder/xml/XMLMapperBuilder.java
private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
        // 获取databaseId后统一解析
        buildStatementFromContext(list, configuration.getDatabaseId());
    }
    // 获取
    buildStatementFromContext(list, null);
}

循环遍历mapper文件下的所有插入、查询、更新、删除节点

// --XMLMapperBuilder
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    // 循环遍历mapper文件下的所有插入、查询、更新、删除节点
    for (XNode context : list) {
        // 构建解析器XMLStatementBuilder
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
        try {
            // 委托XMLStatementBuilder进行解析
            statementParser.parseStatementNode();
        } catch (IncompleteElementException e) {
            // 增加到incompleteStatements集合进行二次解析
            configuration.addIncompleteStatement(statementParser);
        }
    }
}

解析映射插入、查询、更新、删除语句

// --XMLStatementBuilder
public void parseStatementNode() {
    // 	在命名空间中唯一的标识符,可以被用来引用这条语句。
    String id = context.getStringAttribute("id");
    // 数据库厂商标识(databaseIdProvider)
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
        return;
    }

    // 每次批量返回的结果行数
    Integer fetchSize = context.getIntAttribute("fetchSize");
    // 超时时间
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    // 将会传入这条语句的参数类的完全限定名或别名
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);

    // 外部 resultMap 的命名引用
    String resultMap = context.getStringAttribute("resultMap");
    // 从这条语句中返回的期望类型的类的完全限定名或别名
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class<?> resultTypeClass = resolveClass(resultType);
    // 	FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖驱动)。
    String resultSetType = context.getStringAttribute("resultSetType");

    // STATEMENT,PREPARED 或 CALLABLE 中的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    String nodeName = context.getNode().getNodeName();
    // 解析SQL命令类型,目前主要有UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    // insert/delete/update后是否刷新缓存
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);

    // select是否使用缓存
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);

    // 这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。
    // 这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。 TODO ?????
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    // 解析语句中包含的sql片段,也就是
    // <select id="select" resultType="map">
    //  select
    //      field1, field2, field3
    //      <include refid="someinclude"></include>
    // </select>
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    // selectKey 元素的解析
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

    // 列出语句执行后返回的结果集并给每个结果集一个名称,名称是逗号分隔的。
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
        keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
        // (仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键
        // (比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。
        keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
                configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
                ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }
    // 构造MappedStatement对象,并添加MappedStatement实例到configuration对象中
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

解析语句中包含的sql片段

// --XMLIncludeTransformer
/**
 * <select id="select" resultType="map">
 *    select
 *     field1, field2, field3
 *     <include refid="someinclude"></include>
 *  </select>
 */
private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
    if (source.getNodeName().equals("include")) { // include
        /**
         * toInclude: <sql> from table </sql>
         */
        Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
        Properties toIncludeContext = getVariablesContext(source, variablesContext);
        // 解析sql 的子节点,并替换${xxx}
        applyIncludes(toInclude, toIncludeContext, true);
        if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
            toInclude = source.getOwnerDocument().importNode(toInclude, true);
        }
        // 替换select 节点下面的 include 节点为 sql节点
        source.getParentNode().replaceChild(toInclude, source);
        while (toInclude.hasChildNodes()) {
            // sql节点前插入替换好变量的sql
            toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
        }
        // 删除sql节点
        /**
         * 最终数据:select field1, field2, field3/r/n from table /r/n
         */
        toInclude.getParentNode().removeChild(toInclude);
    } else if (source.getNodeType() == Node.ELEMENT_NODE) { // 非include
        if (included && !variablesContext.isEmpty()) {
            // replace variables in attribute values
            NamedNodeMap attributes = source.getAttributes();
            for (int i = 0; i < attributes.getLength(); i++) {
                Node attr = attributes.item(i);
                attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));
            }
        }
        /**
         * 获取select节点的子节点
         *    select field1, field2, field3/r/n
         *    <include refid="someinclude"></include>
         *    /r/n
         *
         */
        NodeList children = source.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            applyIncludes(children.item(i), variablesContext, included);
        }
    } else if (included && source.getNodeType() == Node.TEXT_NODE // 文本节点
            && !variablesContext.isEmpty()) {
        // replace variables in text node
        // 替换sql 节点下的变量
        source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
    }
}

通过refid查找sql节点

// org/apache/ibatis/builder/xml/XMLIncludeTransformer.java
// 查找sql节点
private Node findSqlFragment(String refid, Properties variables) {
    // 获取refid去掉${和}
    refid = PropertyParser.parse(refid, variables);
    refid = builderAssistant.applyCurrentNamespace(refid, true);
    try {
        XNode nodeToInclude = configuration.getSqlFragments().get(refid);
        return nodeToInclude.getNode().cloneNode(true);
    } catch (IllegalArgumentException e) {
        throw new IncompleteElementException("Could not find SQL statement to include with refid '" + refid + "'", e);
    }
}

selectKey 元素的解析

配置示例:

<selectKey keyProperty="id" resultType="int" order="BEFORE">
    select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
</selectKey>

selectKey元素的属性描述

属性 描述
keyProperty selectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
keyColumn 匹配属性的返回结果集中的列名称。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表
resultType 结果的类型
order 这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先生成主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。
statementType MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 语句的映射类型,分别代表 PreparedStatement 和 CallableStatement 类型。

解析:

// org/apache/ibatis/builder/xml/XMLStatementBuilder.java
private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
    List<XNode> selectKeyNodes = context.evalNodes("selectKey");
    if (configuration.getDatabaseId() != null) {
        // 解析selectKey节点
        parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
    }
    parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
    // 在原来语句中移除selectKey节点
    removeSelectKeyNodes(selectKeyNodes);
}

解析selectKey全部节点

private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass,
        LanguageDriver langDriver, String skRequiredDatabaseId) {
    for (XNode nodeToHandle : list) {
        String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
        String databaseId = nodeToHandle.getStringAttribute("databaseId");
        // databaseId是否匹配,上面一步parseSelectKeyNodes两次调用而不会出现重复在此做判断
        if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
            parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
        }
    }
}

解析selectKey某个节点

// org/apache/ibatis/builder/xml/XMLStatementBuilder.java
private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
    // 结果的类型。
    String resultType = nodeToHandle.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    // MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 语句的映射类型,分别代表 PreparedStatement 和 CallableStatement 类型。
    StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));

    // selectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
    String keyProperty = nodeToHandle.getStringAttribute("keyProperty");

    // 匹配属性的返回结果集中的列名称。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
    String keyColumn = nodeToHandle.getStringAttribute("keyColumn");

    // 这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先生成主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句
    boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));

    //defaults
    boolean useCache = false;
    boolean resultOrdered = false;
    KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
    Integer fetchSize = null;
    Integer timeout = null;
    boolean flushCache = false;
    String parameterMap = null;
    String resultMap = null;
    ResultSetType resultSetTypeEnum = null;

    // 使用语言驱动器创建SqlSource
    SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
    // 直接这种sql类型为select
    SqlCommandType sqlCommandType = SqlCommandType.SELECT;

    // MappedStatement 构建
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);

    id = builderAssistant.applyCurrentNamespace(id, false);

    MappedStatement keyStatement = configuration.getMappedStatement(id, false);
    // 为SelectKey对应的sql语句创建并维护一个KeyGenerator
    configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
}

最终解析selectKey节点的结果是在configuration对象中的keyGenerators集合中增加一个SelectKeyGenerator对象

创建SqlSource

使用语言驱动器XMLLanguageDriver解析SQL主体

默认使用:XMLLanguageDriver

// --XMLLanguageDriver
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    // 委托XMLScriptBuilder对象进行解析
    return builder.parseScriptNode();
}

硬编码添加动态元素对应的处理器

// --XMLScriptBuilder
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
    super(configuration);
    this.context = context;
    this.parameterType = parameterType;
    initNodeHandlerMap();
}

// --XMLScriptBuilder
private void initNodeHandlerMap() {
    nodeHandlerMap.put("trim", new TrimHandler());
    nodeHandlerMap.put("where", new WhereHandler());
    nodeHandlerMap.put("set", new SetHandler());
    nodeHandlerMap.put("foreach", new ForEachHandler());
    nodeHandlerMap.put("if", new IfHandler());
    nodeHandlerMap.put("choose", new ChooseHandler());
    nodeHandlerMap.put("when", new IfHandler());
    nodeHandlerMap.put("otherwise", new OtherwiseHandler());
    nodeHandlerMap.put("bind", new BindHandler());
}

解析标签

// --XMLScriptBuilder
public SqlSource parseScriptNode() {
    // 解析动态标签
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource = null;
    if (isDynamic) {
        sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
        sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
}

判断是否是动态的语句

isDynamic解析时序图:

最后一步的handleToken其实调用的是子类DynamicCheckerTokenParser中的实现,即设置isDynamic标识为true

// org/apache/ibatis/scripting/xmltags/TextSqlNode.java
public boolean isDynamic() {
    // 创建动态标识解析器
    DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
    // 创建占位符解析器
    GenericTokenParser parser = createParser(checker);
    // 解析字符串
    parser.parse(text);
    return checker.isDynamic();
}

private GenericTokenParser createParser(TokenHandler handler) {
    // 创建占位符解析器,GenericTokenParser 是一个通用解析器,并非只能解析 ${}
    return new GenericTokenParser("${", "}", handler);
}

private static class DynamicCheckerTokenParser implements TokenHandler {
    // 省略其他
    @Override
    public String handleToken(String content) {
        // 如果存在${
        this.isDynamic = true;
        return null;
    }
}

动态标签解析

// --XMLScriptBuilder
protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<SqlNode>();
    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);
}

动态节点类型

节点类型解析器 if标签的解析

// --XMLScriptBuilder
private class IfHandler implements NodeHandler {
  public IfHandler() {
    // Prevent Synthetic Access
  }

  @Override
  public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
    // 获取if属性的值,将值设置为IfSqlNode的属性,便于运行时解析
    String test = nodeToHandle.getStringAttribute("test");
    IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
    targetContents.add(ifSqlNode);
  }
}

trim标签的解析

/**
 * select * from user
 * <trim prefix="WHERE" prefixoverride="AND |OR">
 *     <if test="name != null and name.length()>0"> AND name=#{name}</if>
 *     <if test="gender != null and gender.length()>0"> AND gender=#{gender}</if>
 * </trim>
 * update user
 * <trim prefix="set" suffixoverride="," suffix=" where id = #{id} ">
 *     <if test="name != null and name.length()>0"> name=#{name} , </if>
 *     <if test="gender != null and gender.length()>0"> gender=#{gender} ,  </if>
 * </trim>
 */
private class TrimHandler implements NodeHandler {
  public TrimHandler() {
    // Prevent Synthetic Access
  }

  @Override
  public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
    // 包含的子节点解析后SQL文本不为空时要添加的前缀内容
    String prefix = nodeToHandle.getStringAttribute("prefix");
    // 要覆盖的子节点解析后SQL文本前缀内容
    String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
    // 包含的子节点解析后SQL文本不为空时要添加的后缀内容
    String suffix = nodeToHandle.getStringAttribute("suffix");
    // 要覆盖的子节点解析后SQL文本后缀内容
    String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
    TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
    targetContents.add(trim);
  }
}

choose标签解析,子节点when使用IfHandler处理器进行解析

/**
 * <choose>
 *   <when test="title != null">
 *     AND title like #{title}
 *   </when>
 *   <when test="author != null and author.name != null">
 *     AND author_name like #{author.name}
 *   </when>
 *   <otherwise>
 *     AND featured = 1
 *   </otherwise>
 * </choose>
 */
private class ChooseHandler implements NodeHandler {
  public ChooseHandler() {
    // Prevent Synthetic Access
  }

  @Override
  public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    List<SqlNode> whenSqlNodes = new ArrayList<SqlNode>();
    List<SqlNode> otherwiseSqlNodes = new ArrayList<SqlNode>();

    // 拆分出when 和 otherwise 节点
    handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
    SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
    ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);
    targetContents.add(chooseSqlNode);
  }

  private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes, List<SqlNode> defaultSqlNodes) {
    List<XNode> children = chooseSqlNode.getChildren();
    for (XNode child : children) {
      String nodeName = child.getNode().getNodeName();
      NodeHandler handler = nodeHandlerMap.get(nodeName);
      if (handler instanceof IfHandler) {
        handler.handleNode(child, ifSqlNodes);
      } else if (handler instanceof OtherwiseHandler) {
        handler.handleNode(child, defaultSqlNodes);
      }
    }
  }

  private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
    SqlNode defaultSqlNode = null;
    if (defaultSqlNodes.size() == 1) {
      defaultSqlNode = defaultSqlNodes.get(0);
    } else if (defaultSqlNodes.size() > 1) {
      throw new BuilderException("Too many default (otherwise) elements in choose statement.");
    }
    return defaultSqlNode;
  }
}

foreach标签解析

/**
 * <foreach item="item" index="index" collection="list"
 *     open="(" separator="," close=")">
 *       #{item}
 * </foreach>
 */
private class ForEachHandler implements NodeHandler {
  public ForEachHandler() {
    // Prevent Synthetic Access
  }

  @Override
  public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
    String collection = nodeToHandle.getStringAttribute("collection");
    String item = nodeToHandle.getStringAttribute("item");
    String index = nodeToHandle.getStringAttribute("index");
    String open = nodeToHandle.getStringAttribute("open");
    String close = nodeToHandle.getStringAttribute("close");
    String separator = nodeToHandle.getStringAttribute("separator");
    ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator);
    targetContents.add(forEachSqlNode);
  }
}

经过上面的动态标签解析,可以将sql语句拆分成不同 SqlNode 节点,最后将这些 SqlNode 节点集合封装成一个 MixedSqlNode 对象

SqlSource是XML文件或者注解方法中映射语句的实现时表示,通过SqlSourceBuilder.parse()方法创建,SqlSourceBuilder中符号解析器将mybatis中的查询参数#{}转换为?,并记录了参数的顺序。 SqlSouce类型

StaticSqlSource:最终静态SQL语句的封装,其他类型的SqlSource最终都委托给StaticSqlSource。 RawSqlSource:原始静态SQL语句的封装,在加载时就已经确定了SQL语句,没有、等动态标签和${} SQL拼接,比动态SQL语句要快,因为不需要运行时解析SQL节点。 DynamicSqlSource:动态SQL语句的封装,在运行时需要根据参数处理、等标签或者${} SQL拼接之后才能生成最后要执行的静态SQL语句。 ProviderSqlSource:当SQL语句通过指定的类和方法获取时(使用@XXXProvider注解),需要使用本类,它会通过反射调用相应的方法得到SQL语句。

如果sql语句被检测到是一个非动态的语句,则会创建一个RawSqlSource对象,RawSqlSource也是通过DynamicSqlSource创建

首先查询RawSqlSource解析时序图:

同样handleToken方法也是调用TokenHandler子类ParameterMappingTokenHandler的handleToken方法

// org/apache/ibatis/builder/SqlSourceBuilder.ParameterMappingTokenHandler
public String handleToken(String content) {
    // 获取 content 的对应的 ParameterMapping
    parameterMappings.add(buildParameterMapping(content));
    // 返回 ?
    return "?";
}

创建非动态的sql处理对象RawSqlSource

// org/apache/ibatis/scripting/defaults/RawSqlSource.java
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    // 创建SqlSourceBuilder处理器
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    //  #{} 占位符处理器
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
}

委托SqlSourceBuilder处理静态语句

// org/apache/ibatis/builder/SqlSourceBuilder.java
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 = parser.parse(originalSql);
    // 封装解析结果到 StaticSqlSource 中,并返回
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

ParameterMappingTokenHandler的作用:

  1. 将 #{xxx} 占位符中的内容解析成 Map,然后保存在parseParameterMapping集合中
  2. 替换#{xxx}为?占位符

将 #{xxx} 占位符中的内容解析成 Map

// org/apache/ibatis/builder/SqlSourceBuilder.ParameterMappingTokenHandler
private ParameterMapping buildParameterMapping(String content) {

    /*
     * 将 #{xxx} 占位符中的内容解析成 Map。大家可能很好奇一个普通的字符串是怎么解析成 Map 的,
     * 举例说明一下。如下:
     *
     *    #{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
     *
     * 上面占位符中的内容最终会被解析成如下的结果:
     *
     *  {
     *      "property": "age",
     *      "typeHandler": "MyTypeHandler",
     *      "jdbcType": "NUMERIC",
     *      "javaType": "int"
     *  }
     *
     * parseParameterMapping 内部依赖 ParameterExpression 对字符串进行解析,ParameterExpression 的
     * 逻辑不是很复杂,这里就不分析了。大家若有兴趣,可自行分析
     */
    Map<String, String> propertiesMap = parseParameterMapping(content);
    String property = propertiesMap.get("property");
    Class<?> propertyType;

    // metaParameters 为 DynamicContext 成员变量 bindings 的元信息对象
    if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
        propertyType = metaParameters.getGetterType(property);

        /*
         * parameterType 是运行时参数的类型。如果用户传入的是单个参数,比如 Article 对象,此时
         * parameterType 为 Article.class。如果用户传入的多个参数,比如 [id = 1, author = "coolblog"],
         * MyBatis 会使用 ParamMap 封装这些参数,此时 parameterType 为 ParamMap.class。如果
         * parameterType 有相应的 TypeHandler,这里则把 parameterType 设为 propertyType
         */
    } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
        propertyType = parameterType;
    } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
        propertyType = java.sql.ResultSet.class;
    } else if (property == null || Map.class.isAssignableFrom(parameterType)) {
        // 如果 property 为空,或 parameterType 是 Map 类型,则将 propertyType 设为 Object.class
        propertyType = Object.class;
    } else {

        /*
         * 代码逻辑走到此分支中,表明 parameterType 是一个自定义的类,
         * 比如 Article,此时为该类创建一个元信息对象
         */
        MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
        // 检测参数对象有没有与 property 想对应的 getter 方法
        if (metaClass.hasGetter(property)) {
            // 获取成员变量的类型
            propertyType = metaClass.getGetterType(property);
        } else {
            propertyType = Object.class;
        }
    }


    // 将 propertyType 赋值给 javaType
    ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
    Class<?> javaType = propertyType;
    String typeHandlerAlias = null;

    // 遍历 propertiesMap
    for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
        String name = entry.getKey();
        String value = entry.getValue();
        if ("javaType".equals(name)) {
            // 如果用户明确配置了 javaType,则以用户的配置为准
            javaType = resolveClass(value);
            builder.javaType(javaType);
        } else if ("jdbcType".equals(name)) {
            // 解析 jdbcType
            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 " + parameterProperties);
        }
    }
    if (typeHandlerAlias != null) {
        // 解析 TypeHandler
        builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
    }
    // 构建 ParameterMapping 对象
    return builder.build();
}

构造MappedStatement对象,并添加MappedStatement实例到configuration对象中,每个select|insert|update|delete语句都会创建一个

public MappedStatement addMappedStatement(
    String id,
    SqlSource sqlSource,
    StatementType statementType,
    SqlCommandType sqlCommandType,
    Integer fetchSize,
    Integer timeout,
    String parameterMap,
    Class<?> parameterType,
    String resultMap,
    Class<?> resultType,
    ResultSetType resultSetType,
    boolean flushCache,
    boolean useCache,
    boolean resultOrdered,
    KeyGenerator keyGenerator,
    String keyProperty,
    String keyColumn,
    String databaseId,
    LanguageDriver lang,
    String resultSets) {

  if (unresolvedCacheRef) {
    throw new IncompleteElementException("Cache-ref not yet resolved");
  }

  id = applyCurrentNamespace(id, false);
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  // 创建 MappedStatement 对象,并设置一些属性
  MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
      .resource(resource)
      .fetchSize(fetchSize)
      .timeout(timeout)
      .statementType(statementType)
      .keyGenerator(keyGenerator)
      .keyProperty(keyProperty)
      .keyColumn(keyColumn)
      .databaseId(databaseId)
      .lang(lang)
      .resultOrdered(resultOrdered)
      .resultSets(resultSets)
      .resultMaps(getStatementResultMaps(resultMap, resultType, id))
      .resultSetType(resultSetType)
      .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
      .useCache(valueOrDefault(useCache, isSelect))
      .cache(currentCache);

  // 创建语句参数映射
  ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
  if (statementParameterMap != null) {
    statementBuilder.parameterMap(statementParameterMap);
  }

  MappedStatement statement = statementBuilder.build();
  configuration.addMappedStatement(statement);
  return statement;
}

至此crud解析结束

最终解析mapper文件解析完成后的MappedStatement列表

具体的MappedStatement对象

⚠️ **GitHub.com Fallback** ⚠️