Mybatis3源码分析(05)-加载Configuration-加载MappedStatement

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

作者:ashan_li

出处:https://blog.csdn.net/ashan_li/article/category/6047775/1?


MappedStatement说明

一个MappedStatement对象对应Mapper配置文件中的一个select/update/insert/delete节点,主要描述的是一条SQL语句。其属性有


//节点中的id属性加要命名空间 private String id; //直接从节点属性中取 private Integer fetchSize; //直接从节点属性中取 private Integer timeout; private StatementType statementType; private ResultSetType resultSetType; //对应一条SQL语句 private SqlSource sqlSource; //每条语句都对就一个缓存,如果有的话。 private Cache cache; //这个已经过时了 private ParameterMap parameterMap; private List<ResultMap> resultMaps; private boolean flushCacheRequired; private boolean useCache; private boolean resultOrdered; //SQL的类型,select/update/insert/detete private SqlCommandType sqlCommandType; private KeyGenerator keyGenerator; private String[] keyProperties; private String[] keyColumns; //是否有内映射 private boolean hasNestedResultMaps; private String databaseId; private Log statementLog; private LanguageDriver lang; private String[] resultSets;

上面属性都比较简单,复杂的是SqlSource,下面有详细的描述!

XMLStatementBuilder.parseStatementNode()方法

resultMap元素的解析已经分析完毕。与resultMap不一样,XmlMapperBuilder在解析select/update/insert/delete的元素时会创建一个XMLStatementBuilder对象,解析的工作交由其方法parseStatementNode()方法完成。

    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        for (XNode context : list) {
          //一个select/update/insert/delete元素创建一个XMLStatementBuilder对象
          final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
          try {
            //将元素解析成MappedStatemenet对象,并加入到Configuration中去
            statementParser.parseStatementNode();
          } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
          }
        }

如下是parseStatementNode()方法的代码

    public void parseStatementNode() {
        String id = context.getStringAttribute("id");
        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);
        String resultMap = context.getStringAttribute("resultMap");
        String resultType = context.getStringAttribute("resultType");
        String lang = context.getStringAttribute("lang");
        LanguageDriver langDriver = getLanguageDriver(lang);

        Class<?> resultTypeClass = resolveClass(resultType);
        String resultSetType = context.getStringAttribute("resultSetType");
        //Statement的类型,对应jdbc里的三个类型:Statement、PreparedStatement、CallableStatement,默认使用PreparedStatement
        StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
        //这个也是跟jdbc里相对应的,一般采用默认即可
        ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

        //Sql的类型,select/update/insert/delete
        String nodeName = context.getNode().getNodeName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        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
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());
        //不做分析
        // Parse selectKey after includes and remove them.
        processSelectKeyNodes(id, parameterTypeClass, langDriver);

        //生成SqlSource对象,这个对象非常重要,接下来详细分析
        // 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");

        //自动生成key,这里也不做讨论
        KeyGenerator keyGenerator;
        String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
        keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
        if (configuration.hasKeyGenerator(keyStatementId)) {
          keyGenerator = configuration.getKeyGenerator(keyStatementId);
        } else {
          keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
              configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
              ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
        }
        //生成MappedStatement对象,并加到Configuration中
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
      }

上在方法里附件解析一些基本的属性外还有两个主要的部分

  1. SqlSource的构建过程
         SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  1. MappedStatement的构建过程
         builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
                fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
                resultSetTypeEnum, flushCache, useCache, resultOrdered,
                keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

SqlSource构建过程

SqlSource接口

    /**
     * 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 getBoundSql(Object parameterObject);

    }

SqlSource表示从mapper.xml或注解中读取的sql内容,该sql一般还不能都被直接执行,例如

    <select id="selectUserDetail" resultMap="detailUserResultMap">
        <!--CDATA里内容会都解析成一个SqlSource对象-->
                    <![CDATA[
                select user_id,user_name,user_type,cust_id from tf_f_user a where a.user_id=#${userId}
            ]]>/select>

SqlSource只有一个方法:getBoundSql(paramenterObject),其中paramenterObject为运行sql里的实际参数

BoundSql

    /**
     * An actual SQL String got form an {@link SqlSource} after having processed any dynamic content.
     * The SQL may have SQL placeholders "?" and an list (ordered) of an parameter mappings
     * with the additional information for each parameter (at least the property name of the input object to read
     * the value from).
     * </br>
     * Can also have additional parameters that are created by the dynamic language (for loops, bind...).
     */
    /**
     * @author Clinton Begin
     */
    public class BoundSql {

      private String sql;
      private List<ParameterMapping> parameterMappings;
      private Object parameterObject;
      private Map<String, Object> additionalParameters;
      private 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<String, Object>();
        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) {
        return metaParameters.hasGetter(name);
      }

      public void setAdditionalParameter(String name, Object value) {
        metaParameters.setValue(name, value);
      }

      public Object getAdditionalParameter(String name) {
        return metaParameters.getValue(name);
      }
    }

SqlBound代码并不多,就是一个普通的java对象,有两个属性非常重要

  1. sql:看代码里的注解,这个sql已经是经过了一些处理,可以被jdbc执行的了。xml里配置的sql可能有占位符#{username},这里的sql占位符已经被替换成"?"号了。
  2. parameterMappings:执行sql对象的实际的参数。由此可以判断,每执行一条sql都会创建一个BoundSql对象。

SqlSource和BoundSql本身并不复杂,复杂的是这两个对象被创建的过程。

LanguageDriver

SqlSource对象是通过LanguageDriver对象构建的,在mapper.xml配置sql里可以通过lang属性指定一个LanguageDriver,但我们通常不会这样子做。当lang属性没有配置时,Mybatis会属性默认给一个。这个默认的LanguageDriver在Configuration的构造方法中定义的:

    public Configuration() {
        ...
        languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
        languageRegistry.register(RawLanguageDriver.class);
      }

马上来看XMLLanguageDriver.createSqlSource()方法

    public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
        XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
        return builder.parseScriptNode();
      }

XMLScriptBuilder

XMLScriptBuilder.parseScriptNode()方法

    public SqlSource parseScriptNode() {
        //将一个sql内容解析成多个SqlNode
        List<SqlNode> contents = parseDynamicTags(context);
        //将多个SqlNode组合一个SqlNode
        MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
        SqlSource sqlSource = null;
        //判断sql是否是动态的
        if (isDynamic) {
          //生成动态的SqlSource
          sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
        } else {
          //生成静态的SqlSource
          sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
        }
        return sqlSource;
      }

再看parseDynamicTagS(context)方法

    private List<SqlNode> parseDynamicTags(XNode node) {
        //一个sql会被解析成多个SqlNode,稍后会有示例详细说明
        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) {
            //如果这个Node只包含文本
            String data = child.getStringBody("");
            //生成一个TextSqlNode
            TextSqlNode textSqlNode = new TextSqlNode(data);
            //判断是否是动态的,如果文本里包含占位符,如#{username}或{table_name},isDynamic()方法就会返回true
            if (textSqlNode.isDynamic()) {
              contents.add(textSqlNode);
              isDynamic = true;
            } else {
              contents.add(new StaticTextSqlNode(data));
            }
          } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
            //如果是有xml标签的Node,交由Handler处理,同时被认为是动态的
            String nodeName = child.getNode().getNodeName();
            NodeHandler handler = nodeHandlers.get(nodeName);
            if (handler == null) {
              throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
            }
            handler.handleNode(child, contents);

            isDynamic = true;
          }
        }
        return contents;
      }

再看看nodeHandlers都有那些

    private Map<String, NodeHandler> nodeHandlers = new HashMap<String, NodeHandler>() {
        private static final long serialVersionUID = 7123056019193266281L;

        {
          //Mybatis3动态sql都支持那些配置,这里就很清楚啦
          put("trim", new TrimHandler());
          put("where", new WhereHandler());
          put("set", new SetHandler());
          put("foreach", new ForEachHandler());
          put("if", new IfHandler());
          put("choose", new ChooseHandler());
          put("when", new IfHandler());
          put("otherwise", new OtherwiseHandler());
          put("bind", new BindHandler());
        }
      };

看到这里基本上能了解sql是怎么被解析的啦!举例说明:

    <select id="selectUserDetail" resultMap="detailUserResultMap">
            <![CDATA[
                select user_id,user_name,user_type,cust_id --这里一行会解析成一个StaticTextSqlNode
                    from tf_f_user a --这里一行也会解析成一个StaticTextSqlNode
                    where a.user_id=#{userId} --这行会被解析成TextSqlNode,并且isDynamic被设置成true,因为有占位符
                                              --这个空行也解析成一个StaticTextSqlNode
            ]]><!-- 这四个SqlNode会被组合成一个MixedSqlNode -->
        </select>

再来个动态sql的:

    <select id="selectUserDetail" resultMap="detailUserResultMap">
            <![CDATA[
                select user_id,user_name,user_type,cust_id --这里一行会解析成一个StaticTextSqlNode
                    from tf_f_user a --这里一行也会解析成一个StaticTextSqlNode
                    where a.user_id=#{userId} --这行会被解析成TextSqlNode,并且isDynamic被设置成true,因为有占位符
                                              --这个空行也解析成一个StaticTextSqlNode
            ]]>
            <if test="user_name!=null"> <!-- 这个标签里的内容会交给IfHandler处理 -->
                and --这里的解析与上行的一样,解析成一个StaticTextSqlNode
                user_name=#{userName} --这里的解析与上行的一样,也会被解析成一个TextSqlNode,并且isDynamic被设置成true,因为有占位符
            </if><!-- IfHandler会将这里面的内个SqlNode组成MixedSqlNode再组成一个IfSqlNode -->
        </select><!-- 这五个SqlNode会被组合成一个MixedSqlNode -->

附上IfHandler的代码

    private class IfHandler implements NodeHandler {
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
          //解析子节点
          List<SqlNode> contents = parseDynamicTags(nodeToHandle);
          //组合
          MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
          String test = nodeToHandle.getStringAttribute("test");
          //生成IfSqlNode
          IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
          targetContents.add(ifSqlNode);
        }
      }

其他的nodeHandler在这里就不讨论了,实现方式与IfHandler差不多。如下两个方法也不在这里做讨论

  1. SqlSource.getBoundSql()方法
  2. SqlNode.apply(DynamicContextcontext)方法

因为这两个方法都是在sql被执行时才调用。在后续的SqlSession实现章节再讨论!

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Mybatis3源码分析(05)-加载Configuration-加载MappedStatement

  • 暂无文章

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏