欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-18 22:08:59
...

目录

1.准备工作

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

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

 

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的体系结构:

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

 

  • 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的解析过程 

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

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

parser即为我们获取的XMLConfigBuilder对象,调用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;
        }
    }

进入parseConfiguration方法中一探究竟:

 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();
    }

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

 


相关标签: redis源码