mybatis源码解析一 xml解析(解析器)

     最近闲来无事,看着一些源码类的书籍,只是光看,好像并不能给自己很好的益处,无法记下来,所以就有了这个Mybatis源码解析系列的博客。网上也有大量的源码解析,在此记录有两个原因,一是为了加深自己的印象,二来则是让广大读者朋友能及时纠正在下的一些理解的错误。如果各位发现有任何的错误,请留言指出,在此感激不尽。闲话不多说,这就开始我们的第一篇xml解析。
     首先,本博客系列的图、代码,大多来自《mybatis技术内幕》一书。

     我们先来看看如图,mybatis的结构分层。

这一讲主要是对解析器模块一个简单分析。

那么就先由一个简单的Mybatis启动开始讲解吧。
也请大家下载mybatis源码(或者在https://github.com/mybatis/mybatis-3中找到对应的源码),本博客中的例子大部分来自己mybatis中的test包。我尽量减少在里面放置过多的源码。


https://github.com/mybatis/mybatis-3/blob/master/src/test/java/org/apache/ibatis/session/SqlSessionTest.java#L64


  public static void setup() throws Exception {
    createBlogDataSource();
    final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
    final Reader reader = Resources.getResourceAsReader(resource);
    sqlMapper = new SqlSessionFactoryBuilder().build(reader);
  }

在此处加载了mybatis的config文件,大家在mapperConfig.xml(https://github.com/mybatis/mybatis-3/blob/master/src/test/java/org/apache/ibatis/builder/MapperConfig.xml)中可以看到内容的。

在Build方法调用时会创建相应的xpath对象

https://github.com/mybatis/mybatis-3/blob/master/src/main/java/org/apache/ibatis/parsing/XPathParser.java#L229
createDocument方法中创建了xpath。

现在我们来说说xpath的API解析XML。

package org.apache.ibatis.test;

import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.io.Resources;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.IOException;
import java.io.Reader;

public class XPathTest {

    private Document createDocument(InputSource inputSource) {
        boolean validation = true;
        EntityResolver entityResolver = new XMLMapperEntityResolver();
        // important: this must only be called AFTER common constructor
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setValidating(validation);

            factory.setNamespaceAware(false);
            factory.setIgnoringComments(true);
            factory.setIgnoringElementContentWhitespace(false);
            factory.setCoalescing(false);
            factory.setExpandEntityReferences(true);

            DocumentBuilder builder = factory.newDocumentBuilder();
            builder.setEntityResolver(entityResolver);
            builder.setErrorHandler(new ErrorHandler() {
                @Override
                public void error(SAXParseException exception) throws SAXException {
                    throw exception;
                }

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

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

    public static void main(String[] args) throws IOException, XPathExpressionException {
        final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
        final Reader reader = Resources.getResourceAsReader(resource);
        XPathTest test = new XPathTest();
        Document document = test.createDocument(new InputSource(reader));
        XPathFactory factory = XPathFactory.newInstance();
        XPath xPath = factory.newXPath();
        Node nodes = (Node) xPath.evaluate("/configuration", document, XPathConstants.NODE);
        // 获取指定节点
        Node mappers = (Node) xPath.evaluate("mappers", nodes, XPathConstants.NODE);
        NodeList mapperList = mappers.getChildNodes();
        for(int i=0; i<mapperList.getLength(); i++){
            Node node = mapperList.item(i);
            if(node.getNodeType() == Node.ELEMENT_NODE) // 只让mapper节点进入,因为可能会有空的text节点
                System.out.println("get mapper source : " + getAttribute(node, "resource"));
        }
    }

    public static void parse(NodeList nodeList) {
        for(int i = 0; i < nodeList.getLength(); i++){
            NodeList childNodes = nodeList.item(i).getChildNodes();
            System.out.println("node name: " + nodeList.item(i).getNodeName());
            if(childNodes != null && childNodes.getLength() > 0)
                parse(childNodes);
        }
    }

    public static String getAttribute(Node node, String attributeName){
        NamedNodeMap nodeAttributes = node.getAttributes();
        for (int i= 0; i< nodeAttributes.getLength(); i ++){
            Node attribute = nodeAttributes.item(i);
            String name = attribute.getNodeName();
            String value = attribute.getNodeValue();
            if(attributeName.equalsIgnoreCase(name)){
                return value;
            }
        }
        return null;
    }
}

这是一个简单的xpath的解析xml的例子,与Mybatis中获取xml配置的解析差不多。
由此我们知道了xml的各节点是以该方式解析出来的。

现在我们来看看XPathParse类,该类就是封装了解析xml的api的。
XPathParse类中的字段说明:

private final Document document;  //就是我们上述XPathTest中的document对象
private boolean validation; //是否开启验证
private EntityResolver entityResolver;//用于加载本地的DTD文件(DTD是用来验证xml的合法性的)
private Properties variables; //用于记录mybatis-config.xml中的变量的键值对的,比如在xml中有${password}等变量时,会由该字段保留具体值。
private XPath xpath;// 与XPathTest中的xpath一致,用于解析xml

XNode对象是对于XPath中的获取的Node对象的一个封装,这里就不过多的讲解,各位可以去看该类的源码。

在XMLConfigBuilder类中的https://github.com/mybatis/mybatis-3/blob/master/src/main/java/org/apache/ibatis/builder/xml/XMLConfigBuilder.java#L102
 

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }


此方法中的root.evalNode中获取的XNode对象就是对XPath中的Node对象的一个封闭。

在此xml解析就大致完了,下一节应该会是反射相关的分析。
mybatis中大量用了反射来将Mapper接口中的方法参数与返回值做解析。

Mybatis是一个轻量级的Java持久层开源框架,它封装了JDBC操作数据库的底层细节,提供了一个简单易用的数据库访问方式。 Mybatis源码分为核心模块和附加模块两部分,核心模块主要包括配置解析、SQL解析、SQL执行等功能,附加模块包括连接池、缓存、事务管理等功能。 在Mybatis源码中,配置解析是其中的关键部分。通过解析mybatis-config.xml配置文件,可以获取到数据库连接信息、映射器配置、插件配置等。在配置解析过程中,Mybatis会对配置文件进行校验,确保配置的正确性。 SQL解析Mybatis的另一个重要功能。Mybatis通过解析Mapper接口中的注解或XML配置文件中的SQL语句,将SQL语句解析为ParameterMapping、BoundSql等对象,并将其封装成一个MappedStatement对象,供后续的SQL执行使用。 SQL执行是Mybatis的核心功能之一。在SQL执行阶段,Mybatis会根据MappedStatement中的信息,获取数据库连接,并执行对应的SQL语句。在执行过程中,Mybatis会通过TypeHandler对参数进行类型转换,并使用ResultSetHandler将查询结果封装成Java对象。 除了核心模块,Mybatis源码还包括了连接池、缓存、事务管理等附加模块的实现。连接池模块负责管理数据库连接的获取和释放,缓存模块负责缓存查询结果以提高性能,而事务管理模块则负责管理数据库的事务处理。 总之,Mybatis源码解析涉及多个关键模块的实现,包括配置解析、SQL解析、SQL执行、连接池、缓存、事务管理等。通过了解这些模块的实现原理,我们可以更好地理解和使用Mybatis框架。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值