MyBatis源码解析(一)一MyBatis的初始化过程

目录

1.准备工作

为了更好的演示MyBatis的初始化过程,先创建一个简单的java工程目录,如下所示:

 

1.1 创建实体类Product

package com.tongtong.pojo;


import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@ToString
public class Product {

    @Getter
    @Setter
    private String id;
    @Getter
    @Setter
    private String name;
    @Getter
    @Setter
    private String price;


}

1.2 ProductMapper.xml映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mybatis.ProductMapper">
    <select id="selectProduct" resultType="com.tongtong.pojo.Product">
        select * from Product where name = #{name}
    </select>
</mapper>

1.3 mybatis配置文件 mybatis-config.xml,里面有连接数据库的配置信息

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/alipay?useUnicode=true"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mybatis/ProductMapper.xml"/>
    </mappers>
</configuration>

1.4 测试类MyBatisTest

package com.tongtong;

import com.tongtong.pojo.Product;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;

@Slf4j
public class MyBatisTest {

    public static void main(String[] args) throws IOException {

        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        Product product = sqlSession.selectOne("selectProduct","aa");

        log.info("用户数据:{}",product);
    }
}

 

2.MyBatis的初始化过程

2.1 读取配置文件,获取InputStream对象

 String resource = "mybatis-config.xml";
 InputStream inputStream = Resources.getResourceAsStream(resource);

 

2.2 创建SqlSessionFactoryBuilder对象,并调用build(InputStream inputStream)方法,获取SqlSessionFactory对象

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

来具体看看在创建SqlSessionFactory对象时都做了什么工作

3.通过SqlSessionFactoryBuilder获取SqlSessionFactory

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
            SqlSessionFactory var5;
        try {
            //创建XMLConfigBuilder对象,此对象是解析XML文件所用
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            //parser.parse()是为了创建Configuration对象,Configuration对象的作用后续会介绍
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                inputStream.close();
            } catch (IOException var13) {
                ;
            }

        }

        return var5;
    }

    //上述var5 = this.build(parser.parse())调用的就是此方法,传递一个Configuration对象,可
       以返回一个SqlSessionFactory对象,此处创建的是SqlSessionFactory的子类
       DefaultSqlSessionFactory的实例对象
    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

3.1 XMLConfigBuilder的解析

XMLConfigBuilder的体系结构:

 

  • XMLxxxBuilder是用来解析XML配置文件的,不同类型XMLxxxBuilder用来解析MyBatis配置文件的不同部位。比如:XMLConfigBuilder用来解析MyBatis的配置文件,XMLMapperBuilder用来解析MyBatis中的映射文件(如上文提到的ProductMapper.xml),XMLStatementBuilder用来解析映射文件中的SQL语句。

  • 这些XMLxxxBuilder都有一个共同的父类——BaseBuilder。这个父类维护了一个全局的Configuration对象,MyBatis的配置文件解析后就以Configuration对象的形式存储。

  • 当创建XMLconfigBuilder对象时,会初始化Configuration,见下面代码:

  • 在初始化的时候,一下别名会被注册到Configuration的typeAliasRegistry容器中。

    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
            //初始化Configuration
            super(new Configuration());
            this.localReflectorFactory = new DefaultReflectorFactory();
            ErrorContext.instance().resource("SQL Mapper Configuration");
            this.configuration.setVariables(props);
            this.parsed = false;
            this.environment = environment;
            this.parser = parser;
        }
    
    
    //下面代码是初始化Configuration的代码
     public Configuration() {
          
       this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
       this.typeAliasRegistry.registerAlias("MANAGED",ManagedTransactionFactory.class);
       this.typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
       this.typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
       this.typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
       this.typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
       this.typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
       this.typeAliasRegistry.registerAlias("LRU", LruCache.class);
       this.typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
       this.typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
       this.typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
       this.typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
       this.typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
       this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
       this.typeAliasRegistry.registerAlias("COMMONS_LOGGING",
                                             JakartaCommonsLoggingImpl.class);
       this.typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
       this.typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
       this.typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
       this.typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
       this.typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
       this.typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
       this.typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
       this.languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
       this.languageRegistry.register(RawLanguageDriver.class);
      }
    

    3.2 XMLConfigBuilder的解析过程 

  • 用DOM方法对Xml文件进行解析(这个知识点是关于xml解析的,有几种方式,此处使用的是dom解析)

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

进入XPathParser()中

public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
        this.commonConstructor(validation, variables, entityResolver);
        this.document = this.createDocument(new InputSource(inputStream));
    }

进入createDocument()中

 private Document createDocument(InputSource inputSource) {
        try {
            //1.先获得一个DocumentBuilderFactory对象
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setValidating(this.validation);
            factory.setNamespaceAware(false);
            factory.setIgnoringComments(true);
            factory.setIgnoringElementContentWhitespace(false);
            factory.setCoalescing(false);
            factory.setExpandEntityReferences(true);
            //2.获得一个DocumentBuilder对象,用于解析xml文件
            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 {
                }
            });
            //3.解析
            return builder.parse(inputSource);
        } catch (Exception var4) {
            throw new BuilderException("Error creating document instance.  Cause: " + var4, var4);
        }
    }

上面的过程可以说是为dom解析做准备,而builder.parse(inputSource)中才是真正进行解析的地方:

public Document parse(InputSource is) throws SAXException, IOException {
        if (is == null) {
            throw new IllegalArgumentException(
                DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN,
                "jaxp-null-input-source", null));
        }
        if (fSchemaValidator != null) {
            if (fSchemaValidationManager != null) {
                fSchemaValidationManager.reset();
                fUnparsedEntityHandler.reset();
            }
            resetSchemaValidator();
        }
        domParser.parse(is);
        Document doc = domParser.getDocument();
        domParser.dropDocumentReferences();
        return doc;
    }

有兴趣的话可以自己跟进去看看具体的解析步骤,对这一部分不熟悉的建议再好好看看关于xml解析这部分知识,这里不再赘述。

经过各种折腾,我们把xml配置文件顺利解析成了Document对象,保存在了XPathParser对象中。目前的情况是:XMLConfigBuilder中包含XPathParser,而XPathParser中包含Document(由xml配置文件经dom解析而来)。

现在回过头来分析上述的一段代码,如下

var5 = this.build(parser.parse());

parser即为我们获取的XMLConfigBuilder对象(里面包含Document),调用parse()方法,如下:

 public Configuration parse() {
        if (this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            this.parsed = true;
            //调用parseConfiguration()方法解析配置文件
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            //返回Configuration对象
            return this.configuration;
        }
    }

this.parser.evalNode("/configuration")会返回一个XNode对象

下面就是XNode里面保存的数据,其实就是从xml中解析出的数据:

<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/alipay?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mybatis/ProductMapper.xml"/>
</mappers>
</configuration>

 

进入parseConfiguration方法中一探究竟:

从代码中可以看出对传过来的XNode对象中的各个属性进行解析

 private void parseConfiguration(XNode root) {
        //该方法的主要任务就是对XML文件中的属性进行解析
        try {
            this.propertiesElement(root.evalNode("properties"));
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }

从上述代码中可以看出,XMLConfigBuilder会依次解析配置文件中的<properties>、<settings>、<environments>、<typeAliases>、<plugins>、<mappers>属性进行解析。

来简单的看几个属性的解析步骤:

3.2.1 <properties>节点的解析:

<properties>节点的配置信息:

<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/alipay?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>

<properties>节点的解析过程:

 private void propertiesElement(XNode context) throws Exception {
        if (context != null) {
            //获取<properties>节点的所有子节点>
            Properties defaults = context.getChildrenAsProperties();
            //获取<properties>节点的resource属性
            String resource = context.getStringAttribute("resource");
            //获取<properties>节点的url属性
            String url = context.getStringAttribute("url");
            //resource和url不能同时存在
            if (resource != null && url != null) {
                throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
            }
            //若只存在resource属性,那么获取resouce属性值对应的properties文件中的键值对,并
              添加至default容器中
            if (resource != null) {
                defaults.putAll(Resources.getResourceAsProperties(resource));
            } else if (url != null) {
             /反之,获取url属性值对应的properties文件中的键值对,并
              添加至default容器中
                defaults.putAll(Resources.getUrlAsProperties(url));
            }

            //获取Configuration中原本的属性,并添加至defaults容器中
            Properties vars = this.configuration.getVariables();
            if (vars != null) {
                defaults.putAll(vars);
            }

            this.parser.setVariables(defaults);
            //将default容器添加至configuration中
            this.configuration.setVariables(defaults);
        }

    }

 

3.2.2 <mappers>节点的解析:

<mappers>节点的配置信息:

<mappers>
      <mapper resource="mybatis/ProductMapper.xml"/>
</mappers>

<mappers>节点的解析过程:

private void mapperElement(XNode parent) throws Exception {
   if (parent != null) {
      //遍历<mappers>下所有子节点
      Iterator var2 = parent.getChildren().iterator();

   while(true) {
     while(var2.hasNext()) {
         XNode child = (XNode)var2.next();
         String resource;
         //如果当前节点为<package>
         if ("package".equals(child.getName())) {
             //获取<package>节点的name属性
             resource = child.getStringAttribute("name");
             //将该包下所有的MapperClass注册到configuration的mapperRegistry容器中
             this.configuration.addMappers(resource);
             
             //如果当前节点为<mapper>
             } else {
             //依次获取resource、url、class属性
             resource = child.getStringAttribute("resource");
             String url = child.getStringAttribute("url");
             String mapperClass = child.getStringAttribute("class");
             XMLMapperBuilder mapperParser;
             InputStream inputStream;
             //解析resource属性(mapper.xml文件的路径)
             if (resource != null && url == null && mapperClass == null) {
                 ErrorContext.instance().resource(resource);
                 //将mapper.xml文件解析成输入流
                 inputStream = Resources.getResourceAsStream(resource);
                 //使用XMLMapperBuilder解析Mapper.xml,并将Mapper Class注册进
                   configuration对象中
                 mapperParser = new XMLMapperBuilder(inputStream, this.configuration, 
                                 resource, this.configuration.getSqlFragments());
                            mapperParser.parse();
                 } else if (resource == null && url != null && mapperClass == null) {
                       //解析url属性(mapper.xml文件的路径)
                       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属性(Mapper Class的全限定名)并将全限定名转换成Class对象
                     Class<?> mapperInterface = Resources.classForName(mapperClass);
                             //放入Configuration对象的mapperRegistry容器中
                            this.configuration.addMapper(mapperInterface);
                        }
                    }
                }

                return;
            }
  • MyBatis会遍历<mappers>下所有的子节点,如果当前子节点为<package>,则MyBatis会将该包下的所有的Mapper Class注册到Configuration的mapperRegistry容器中。
  • 如果当前节点为<mapper>,则会依次获取resource、url、class属性,解析映射文件,并将映射文件对应的Mapper Class注册到Configuration的mapperRegistry容器中。

3.3  <Mapper>节点的解析过程

 if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          inputStream = Resources.getResourceAsStream(resource);
          //<mapper>节点的解析
          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);
          //<mapper>节点的解析
          mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, 
          this.configuration.getSqlFragments());
          mapperParser.parse();
 } 

现在看下面这两段:

 mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, 
 this.configuration.getSqlFragments());
 mapperParser.parse();

在解析之前需要先创建XMLMapperBuilder对象:

 private XMLMapperBuilder(XPathParser parser, Configuration configuration, String 
 resource, Map<String, XNode> sqlFragments) {
        //将configuration赋给BaseBuilder
        super(configuration);
        //创建MapperBuilderAssistant对象,从名字上可以看出它是MapperBuilder的协助者
        this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
        this.parser = parser;
        this.sqlFragments = sqlFragments;
        this.resource = resource;
    }
  • 首先会初始化父类BaseBuilder,并将configuration赋给BaseBuilder;
  • 然后创建MapperBuilderAssistant对象,该对象为XMLMapperBuilder的协助者,用来协助XMLMapperBuilder完成一些解析映射文件的动作。

创建好了XMLMapperBuilder后,便可进入解析<mapper>的过程:

  public void parse() {
            //若当前的Mapper.xml没有被解析,则开始解析
        if (!this.configuration.isResourceLoaded(this.resource)) {
            //解析<mapper>节点
            this.configurationElement(this.parser.evalNode("/mapper"));
            //将该Mapper.xml添加至configuration的LoadResource容器中,下回无需再解析
            this.configuration.addLoadedResource(this.resource);
            //将该Mapper.xml对应的Mapper Class注册进configuration的mapperRegistry容器中
            this.bindMapperForNamespace();
        }

        this.parsePendingResultMaps();
        this.parsePendingCacheRefs();
        this.parsePendingStatements();
    }

待续..........

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值