mybatis-加载statement配置2

1. 概述

本文接 《精尽 MyBatis 源码分析 —— MyBatis 初始化(二)之加载 Mapper 映射配置文件》 一文,来分享 MyBatis 初始化的第三步,加载 Statement 配置。而这个步骤的入口是 XMLStatementBuilder 。下面,我们一起来看看它的代码实现。

在 《精尽 MyBatis 源码分析 —— MyBatis 初始化(二)之加载 Mapper 映射配置文件》 的 「2.3.5 buildStatementFromContext」 中,我们已经看到对 XMLStatementBuilder 的调用代码。代码如下:

// XMLMapperBuilder.java

private void buildStatementFromContext(List<XNode> list) {    if (configuration.getDatabaseId() != null) {        buildStatementFromContext(list, configuration.getDatabaseId());    }    buildStatementFromContext(list, null);    // 上面两块代码,可以简写成 buildStatementFromContext(list, configuration.getDatabaseId());}

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {    // <1> 遍历 <select /> <insert /> <update /> <delete /> 节点们    for (XNode context : list) {        // <1> 创建 XMLStatementBuilder 对象,执行解析        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);        try {            statementParser.parseStatementNode();        } catch (IncompleteElementException e) {            // <2> 解析失败,添加到 configuration 中            configuration.addIncompleteStatement(statementParser);        }    }}

2. XMLStatementBuilder

org.apache.ibatis.builder.xml.XMLStatementBuilder ,继承 BaseBuilder 抽象类,Statement XML 配置构建器,主要负责解析 Statement 配置,即 <select /><insert /><update /><delete /> 标签。

2.1 构造方法

// XMLStatementBuilder.java

private final MapperBuilderAssistant builderAssistant;/** * 当前 XML 节点,例如:<select />、<insert />、<update />、<delete /> 标签 */private final XNode context;/** * 要求的 databaseId */private final String requiredDatabaseId;

public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {    super(configuration);    this.builderAssistant = builderAssistant;    this.context = context;    this.requiredDatabaseId = databaseId;}

2.2 parseStatementNode

#parseStatementNode() 方法,执行 Statement 解析。代码如下:

// XMLStatementBuilder.java

public void parseStatementNode() {    // <1> 获得 id 属性,编号。    String id = context.getStringAttribute("id");    // <2> 获得 databaseId , 判断 databaseId 是否匹配    String databaseId = context.getStringAttribute("databaseId");    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {        return;    }

    // <3> 获得各种属性    Integer fetchSize = context.getIntAttribute("fetchSize");    Integer timeout = context.getIntAttribute("timeout");    String parameterMap = context.getStringAttribute("parameterMap");    String parameterType = context.getStringAttribute("parameterType");    Class<?> parameterTypeClass = resolveClass(parameterType);    String resultMap = context.getStringAttribute("resultMap");    String resultType = context.getStringAttribute("resultType");    String lang = context.getStringAttribute("lang");

    // <4> 获得 lang 对应的 LanguageDriver 对象    LanguageDriver langDriver = getLanguageDriver(lang);

    // <5> 获得 resultType 对应的类    Class<?> resultTypeClass = resolveClass(resultType);    // <6> 获得 resultSet 对应的枚举值    String resultSetType = context.getStringAttribute("resultSetType");    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);    // <7> 获得 statementType 对应的枚举值    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));

    // <8> 获得 SQL 对应的 SqlCommandType 枚举值    String nodeName = context.getNode().getNodeName();    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));    // <9> 获得各种属性    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);    boolean useCache = context.getBooleanAttribute("useCache", isSelect);    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing    // <10> 创建 XMLIncludeTransformer 对象,并替换 <include /> 标签相关的内容    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.    // <11> 解析 <selectKey /> 标签    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)    // <12> 创建 SqlSource    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);    // <13> 获得 KeyGenerator 对象    String resultSets = context.getStringAttribute("resultSets");    String keyProperty = context.getStringAttribute("keyProperty");    String keyColumn = context.getStringAttribute("keyColumn");    KeyGenerator keyGenerator;    // <13.1> 优先,从 configuration 中获得 KeyGenerator 对象。如果存在,意味着是 <selectKey /> 标签配置的    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);    if (configuration.hasKeyGenerator(keyStatementId)) {        keyGenerator = configuration.getKeyGenerator(keyStatementId);    // <13.2> 其次,根据标签属性的情况,判断是否使用对应的 Jdbc3KeyGenerator 或者 NoKeyGenerator 对象    } else {        keyGenerator = context.getBooleanAttribute("useGeneratedKeys", // 优先,基于 useGeneratedKeys 属性判断                configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) // 其次,基于全局的 useGeneratedKeys 配置 + 是否为插入语句类型                ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;    }

    // 创建 MappedStatement 对象    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,            resultSetTypeEnum, flushCache, useCache, resultOrdered,            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);}
  • <1> 处,获得 id 属性,编号。
  • <2> 处,获得 databaseId 属性,并调用 #databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) 方法,判断 databaseId 是否匹配。详细解析,见 「2.3 databaseIdMatchesCurrent」 。
  • <3> 处,获得各种属性。
  • <4> 处,调用 #getLanguageDriver(String lang) 方法,获得 lang 对应的 LanguageDriver 对象。详细解析,见 「2.4 getLanguageDriver」 。
  • <5> 处,获得 resultType 对应的类。
  • <6> 处,获得 resultSet 对应的枚举值。关于 org.apache.ibatis.mapping.ResultSetType 枚举类,点击查看。一般情况下,不会设置该值。它是基于 java.sql.ResultSet 结果集的几种模式,感兴趣的话,可以看看 《ResultSet 的 Type 属性》 。
  • <7> 处,获得 statementType 对应的枚举值。关于 org.apache.ibatis.mapping.StatementType 枚举类,点击查看
  • <8> 处,获得 SQL 对应的 SqlCommandType 枚举值。
  • <9> 处,获得各种属性。
  • <10> 处,创建 XMLIncludeTransformer 对象,并调用 XMLIncludeTransformer#applyIncludes(Node source) 方法,替换 <include /> 标签相关的内容。详细解析,见 「3. XMLIncludeTransformer」 。
  • <11> 处,调用 #processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) 方法,解析 <selectKey /> 标签。详细解析,见 「2.5 processSelectKeyNodes」 。
  • <12> 处,调用 LanguageDriver#createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) 方法,创建 SqlSource 对象。详细解析,见后续文章。
  • <13> 处,获得 KeyGenerator 对象。分成 <13.1> 和 <13.2> 两种情况。具体的,胖友耐心看下代码注释。
  • <14> 处,调用 MapperBuilderAssistant#addMappedStatement(...) 方法,创建 MappedStatement 对象。详细解析,见 「4.1 addMappedStatement」 中。

2.3 databaseIdMatchesCurrent

#databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) 方法,判断 databaseId 是否匹配。代码如下:

// XMLStatementBuilder.java

private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {    // 如果不匹配,则返回 false    if (requiredDatabaseId != null) {        return requiredDatabaseId.equals(databaseId);    } else {        // 如果未设置 requiredDatabaseId ,但是 databaseId 存在,说明还是不匹配,则返回 false        // mmp ,写的好绕        if (databaseId != null) {            return false;        }        // skip this statement if there is a previous one with a not null databaseId        // 判断是否已经存在        id = builderAssistant.applyCurrentNamespace(id, false);        if (this.configuration.hasStatement(id, false)) {            MappedStatement previous = this.configuration.getMappedStatement(id, false); // issue #2            // 若存在,则判断原有的 sqlFragment 是否 databaseId 为空。因为,当前 databaseId 为空,这样两者才能匹配。            return previous.getDatabaseId() == null;        }    }    return true;}
  • 代码比较简单,胖友自己瞅瞅就得。从逻辑上,和我们在 XMLMapperBuilder 看到的同名方法 #databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) 方法是一致的。

2.4 getLanguageDriver

#getLanguageDriver(String lang) 方法,获得 lang 对应的 LanguageDriver 对象。代码如下:

// XMLStatementBuilder.java

private LanguageDriver getLanguageDriver(String lang) {    // 解析 lang 对应的类    Class<? extends LanguageDriver> langClass = null;    if (lang != null) {        langClass = resolveClass(lang);    }    // 获得 LanguageDriver 对象    return builderAssistant.getLanguageDriver(langClass);}
  • 调用 MapperBuilderAssistant#getLanguageDriver(lass<? extends LanguageDriver> langClass) 方法,获得 LanguageDriver 对象。代码如下:

    // MapperBuilderAssistant.java
    
    public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) {    // 获得 langClass 类    if (langClass != null) {        configuration.getLanguageRegistry().register(langClass);    } else { // 如果为空,则使用默认类        langClass = configuration.getLanguageRegistry().getDefaultDriverClass();    }    // 获得 LanguageDriver 对象    return configuration.getLanguageRegistry().getDriver(langClass);}
    • 关于 org.apache.ibatis.scripting.LanguageDriverRegistry 类,我们在后续的文章,详细解析。

2.5 processSelectKeyNodes

#processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) 方法,解析 <selectKey /> 标签。代码如下:

// XMLStatementBuilder.java

private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {    // <1> 获得 <selectKey /> 节点们    List<XNode> selectKeyNodes = context.evalNodes("selectKey");    // <2> 执行解析 <selectKey /> 节点们    if (configuration.getDatabaseId() != null) {        parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());    }    parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);    // <3> 移除 <selectKey /> 节点们    removeSelectKeyNodes(selectKeyNodes);}
  • <1> 处,获得 <selectKey /> 节点们。
  • <2> 处,调用 #parseSelectKeyNodes(...) 方法,执行解析 <selectKey /> 节点们。详细解析,见 「2.5.1 parseSelectKeyNodes」 。
  • <3> 处,调用 #removeSelectKeyNodes(List<XNode> selectKeyNodes) 方法,移除 <selectKey /> 节点们。代码如下:
    // XMLStatementBuilder.java
    
    private void removeSelectKeyNodes(List<XNode> selectKeyNodes) {    for (XNode nodeToHandle : selectKeyNodes) {        nodeToHandle.getParent().getNode().removeChild(nodeToHandle.getNode());    }}

2.5.1 parseSelectKeyNodes

#parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) 方法,执行解析 <selectKey /> 子节点们。代码如下:

// XMLStatementBuilder.java

private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {    // <1> 遍历 <selectKey /> 节点们    for (XNode nodeToHandle : list) {        // <2> 获得完整 id ,格式为 `${id}!selectKey`        String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;        // <3> 获得 databaseId , 判断 databaseId 是否匹配        String databaseId = nodeToHandle.getStringAttribute("databaseId");        if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {            // <4> 执行解析单个 <selectKey /> 节点            parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);        }    }}
  • <1> 处,遍历 <selectKey /> 节点们,逐个处理。
  • <2> 处,获得完整 id 编号,格式为 ${id}!selectKey 。这里很重要,最终解析的 <selectKey /> 节点,会创建成一个 MappedStatement 对象。而该对象的编号,就是 id 。
  • <3> 处,获得 databaseId ,并调用 #databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) 方法,判断 databaseId 是否匹配。?? 通过此处,我们可以看到,即使有多个 <selectionKey /> 节点,但是最终只会有一个节点被解析,就是符合的 databaseId 对应的。因为不同的数据库实现不同,对于获取主键的方式也会不同。
  • <4> 处,调用 #parseSelectKeyNode(...) 方法,执行解析单个 <selectKey /> 节点。详细解析,见 「2.5.2 parseSelectKeyNode」 。

2.5.2 parseSelectKeyNode

#parseSelectKeyNode(...) 方法,执行解析单个 <selectKey /> 节点。代码如下:

// XMLStatementBuilder.java

private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {    // <1.1> 获得各种属性和对应的类    String resultType = nodeToHandle.getStringAttribute("resultType");    Class<?> resultTypeClass = resolveClass(resultType);    StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));    String keyProperty = nodeToHandle.getStringAttribute("keyProperty");    String keyColumn = nodeToHandle.getStringAttribute("keyColumn");    boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));

    // defaults    // <1.2> 创建 MappedStatement 需要用到的默认值    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;

    // <1.3> 创建 SqlSource 对象    SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);    SqlCommandType sqlCommandType = SqlCommandType.SELECT;

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

    // <2.1> 获得 SelectKeyGenerator 的编号,格式为 `${namespace}.${id}`    id = builderAssistant.applyCurrentNamespace(id, false);    // <2.2> 获得 MappedStatement 对象    MappedStatement keyStatement = configuration.getMappedStatement(id, false);    // <2.3> 创建 SelectKeyGenerator 对象,并添加到 configuration 中    configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));}
  • <1.1> 处理,获得各种属性和对应的类。
  • <1.2> 处理,创建 MappedStatement 需要用到的默认值。
  • <1.3> 处理,调用 LanguageDriver#createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) 方法,创建 SqlSource 对象。详细解析,见后续文章。
  • <1.4> 处理,调用 MapperBuilderAssistant#addMappedStatement(...) 方法,创建 MappedStatement 对象。
  • <2.1> 处理,获得 SelectKeyGenerator 的编号,格式为 ${namespace}.${id} 。
  • <2.2> 处理,获得 MappedStatement 对象。该对象,实际就是 <1.4> 处创建的 MappedStatement 对象。
  • <2.3> 处理,调用 Configuration#addKeyGenerator(String id, KeyGenerator keyGenerator) 方法,创建 SelectKeyGenerator 对象,并添加到 configuration 中。代码如下:
    // Configuration.java
    
    /** * KeyGenerator 的映射 * * KEY:在 {@link #mappedStatements} 的 KEY 的基础上,跟上 {@link SelectKeyGenerator#SELECT_KEY_SUFFIX} */protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
    
    public void addKeyGenerator(String id, KeyGenerator keyGenerator) {    keyGenerators.put(id, keyGenerator);}

3. XMLIncludeTransformer

org.apache.ibatis.builder.xml.XMLIncludeTransformer ,XML <include /> 标签的转换器,负责将 SQL 中的 <include /> 标签转换成对应的 <sql /> 的内容。

3.1 构造方法

// XMLIncludeTransformer.java

private final Configuration configuration;private final MapperBuilderAssistant builderAssistant;

public XMLIncludeTransformer(Configuration configuration, MapperBuilderAssistant builderAssistant) {    this.configuration = configuration;    this.builderAssistant = builderAssistant;}

3.2 applyIncludes

#applyIncludes(Node source) 方法,将 <include /> 标签,替换成引用的 <sql /> 。代码如下:

// XMLIncludeTransformer.java

public void applyIncludes(Node source) {    // <1> 创建 variablesContext ,并将 configurationVariables 添加到其中    Properties variablesContext = new Properties();    Properties configurationVariables = configuration.getVariables();    if (configurationVariables != null) {        variablesContext.putAll(configurationVariables);    }    // <2> 处理 <include />    applyIncludes(source, variablesContext, false);}
  • <1> 处,创建 variablesContext ,并将 configurationVariables 添加到其中。这里的目的是,避免 configurationVariables 被下面使用时候,可能被修改。实际上,从下面的实现上,不存在这个情况。
  • <2> 处,调用 #applyIncludes(Node source, final Properties variablesContext, boolean included) 方法,处理 <include /> 。


#applyIncludes(Node source, final Properties variablesContext, boolean included) 方法,使用递归的方式,将 <include /> 标签,替换成引用的 <sql /> 。代码如下:

// XMLIncludeTransformer.java

private void applyIncludes(Node source, final Properties variablesContext, boolean included) {    // <1> 如果是 <include /> 标签    if (source.getNodeName().equals("include")) {        // <1.1> 获得 <sql /> 对应的节点        Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);        // <1.2> 获得包含 <include /> 标签内的属性        Properties toIncludeContext = getVariablesContext(source, variablesContext);        // <1.3> 递归调用 #applyIncludes(...) 方法,继续替换。注意,此处是 <sql /> 对应的节点        applyIncludes(toInclude, toIncludeContext, true);        if (toInclude.getOwnerDocument() != source.getOwnerDocument()) { // 这个情况,艿艿暂时没调试出来            toInclude = source.getOwnerDocument().importNode(toInclude, true);        }        // <1.4> 将 <include /> 节点替换成 <sql /> 节点        source.getParentNode().replaceChild(toInclude, source); // 注意,这是一个奇葩的 API ,前者为 newNode ,后者为 oldNode        // <1.4> 将 <sql /> 子节点添加到 <sql /> 节点前面        while (toInclude.hasChildNodes()) {            toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude); // 这里有个点,一定要注意,卡了艿艿很久。当子节点添加到其它节点下面后,这个子节点会不见了,相当于是“移动操作”        }        // <1.4> 移除 <include /> 标签自身        toInclude.getParentNode().removeChild(toInclude);    // <2> 如果节点类型为 Node.ELEMENT_NODE    } else if (source.getNodeType() == Node.ELEMENT_NODE) {        // <2.1> 如果在处理 <include /> 标签中,则替换其上的属性,例如 <sql id="123" lang="${cpu}"> 的情况,lang 属性是可以被替换的        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));            }        }        // <2.2> 遍历子节点,递归调用 #applyIncludes(...) 方法,继续替换        NodeList children = source.getChildNodes();        for (int i = 0; i < children.getLength(); i++) {            applyIncludes(children.item(i), variablesContext, included);        }    // <3> 如果在处理 <include /> 标签中,并且节点类型为 Node.TEXT_NODE ,并且变量非空    // 则进行变量的替换,并修改原节点 source    } else if (included && source.getNodeType() == Node.TEXT_NODE            && !variablesContext.isEmpty()) {        // replace variables in text node        source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));    }}
  • 这是个有自递归逻辑的方法,所以理解起来会有点绕,实际上还是蛮简单的。为了更好的解释,我们假设示例如下:

    // mybatis-config.xml
    
    <properties>    <property name="cpu" value="16c" />    <property name="target_sql" value="123" /></properties>
    
    // Mapper.xml
    
    <sql id="123" lang="${cpu}">    ${cpu}    aoteman    qqqq</sql>
    
    <select id="testForInclude">    SELECT * FROM subject    <include refid="${target_sql}" /></select>
  • 方法参数 included ,是否正在处理 <include /> 标签中。?? 一脸懵逼?不要方,继续往下看。
  • 在上述示例的 <select /> 节点进入这个方法时,会首先进入 <2> 这块逻辑。
    • <2.1> 处,因为 不满足 included 条件,初始传入是 false ,所以跳过。
    • <2.2> 处,遍历子节点,递归调用 #applyIncludes(...) 方法,继续替换。如图所示:
      • 子节点 [0] 和 [2] ,执行该方法时,不满足 <1><2><3> 任一一种情况,所以可以忽略。虽然说,满足 <3> 的节点类型为 Node.TEXT_NODE ,但是 included 此时为 false ,所以不满足。
      • 子节点 [1] ,执行该方法时,满足 <1> 的情况,所以走起。
  • 在子节点 [1] ,即 <include /> 节点进入 <1> 这块逻辑:
    • <1.1> 处,调用 #findSqlFragment(String refid, Properties variables) 方法,获得 <sql /> 对应的节点,即上述示例看到的,<sql id="123" lang="${cpu}"> ... </> 。详细解析,见 「3.3 findSqlFragment」 。
    • <1.2> 处,调用 #getVariablesContext(Node node, Properties inheritedVariablesContext) 方法,获得包含 <include /> 标签内的属性 Properties 对象。详细解析,见 「3.4 getVariablesContext」 。
    • <1.3> 处,递归调用 #applyIncludes(...) 方法,继续替换。注意,此处是 <sql /> 对应的节点,并且 included 参数为 true 。详细的结果,见 ?????? 处。
    • <1.4> 处,将处理好的 <sql /> 节点,替换掉 <include /> 节点。逻辑有丢丢绕,胖友耐心看下注释,好好思考。
  • ?????? 在 <sql /> 节点,会进入 <2> 这块逻辑:
    • <2.1> 处,因为 included 为 true ,所以能满足这块逻辑,会进行执行。如 <sql id="123" lang="${cpu}"> 的情况,lang 属性是可以被替换的。
    • <2.2> 处,遍历子节点,递归调用 #applyIncludes(...) 方法,继续替换。如图所示:
      • 子节点 [0] ,执行该方法时,满足 <3> 的情况,所以可以使用变量 Properteis 对象,进行替换,并修改原节点。

其实,整理一下,逻辑也不会很绕。耐心耐心耐心。

3.3 findSqlFragment

#findSqlFragment(String refid, Properties variables) 方法,获得对应的 <sql /> 节点。代码如下:

// XMLIncludeTransformer.java

private Node findSqlFragment(String refid, Properties variables) {    // 因为 refid 可能是动态变量,所以进行替换    refid = PropertyParser.parse(refid, variables);    // 获得完整的 refid ,格式为 "${namespace}.${refid}"    refid = builderAssistant.applyCurrentNamespace(refid, true);    try {        // 获得对应的 <sql /> 节点        XNode nodeToInclude = configuration.getSqlFragments().get(refid);        // 获得 Node 节点,进行克隆        return nodeToInclude.getNode().cloneNode(true);    } catch (IllegalArgumentException e) {        throw new IncompleteElementException("Could not find SQL statement to include with refid ‘" + refid + "‘", e);    }}

private String getStringAttribute(Node node, String name) {    return node.getAttributes().getNamedItem(name).getNodeValue();}
  • 比较简单,胖友瞅瞅注释。

3.4 getVariablesContext

#getVariablesContext(Node node, Properties inheritedVariablesContext) 方法,获得包含 <include /> 标签内的属性 Properties 对象。代码如下:

// XMLIncludeTransformer.java

private Properties getVariablesContext(Node node, Properties inheritedVariablesContext) {    // 获得 <include /> 标签的属性集合    Map<String, String> declaredProperties = null;    NodeList children = node.getChildNodes();    for (int i = 0; i < children.getLength(); i++) {        Node n = children.item(i);        if (n.getNodeType() == Node.ELEMENT_NODE) {            String name = getStringAttribute(n, "name");            // Replace variables inside            String value = PropertyParser.parse(getStringAttribute(n, "value"), inheritedVariablesContext);            if (declaredProperties == null) {                declaredProperties = new HashMap<>();            }            if (declaredProperties.put(name, value) != null) { // 如果重复定义,抛出异常                throw new BuilderException("Variable " + name + " defined twice in the same include definition");            }        }    }    // 如果 <include /> 标签内没有属性,直接使用 inheritedVariablesContext 即可    if (declaredProperties == null) {        return inheritedVariablesContext;    // 如果 <include /> 标签内有属性,则创建新的 newProperties 集合,将 inheritedVariablesContext + declaredProperties 合并    } else {        Properties newProperties = new Properties();        newProperties.putAll(inheritedVariablesContext);        newProperties.putAll(declaredProperties);        return newProperties;    }}
  • 比较简单,胖友瞅瞅注释。
  • 如下是 <include /> 标签内有属性的示例:
    <sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
    
    <select id="selectUsers" resultType="map">  select    <include refid="userColumns"><property name="alias" value="t1"/></include>,    <include refid="userColumns"><property name="alias" value="t2"/></include>  from some_table t1    cross join some_table t2</select>

4. MapperBuilderAssistant

4.1 addMappedStatement

#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) 方法,构建 MappedStatement 对象。代码如下:

// MapperBuilderAssistant.java

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) {

    // <1> 如果只想的 Cache 未解析,抛出 IncompleteElementException 异常    if (unresolvedCacheRef) {        throw new IncompleteElementException("Cache-ref not yet resolved");    }

    // <2> 获得 id 编号,格式为 `${namespace}.${id}`    id = applyCurrentNamespace(id, false);    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    // <3> 创建 MappedStatement.Builder 对象    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)) // <3.1> 获得 ResultMap 集合            .resultSetType(resultSetType)            .flushCacheRequired(valueOrDefault(flushCache, !isSelect))            .useCache(valueOrDefault(useCache, isSelect))            .cache(currentCache);

    // <3.2> 获得 ParameterMap ,并设置到 MappedStatement.Builder 中    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);    if (statementParameterMap != null) {        statementBuilder.parameterMap(statementParameterMap);    }

    // <4> 创建 MappedStatement 对象    MappedStatement statement = statementBuilder.build();    // <5> 添加到 configuration 中    configuration.addMappedStatement(statement);    return statement;}
  • <1> 处,如果只想的 Cache 未解析,抛出 IncompleteElementException 异常。
  • <2> 处,获得 id 编号,格式为 ${namespace}.${id} 。
  • <3> 处,创建 MappedStatement.Builder 对象。详细解析,见 「4.1.3 MappedStatement」 。
    • <3.1> 处,调用 #getStatementResultMaps(...) 方法,获得 ResultMap 集合。详细解析,见 「4.1.3 getStatementResultMaps」 。
    • <3.2> 处,调用 #getStatementParameterMap(...) 方法,获得 ParameterMap ,并设置到 MappedStatement.Builder 中。详细解析,见 4.1.4 getStatementResultMaps」 。
  • <4> 处,创建 MappedStatement 对象。详细解析,见 「4.1.1 MappedStatement」 。
  • <5> 处,调用 Configuration#addMappedStatement(statement) 方法,添加到 configuration 中。代码如下:
    // Configuration.java
    
    /** * MappedStatement 映射 * * KEY:`${namespace}.${id}` */protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
    
    public void addMappedStatement(MappedStatement ms) {    mappedStatements.put(ms.getId(), ms);}

4.1.1 MappedStatement

org.apache.ibatis.mapping.MappedStatement ,映射的语句,每个 <select /><insert /><update /><delete /> 对应一个 MappedStatement 对象。代码比较简单,但是有点略长,胖友直接点击 链接 查看,已经添加了完整的注释。

另外,比较特殊的是,<selectKey /> 解析后,也会对应一个 MappedStatement 对象。

在另外,MappedStatement 有一个非常重要的方法 #getBoundSql(Object parameterObject) 方法,获得 BoundSql 对象。代码如下:

// MappedStatement.java

public BoundSql getBoundSql(Object parameterObject) {    // 获得 BoundSql 对象    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);    // 忽略,因为 <parameterMap /> 已经废弃,参见 http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html 文档    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)    // 判断传入的参数中,是否有内嵌的结果 ResultMap 。如果有,则修改 hasNestedResultMaps 为 true    // 存储过程相关,暂时无视    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;}

4.1.2 ParameterMap

org.apache.ibatis.mapping.ParameterMap ,参数集合,对应 paramType="" 或 paramMap="" 标签属性。代码比较简单,但是有点略长,胖友直接点击 链接 查看,已经添加了完整的注释。

4.1.3 getStatementResultMaps

#getStatementResultMaps(...) 方法,获得 ResultMap 集合。代码如下:

// MapperBuilderAssistant.java

private List<ResultMap> getStatementResultMaps(        String resultMap,        Class<?> resultType,        String statementId) {    // 获得 resultMap 的编号    resultMap = applyCurrentNamespace(resultMap, true);

    // 创建 ResultMap 集合    List<ResultMap> resultMaps = new ArrayList<>();    // 如果 resultMap 非空,则获得 resultMap 对应的 ResultMap 对象(们)    if (resultMap != null) {        String[] resultMapNames = resultMap.split(",");        for (String resultMapName : resultMapNames) {            try {                resultMaps.add(configuration.getResultMap(resultMapName.trim())); // 从 configuration 中获得            } catch (IllegalArgumentException e) {                throw new IncompleteElementException("Could not find result map " + resultMapName, e);            }        }    // 如果 resultType 非空,则创建 ResultMap 对象    } else if (resultType != null) {        ResultMap inlineResultMap = new ResultMap.Builder(                configuration,                statementId + "-Inline",                resultType,                new ArrayList<>(),                null).build();        resultMaps.add(inlineResultMap);    }    return resultMaps;}

4.1.4 getStatementResultMaps

#getStatementParameterMap(...) 方法,获得 ParameterMap 对象。代码如下:

// MapperBuilderAssistant.java

private ParameterMap getStatementParameterMap(        String parameterMapName,        Class<?> parameterTypeClass,        String statementId) {    // 获得 ParameterMap 的编号,格式为 `${namespace}.${parameterMapName}`    parameterMapName = applyCurrentNamespace(parameterMapName, true);    ParameterMap parameterMap = null;    // <2> 如果 parameterMapName 非空,则获得 parameterMapName 对应的 ParameterMap 对象    if (parameterMapName != null) {        try {            parameterMap = configuration.getParameterMap(parameterMapName);        } catch (IllegalArgumentException e) {            throw new IncompleteElementException("Could not find parameter map " + parameterMapName, e);        }    // <1> 如果 parameterTypeClass 非空,则创建 ParameterMap 对象    } else if (parameterTypeClass != null) {        List<ParameterMapping> parameterMappings = new ArrayList<>();        parameterMap = new ParameterMap.Builder(                configuration,                statementId + "-Inline",                parameterTypeClass,                parameterMappings).build();    }    return parameterMap;}
  • 主要看 <1> 处,如果 parameterTypeClass 非空,则创建 ParameterMap 对象。
  • 关于 <2> 处,MyBatis 官方不建议使用 parameterMap 的方式

原文地址:https://www.cnblogs.com/siye1989/p/11622204.html

时间: 2024-10-18 19:59:29

mybatis-加载statement配置2的相关文章

atitit.动态加载数据库配置in orm hibernate mybatis

atitit.动态加载数据库配置in orm 1. 动态加载数据库配置的优点::: 1 1.1. 组合多个配置文件... 1 1.2. 连接多个数据库 1 2. 基本的流程:::getCfg内存对象,,,,生成工厂类,在opoenSession 1 2.1. Hibernate动态添加配置流程 1 2.2. mybatis动态添加配置流程 1 2.3. #===hb code 2 3. 参考 3 1. 动态加载数据库配置的优点::: 1.1. 组合多个配置文件... 1.2. 连接多个数据库 2

mybatis加载xml配置文件

<build>     <finalName>bizcloud-tcb2b</finalName>     <!-- mybatis加载xml配置文件的配置  -->     <resources>         <resource>             <directory>src/main/java</directory>             <includes>           

写个重新加载 ocelot 配置的接口

原文:写个重新加载 ocelot 配置的接口 写个重新加载 ocelot 配置的接口 Intro 我们想把 ocelot 的配置放在自己的存储中,放在 Redis 或者数据库中,当修改了 Ocelot 的配置之后希望即时生效,又不想在网关这边定时刷新 ocelot 配置,ocelot 配置没变化的时候,定时刷新配置是一种无意义的资源浪费,ocelot 自带的有一个 Administration ,感觉对于我来说,有点太重了,不想去集成这个东西,于是就想自己实现一个重新加载配置的接口. 实现代码

mybatis加载配置文件详解

spring整合Mybatis后,SqlSessionFactory的创建由spring进行了代理,以下是SqlSessionFactory创建的流程 SqlSessionFactoryBean: public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { private

Spring BeanPostProcessor与动态加载数据源配置

前言: 本文旨在介绍Spring动态配置数据源的方式,即对一个DataSource的配置诸如jdbcUrl,user,password,driverClass都通过运行时指定,而非由xml静态配置. Spring构造Context的参数一般只包含配置文件路径和类加载器,如果需要达到动态传入配置参数的目的,需要Spring在初始化数据源相关bean的时候能够对原有配置执行修改或替换,为方便处理,本文将定义一个名为DynamicDataSourceConfigHolder的公共类提供配置数据存储.

网站后端_Python+Flask.0004.FLASK配置管理之三种方式加载外部配置?

简单介绍: 说明: 复杂的项目需要配置各种环境,若设置少可直接硬编码,设置多的话可通过加载配置/加载文件/加载变量的方式来设置 app.config.update(     DEBUG=True, ) 扩展: app.config是flask.config.Config类的实例,继承子PY内置数据结构dict,所以可以使用如上update方法,支持传入多个键值对,其实app.config内置很多配置变量(http://flask.pocoo.org/docs/0.11/config/#Built

【Selenium】Option加载用户配置,Chrom命令行参数

about:version - 显示当前版本 about:memory - 显示本机浏览器内存使用状况 about:plugins - 显示已安装插件 about:histograms - 显示历史记录 about:dns - 显示DNS状态 about:cache - 显示缓存页面 about:gpu -是否有硬件加速 about:flags -开启一些插件 //使用后弹出这么些东西:"请小心,这些实验可能有风险",不知会不会搞乱俺的配置啊! chrome://extensions/

ubuntu开机自动加载iptables配置(转)

原文:http://www.xuebuyuan.com/730127.html iptables的使用参见http://wiki.ubuntu.org.cn/IptablesHowTo iptables配置完成后,规则是自动立即生效的,但是机器重启动后,规则会丢失 ubuntu下可以通过以下步骤保存iptables设置,并实现开机自动加载 1.iptables配置完成后手动保存 执行iptables-save > /etc/iptables.up.rules ,将当前配置保存再iptables.

spring: 加载远程配置

通常在spring应用中,配置中的properties文件,都是打包在war包里的,部署规模较小,只有几台服务器时,这样并没有什么大问题.如果服务器多了,特别是集群部署时,如果要修改某一项配置,得重新打包.部署,一台台机器改过去,十分麻烦. 看了Spring-Cloud项目,深受启发,Spring-Cloud把配置文件放在远程的git或svn这类云平台之上,所有应用启动时从云上获取配置,配置需要修改时,直接修改git上的配置即可,十分方便,但是这个项目并不简单,新概念太多,需要一定时间熟悉. 借