MyBatis源码学习(3)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yangxiaochen0302/article/details/81609906

关于MyBatis的xml解析

MyBatis作为一个ORM框架,xml的解析是其核心部分之一,可以说也是MyBatis运行流程的一部分,所以有必要深究一下。
MyBatis中解析xml配置文件的技术用到了XPath,所以先来说一下XPath是如何解析xml的,直接看代码。有一个xml文件如下:

<root>
    <book year="2018">
        <title>book01</title>
        <author>zhangsan</author>
        <publisher>hhhhh</publisher>
    </book>
    <book year="2017">
        <title>book02</title>
        <author>lisi</author>
        <publisher>aaaaa</publisher>
    </book>
    <book year="2016">
        <title>book03</title>
        <author>wangwu</author>
        <publisher>lllll</publisher>
    </book>
</root>

XPath是解析xml的一种语法,比如:“/” 表是从根节点开始,“//”表是从某一节点开始,“[]”表示条件,“@”表示属性,“node()”表示节点,“text()”表示文本节点,“.”表示当前节点,“* ..”表示当前节点的父节点,所以比如://book表示book节点,//book[author=’zhangsan’]表示book节点的author节点为zhangsan,解析xml的步骤共有7步骤:
* 1、生成DocumentBuilderFactory 目的是获取DocumentBuilder
* 2、由DoumentBuilderFactory获取DocumentBuilder
* 3、DocumentBuilder用来加载整个XML文件,builder.parse(xml文件路径)
* 4、加载完整个xml就可以生成XPathFactory
* 5、由XPathFactory来获取XPath对象
* 6、获取XPath的表达式就是对象XPathExpression(表达式)
* 7、exp来求值,参数doc和XPath语法,是个固定值
示例类:

public class XPathTest {
    public static void main(String[] args) throws Exception {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
        Document doc=builder.parse("src\\main\\resources\\test.xml");
        XPathFactory xPathFactory = XPathFactory.newInstance();
        XPath xpath = xPathFactory.newXPath();
        XPathExpression exp = xpath.compile("//book[author='zhangsan']/title/text()");
        Object result=exp.evaluate(doc,XPathConstants.NODESET);
        System.out.println("查询作者为zhangsan的图书标题:");
        NodeList nodes = (NodeList)result;
        for (int i = 0; i < nodes.getLength(); i++) {
            System.out.println(nodes.item(i).getNodeValue());
        }
    }
}

由此来完成xml的解析。
那么MyBatis是怎么做的呢?首先还记得在生成SqlSessionFactory类的时候调用的代码吧?

  SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

build的时候new了一个XMLConfigBuilder,代码XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
在这个里面,构造方法是:


    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
    }

所以XPathParser来了,这个MyBatis内部的一个XPath的解析类,我们来看一下它的一部分代码:

public class XPathParser {
    private Document document;
    private boolean validation;
    private EntityResolver entityResolver;
    private Properties variables;
    private XPath xpath;

    .................    
    public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
        this.commonConstructor(validation, variables, entityResolver);
        this.document = this.createDocument(new InputSource(inputStream));
    }
     private Document createDocument(InputSource inputSource) {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setValidating(this.validation);
            factory.setNamespaceAware(false);
            factory.setIgnoringComments(true);
            factory.setIgnoringElementContentWhitespace(false);
            factory.setCoalescing(false);
            factory.setExpandEntityReferences(true);
            DocumentBuilder builder = factory.newDocumentBuilder();
            builder.setEntityResolver(this.entityResolver);
            builder.setErrorHandler(new ErrorHandler() {
                public void error(SAXParseException exception) throws SAXException {
                    throw exception;
                }

                public void fatalError(SAXParseException exception) throws SAXException {
                    throw exception;
                }

                public void warning(SAXParseException exception) throws SAXException {
                }
            });
            return builder.parse(inputSource);
        } catch (Exception var4) {
            throw new BuilderException("Error creating document instance.  Cause: " + var4, var4);
        }
    }

    private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
        this.validation = validation;
        this.entityResolver = entityResolver;
        this.variables = variables;
        XPathFactory factory = XPathFactory.newInstance();
        this.xpath = factory.newXPath();
    }
}
XPathFactory factory = XPathFactory.newInstance();
        this.xpath = factory.newXPath();

这个就是我们刚才XPtahTest中的创建XPathFactory的代码,有了XPath也就是说我们要进行XPath语法的解析了,再往下看,创建了一个Document,代码:

 this.document = this.createDocument(new InputSource(inputStream));

干嘛呢,我们看一下,

  private Document createDocument(InputSource inputSource) {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setValidating(this.validation);
            factory.setNamespaceAware(false);
            factory.setIgnoringComments(true);
            factory.setIgnoringElementContentWhitespace(false);
            factory.setCoalescing(false);
            factory.setExpandEntityReferences(true);
            DocumentBuilder builder = factory.newDocumentBuilder();
            builder.setEntityResolver(this.entityResolver);
            builder.setErrorHandler(new ErrorHandler() {
                public void error(SAXParseException exception) throws SAXException {
                    throw exception;
                }

                public void fatalError(SAXParseException exception) throws SAXException {
                    throw exception;
                }

                public void warning(SAXParseException exception) throws SAXException {
                }
            });
            return builder.parse(inputSource);
        } catch (Exception var4) {
            throw new BuilderException("Error creating document instance.  Cause: " + var4, var4);
        }
    }

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();觉不觉着很眼熟,没错就是我们刚才XPathTest中生成DocumentBuilderFactory 目的是获取DocumentBuilder,DocumentBuilder builder = factory.newDocumentBuilder();
有了DocumentBuilder就可以装在xml了,到此时XPathParser就准备好了,然后我们再回到SqlSessionFactory类的这段代码中,

    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        }

SqlSessionFactory 中的build方法中的参数传入了XMLConfigBuilder的parse()这个方法返回的是MyBatis的类Configuration也就是配置信息的存储类,那么我们先不看这个类,再看XMLConfigBuilder的parse(),

    public Configuration parse() {
        if (this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            this.parsed = true;
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration;
        }
    }

this.parseConfiguration(this.parser.evalNode(“/configuration”))这个东西就一目了然了,相当于传入了XPath的表达式获取结果,那么它是从configuration开始获取的,而configuration就是我们mybatis-config.xml的根节点,也就是从这里开始MyBatis开始解析配置文件。我们来看parseConfiguration这个方法的实现:

 private void parseConfiguration(XNode root) {
        try {
      //1.properties
      propertiesElement(root.evalNode("properties"));
      //2.类型别名
      typeAliasesElement(root.evalNode("typeAliases"));
      //3.插件
      pluginElement(root.evalNode("plugins"));
      //4.对象工厂
      objectFactoryElement(root.evalNode("objectFactory"));
      //5.对象包装工厂
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //6.设置
      settingsElement(root.evalNode("settings"));
      // read it after objectFactory and objectWrapperFactory issue #631
      //7.环境
      environmentsElement(root.evalNode("environments"));
      //8.databaseIdProvider
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //9.类型处理器
      typeHandlerElement(root.evalNode("typeHandlers"));
      //10.映射器
      mapperElement(root.evalNode("mappers"));
        }

它为每一个xml里的配置节点都写了一个专门的处理方法,如果你对MyBatis的配置项比较熟悉,一看就能明白,properties、typeAliases、objectFactory、settings还有我们最最熟悉的mappers,也就是说MyBatis会专门去查找这些元素,然后做各自的处理,那么我们就看看我们必不可少的配置元素,mapper吧,代码:

 private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            Iterator var2 = parent.getChildren().iterator();

            while(true) {
                while(var2.hasNext()) {
                    XNode child = (XNode)var2.next();
                    String resource;
                    if ("package".equals(child.getName())) {
                        resource = child.getStringAttribute("name");
                        this.configuration.addMappers(resource);
                    } else {
                        resource = child.getStringAttribute("resource");
                        String url = child.getStringAttribute("url");
                        String mapperClass = child.getStringAttribute("class");
                        XMLMapperBuilder mapperParser;
                        InputStream inputStream;
                        if (resource != null && url == null && mapperClass == null) {
                            ErrorContext.instance().resource(resource);
                            inputStream = Resources.getResourceAsStream(resource);
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                            mapperParser.parse();
                        } else if (resource == null && url != null && mapperClass == null) {
                            ErrorContext.instance().resource(url);
                            inputStream = Resources.getUrlAsStream(url);
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                            mapperParser.parse();
                        } else {
                            if (resource != null || url != null || mapperClass == null) {
                                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                            }

                            Class<?> mapperInterface = Resources.classForName(mapperClass);
                            this.configuration.addMapper(mapperInterface);
                        }
                    }
                }

                return;
            }
        }
    }

我们来看这一段:

  if (resource != null && url == null && mapperClass == null) {
                            ErrorContext.instance().resource(resource);
                            inputStream = Resources.getResourceAsStream(resource);
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                            mapperParser.parse();
                        } else 

实例化了一个类XMLMapperBuilder,在MyBatis里所有带Builder的类都继承自BaseBuilder,所有的Builder类的构造方法也都类似,这是XMLMapperBuilder的构造方法:

    public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
        this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments);
    }

从中我们可以看出,也是实例化了一个XPathParser这个类,在XMLMapperBuilder类中做了什么事呢?new 完XMLMapperBuilder之后调用 mapperParser.parse();在parse()里开始解析mapper.xml,我们来看一下,

    public void parse() {
        if (!this.configuration.isResourceLoaded(this.resource)) {
            this.configurationElement(this.parser.evalNode("/mapper"));
            this.configuration.addLoadedResource(this.resource);
            this.bindMapperForNamespace();
        }

        this.parsePendingResultMaps();
        this.parsePendingChacheRefs();
        this.parsePendingStatements();
    }
 this.configurationElement(this.parser.evalNode("/mapper"));

就是用来解析mapper的,去解析各种mapper的配置属性,由于我们示例里的xml比较简单只写了select|insert|update|delete所以我们来看一下代码片段:

   //1.配置namespace
      String namespace = context.getStringAttribute("namespace");
      if (namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      //2.配置cache-ref
      cacheRefElement(context.evalNode("cache-ref"));
      //3.配置cache
      cacheElement(context.evalNode("cache"));
      //4.配置parameterMap(已经废弃,老式风格的参数映射)
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      //5.配置resultMap(高级功能)
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      //6.配置sql(定义可重用的 SQL 代码段)
      sqlElement(context.evalNodes("/mapper/sql"));
      //7.配置select|insert|update|delete
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

解析select标签:

    private void buildStatementFromContext(List<XNode> list) {
        if (this.configuration.getDatabaseId() != null) {
            this.buildStatementFromContext(list, this.configuration.getDatabaseId());
        }

        this.buildStatementFromContext(list, (String)null);
    }

这个方法buildStatementFromContext里面就是循环遍历,然后新生成了一个XMLStatementBuilder类:

    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      //构建所有语句,一个mapper下可以有很多select
      //语句比较复杂,核心都在这里面,所以调用XMLStatementBuilder
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
          //核心XMLStatementBuilder.parseStatementNode
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
          //如果出现SQL语句不完整,把它记下来,塞到configuration去
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

new完以后调用parseStatementNode()方法,其实就是用来挨个解析select标签里的各种属性的:

  public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");
    //如果databaseId不匹配,退出
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }
    //驱动程序每次批量返回的结果行数
    Integer fetchSize = context.getIntAttribute("fetchSize");
    //超时时间
    Integer timeout = context.getIntAttribute("timeout");
    //引用外部 parameterMap,已废弃
    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");
    //脚本语言,mybatis3.2的新功能
    String lang = context.getStringAttribute("lang");
    //得到语言驱动
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class<?> resultTypeClass = resolveClass(resultType);
    //结果集类型,FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE 中的一种
    String resultSetType = context.getStringAttribute("resultSetType");
    //语句类型, STATEMENT|PREPARED|CALLABLE 的一种
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    //获取命令类型(select|insert|update|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);
    //是否要缓存select结果
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    //仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。
    //这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。 
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    //解析之前先解析<include>SQL片段
    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,一般是DynamicSqlSource
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    //(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值
    String keyProperty = context.getStringAttribute("keyProperty");
    //(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值
    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 {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
    }

最后添加进MappedStatement,这个类就是映射的语句,最后经过一系列复杂的处理会把这个mapper接口添加进Configuration类中的mapper注册器,

 Class<?> mapperInterface = Resources.classForName(mapperClass);
                            this.configuration.addMapper(mapperInterface);

好吧,今天先写到这里。

展开阅读全文

没有更多推荐了,返回首页