天天看點

mybatis 配置初始化過程(1)

開篇

 過去一周忙着上線一個線上服務,沒時間閱讀源碼,幸好服務已經順利上線,可以抽空開始mybatis的系列了,沒錯這周開始準備開啟mybatis的整個系列,歡迎大家訂閱。

 按照我現在粗淺的了解,從mybatis的使用過程來看基本可以分為三大步驟,分别是:

  • 配置加載 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
  • 會話建立 SqlSession sqlSession = sqlSessionFactory.openSession();
  • 執行查詢 User user = sqlSession.selectOne("userTest.selectUser", 1);

 這篇博文主要講解下配置加載的過程,由于配置加載涉及很多标簽的加載,這篇文章暫時隻分析宏觀的過程,細節每個标簽的加載後面文章再分析,核心需要記住mybatis的核心是 all in configuration

 補充一點大家不要把mybatis和spring使用mybatis直接混在一起,因為spring和mybatis之間隔了一層spring-mybatis的實作,我們這邊隻講解最純的mybatis。

解析過程概述

 在mybatis的使用過程中,我們建構一個配置的輸入reader并通過SqlSessionFactoryBuilder解析配置生成SqlSessionFactory,我們的解析過程就在建立SqlSessionFactory的過程當中。

public class MyBatisTest {
    public static void main(String[] args) throws Exception {
        String resource = "configuration.xml";
        Reader reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(reader);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = sqlSession.selectOne("userTest.selectUser", 1);
        System.out.println(user.getUsername());
        sqlSession.close();
    }
}
           

配置解析過程

環境配置xml檔案

 mybatis的xml配置當中我認為可以分為兩大塊,分别是environment和mappers,前者主要是指整個mybatis連接配接的環境配置,後者表示mybatis寫的SQL語句,配置的解析過程其實就是解析這塊内容的。

 關于mybatis支援的xml标簽可以參考文章中的mybatis官網,相信我看了以後你一定不會後悔的,包括:

  • properties
  • settings
  • typeAliases
  • typeHandlers
  • objectFactory
  • plugins 插件
  • environments 環境
  • databaseIdProvider 資料庫廠商辨別
  • mappers 映射器
<?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/mybatis?useSSL=false" />
                <property name="username" value="root" />
                <property name="password" value="123456" />
            </dataSource>
        </environment>
    </environments>
    <!-- 映射檔案 -->
    <mappers>
        <mapper resource="map/query.xml" />
        <mapper resource="map/insert.xml" />
        <mapper resource="map/update.xml" />
        <mapper resource="map/delete.xml" />
    </mappers>
</configuration>    
           

環境配置解析源碼分析

 在XMLConfigBuilder類當中parseConfiguration負責解析上面的xml檔案,基本上每個标簽對應的都有一個解析類,這裡我們就不展開了,每個标簽的解析其實都是一個比較複雜的邏輯。

 我們關注下mapperElement方法,這個方法是解析mappers标簽引入的對應的SQL語句的xml檔案,通過Resources.getResourceAsStream方法讀取xml檔案内容,通過mapperParser.parse()方法進行解析。

private void parseConfiguration(XNode root) {
    try {
      //分步驟解析
      //issue #117 read properties first
      //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"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, 
             resource, configuration.getSqlFragments());
            mapperParser.parse();
          } 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();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }
           

SQL解析

SQL的XML格式

 我們通過标簽<mapper resource="map/insert.xml" />引入的SQL檔案的内容如下,mapper其實包含的标簽個數不是特别多,可以給大家感受下:

  • cache – 給定命名空間的緩存配置。
  • cache-ref – 其他命名空間緩存配置的引用。
  • resultMap – 是最複雜也是最強大的元素,用來描述如何從資料庫結果集中來加載對象。
  • parameterMap – 已廢棄!老式風格的參數映射。内聯參數是首選,這個元素可能在将來被移除,這裡不會記錄。
  • sql – 可被其他語句引用的可重用語句塊。
  • insert – 映射插入語句
  • update – 映射更新語句
  • delete – 映射删除語句
  • select – 映射查詢語句
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dao.daoInterface.UserDao">
    <resultMap id="UserMap" type="domain.UserEntity">
        <id property="id" column="uid" />
        <result property="username" column="username" />
        <result property="password" column="password"/>
        <result property="address" column="address"/>
        <result property="createTime" column="createTime"/>
        <result property="updateTime" column="updateTime"/>
        <collection property="campanyEntity" resultMap="dao.daoInterface.CampanyDao.CampanyMap" />
    </resultMap>

    <!-- 可以将sql語句獨立出來,然後引用 -->
    <sql id="selectMap">
        u.username, u.address ,c.campany_name
    </sql>

    <!-- 根據id查詢使用者 -->
    <select id="getUserInfo" parameterType="int" resultMap="UserMap">
            SELECT <include refid="selectMap"/>
            FROM  user u left join campany c
            ON u.username = c.username
            WHERE id = #{id}
            ORDER BY id ASC
    </select>
</mapper>
           

SQL解析源碼分析

 在XMLMapperBuilder類中通過parse進行解析,從源碼可以看出來從标簽/mapper開始解析,一次解析SQL對應的各類标簽,針對每個标簽的解析都是比較複雜的,這個暫時不展開進行分析。

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

private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }
           

配置解析過程流程圖

 核心在于XMLConfiggBuilder解析環境變量和XMLMapperBuilder解析SQL變量,這個圖應該還算比較清晰,通過解析後核心就生成了Configuration對象,這個是整個mybatis的核心

mybatis 配置初始化過程(1)

配置解析時序圖

參考文章

mybatis官網