mybatis程序员程序员首页投稿

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

2017-10-11  本文已影响744人  大闲人柴毛毛

1. 准备工作

为了看清楚MyBatis的整个初始化过程,先创建一个简单的Java项目,目录结构如下图所示:


1.1 Product 产品实体类

public class Product {
    private long id;
    private String productName;
    private String productContent;
    private String price;
    private int sort;
    private int falseSales;
    private long category_id;
    private byte type;
    private byte state;
    // PS:省略setter、getter函数
}

1.2 ProductMapper 产品持久化接口

public interface ProductMapper {
    /**
     * 查询所有的产品
     * @return
     */
    List<Product> selectProductList();
}

1.3 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="team.njupt.mapper.ProductMapper">
    <select id="selectProductList" resultType="team.njupt.entity.Product">
        select * from product
    </select>
</mapper>

1.4 db.properties 数据库配置文件

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/waimai?useUnicode=true&characterEncoding=utf8
username=root
password=xxxxxx

1.5 mybatis.xml MyBatis的配置文件

<?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>
    <properties resource="db.properties">
        <!--<property name="username" value="dev_user"/>-->
        <!--<property name="password" value="F2Fa3!33TYyg"/>-->
    </properties>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="team/njupt/mapper/ProductMapper.xml"/>
    </mappers>
</configuration>

1.6 Main 主函数

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

        String resource = "mybatis.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            ProductMapper productMapper = sqlSession.getMapper(ProductMapper.class);
            List<Product> productList = productMapper.selectProductList();
            for (Product product : productList) {
                System.out.printf(product.toString());
            }
        } finally {
            sqlSession.close();
        }
    }
}

2. MyBatis初始化过程

2.1 获取配置文件

当系统初始化时,首先会读取配置文件,并将其解析成InputStream

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

2.2 创建SqlSessionFactoryBuilder对象

SqlSessionFactoryBuilder的名字中可以看出,SqlSessionFactoryBuilder是用来创建SqlSessionFactory对象的。
来看一下SqlSessionFactoryBuilder源码:


SqlSessionFactoryBuilder中只有一些重载的build函数,这些build函数的入参都是MyBatis配置文件的输入流,返回值都是SqlSessionFactory;由此可见,SqlSessionFactoryBuilder的作用很纯粹,就是用来通过配置文件创建SqlSessionFactory对象的。

2.3 SqlSessionFactory创建过程

下面具体来看一下,build函数是如何创建SqlSessionFactory对象的。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    return build(parser.parse());
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  } finally {
    ErrorContext.instance().reset();
    try {
      inputStream.close();
    } catch (IOException e) {
      // Intentionally ignore. Prefer previous error.
    }
  }
}

2.3.1 构造XMLConfigBuilder对象

build函数首先会构造一个XMLConfigBuilder对象,从名字上大致可以猜到,该对象是用来解析XML配置文件的。下面来看一下XMLConfigBuilder的体系结构。

2.3.2 解析配置文件

当有了XMLConfigBuilder对象之后,接下来就可以用它来解析配置文件了。

  private void parseConfiguration(XNode root) {
  try {
    // 解析<properties>节点
    propertiesElement(root.evalNode("properties"));
    // 解析<settings>节点
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    // 解析<typeAliases>节点
    typeAliasesElement(root.evalNode("typeAliases"));
    // 解析<plugins>节点
    pluginElement(root.evalNode("plugins"));
    // 解析<objectFactory>节点
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    // 解析<reflectorFactory>节点
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
    // 解析<environments>节点
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    // 解析<mappers>节点
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

从上述代码中可以看到,XMLConfigBuilder会依次解析配置文件中的<properties>< settings >< environments>< typeAliases >< plugins >< mappers >等属性。下面介绍下几个重要属性的解析过程。

2.3.2.1 <properties>节点的解析过程

2.3.2.2 <settings>节点的解析过程

2.3.2.3 <typeAliases>属性的解析过程

<typeAliases>属性的定义方式有如下两种:

<typeAliases>节点的解析过程如下:

  private void typeAliasesElement(XNode parent) {
  if (parent != null) {
    // 遍历<typeAliases>下的所有子节点
    for (XNode child : parent.getChildren()) {
      // 若当前结点为<package>
      if ("package".equals(child.getName())) {
        // 获取<package>上的name属性(包名)
        String typeAliasPackage = child.getStringAttribute("name");
        // 为该包下的所有类起个别名,并注册进configuration的typeAliasRegistry中          
        configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
      } 
      // 如果当前结点为< typeAlias >
      else {
        // 获取alias和type属性
        String alias = child.getStringAttribute("alias");
        String type = child.getStringAttribute("type");
        // 注册进configuration的typeAliasRegistry中
        try {
          Class<?> clazz = Resources.classForName(type);
          if (alias == null) {
            typeAliasRegistry.registerAlias(clazz);
          } else {
            typeAliasRegistry.registerAlias(alias, clazz);
          }
        } catch (ClassNotFoundException e) {
          throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
        }
      }
    }
  }
}

2.3.2.4 <mappers>节点的解析过程

<mappers>节点的定义方式有如下四种:

<mappers>
  <package name="org.mybatis.builder"/>
</mappers>
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
</mappers>
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>

<mappers>节点的解析过程如下:

  private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    // 遍历<mappers>下所有子节点
    for (XNode child : parent.getChildren()) {
      // 如果当前节点为<package>
      if ("package".equals(child.getName())) {
        // 获取<package>的name属性(该属性值为mapper class所在的包名)
        String mapperPackage = child.getStringAttribute("name");
        // 将该包下的所有Mapper Class注册到configuration的mapperRegistry容器中
        configuration.addMappers(mapperPackage);
      } 
      // 如果当前节点为<mapper>
      else {
        // 依次获取resource、url、class属性
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        // 解析resource属性(Mapper.xml文件的路径)
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          // 将Mapper.xml文件解析成输入流
          InputStream inputStream = Resources.getResourceAsStream(resource);
          // 使用XMLMapperBuilder解析Mapper.xml,并将Mapper Class注册进configuration对象的mapperRegistry容器中
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          mapperParser.parse();
        } 
        // 解析url属性(Mapper.xml文件的路径)
        else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          InputStream inputStream = Resources.getUrlAsStream(url);
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
          mapperParser.parse();
        } 
        // 解析class属性(Mapper Class的全限定名)
        else if (resource == null && url == null && mapperClass != null) {
          // 将Mapper Class的权限定名转化成Class对象
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          // 注册进configuration对象的mapperRegistry容器中
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}

其中,<mapper>节点的解析过程如下:

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

2.3.3 创建SqlSessionFactory对象

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    return build(parser.parse());
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  } finally {
    ErrorContext.instance().reset();
    try {
      inputStream.close();
    } catch (IOException e) {
      // Intentionally ignore. Prefer previous error.
    }
  }
}

回过头来再看一下SqlSessionFactorybuild函数,刚才说了半天,介绍了XMLConfigBuilder解析映射文件的过程,解析完成之后parser.parse()函数会返回一个包含了映射文件解析结果的configuration对象,紧接着,这个对象将作为参数传递给另一个build函数,如下:

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

这个函数将configuration作为参数,创建了DefaultSqlSessionFactory对象。
DefaultSqlSessionFactory是接口SqlSessionFactory的一个实现类,SqlSessionFactory的体系结构如下图所示:

此时,SqlSessionFactory创建完毕!

上一篇下一篇

猜你喜欢

热点阅读