Mybatis 原始碼學習(二)

在好的網名也有備註|發表於2020-11-10

Mybatis原始碼分析(二)

話不多說我們們上程式碼!!!!!!

核心配置檔案

​ Mybatis的核心配置檔案,一般命名為mybatis-config.xml,這個檔案包含了使用mybatis的時候的所有配置,只有正確載入了該配置檔案,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>
    <!--開啟日誌輸出-->
    <settings>
        <!-- 設定日誌輸出為LOG4J -->
        <setting name="logImpl" value="LOG4J" />
    </settings>
    <!--配置類別名,配置後在Mapper配置檔案(通常我們將編寫SQL語句的配置檔案成為Mapper配置檔案)中需要使用pojo包中的類時,使用簡單類名即可-->
    <typeAliases>
        <package name="com.li.domain" />
    </typeAliases>
    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/practice?characterEncoding=utf-8"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <package name="com.li.mapper"/>
<!--        <mapper class="com.li.domain.Person"/>-->
    </mappers>

</configuration>

這裡只研究mybatis的對映檔案方式,如下為mapper的對映檔案

Mapper對映檔案

<?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="com.li.mapper.PersonMapper" >
  <resultMap id="BaseResultMap" type="com.li.domain.Person" >
    <result column="id" property="id" jdbcType="VARCHAR" />
    <result column="email" property="email" jdbcType="VARCHAR" />
  </resultMap>
  
  <select id="selectById" parameterType="java.lang.String" 		         resultType="com.li.domain.Person">
        select * from person where id = #{id}
  </select>
</mapper>

上面的mapper對映檔案中,只有一個select標籤,對應的對映檔案的介面為PersonMapper.java

MapperPerson介面檔案

package com.li.mapper;


import com.li.domain.Person;

public interface PersonMapper {

    Person selectById(String id);
    
}

介面中的方法名稱和對映檔案中的select標籤中的id進行對應,必須保持一一對應;

domain檔案

package com.li.domain;

public class Person {
    private String id;

    private String email;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id == null ? null : id.trim();
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email == null ? null : email.trim();
    }
}

Mybatis的測試類

package com.li.test;

import com.li.domain.Person;
import com.li.mapper.PersonMapper;
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.InputStream;

/**
 * When I wrote this, only God and I understood what I was doing
 * Now, God only knows
 *
 * @ClassName: MybatisTest
 * @Description: mybatis原始碼測試
 * @Author: Li
 * @Date: Created in 2020/11/10 21:37
 */
public class MybatisTest {
    public static void main(String[] args) throws Exception{
        //載入mybatis的核心配置檔案
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        //使用建造者模式建立一個SqlSession的工廠SqlSessionFactory物件
        SqlSessionFactory sqlSessionFactory = new 	SqlSessionFactoryBuilder().build(inputStream);
        //通過工廠生產一個sqlsession物件
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //通過SqlSession獲取一個PersionMapper介面的代理物件
        PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
        //執行selectById方法傳入入參 "1"
        Person person = mapper.selectById("1");
        //輸出查詢結果
        System.out.println(person);
        //關閉Sqlsession
        sqlSession.close();
    }
}

mybatis的執行流程

  1. 讀取配置檔案
    //載入mybatis的核心配置檔案
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    

    讀取mybatis的核心配置檔案轉化為InputStream流物件,這裡的Resources類是mybatis用來載入類路徑下的檔案;

  2. SqlSessionFactoryBuilder 使用建造者模式建立SqlSessionFactory工廠;下面是他所持有的方法。

在這裡插入圖片描述

可以看到SqlSessionFactoryBuilder 中所有的方法都是build方法,這就是標準的建造者模式,方法的返回值都是SqlSessionFactory;

    // 1.這裡呼叫的是build(inputStream)方法
    public SqlSessionFactory build(InputStream inputStream) {
        return build(inputStream, null, null);
      }

    // 2.下面又呼叫
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
          // XMLConfigBuilder:用來解析XML配置檔案
          // 使用構建者模式
          XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
          // parser.parse():使用XPATH解析XML配置檔案,將配置檔案封裝為Configuration物件
          // 返回DefaultSqlSessionFactory物件,該物件擁有Configuration物件(封裝配置檔案資訊)
          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.
          }
        }
      }
  1. 在上面的build(InputStream inputStream, String environment, Properties properties)方法中,又使用建造者模式建立了一個XMLConfigBuilder的類,用來解析讀取到的mybatis核心配置檔案,生成configuration物件並封裝到SqlSessionFactory中;
    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
      }
    // 3.通過建構函式建立XMLConfigBuilder類
      private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    	//  建立Configuration物件,並通過TypeAliasRegistry註冊一些Mybatis內部相關類的別名
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
      }
    
  2. 建立XMLConfigBuilder類後,通過呼叫上面<2>中build(parser.parse())方法來進行mybatis核心配置檔案的第一步解析
    /**
       * 解析XML配置檔案
       * @return
       */
      public Configuration parse() {
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        // parser.evalNode("/configuration"):通過XPATH解析器,解析configuration根節點
        // 從configuration根節點開始解析,最終將解析出的內容封裝到Configuration物件中
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
      }
    
    // 接著呼叫parseConfiguration(parser.evalNode("/configuration")),對mybatis的核心配置檔案進行解析
    private void parseConfiguration(XNode root) {
        try {
          //issue #117 read properties first
          // 解析</properties>標籤
          propertiesElement(root.evalNode("properties"));
          // 解析</settings>標籤
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          loadCustomVfs(settings);
          loadCustomLogImpl(settings);
          // 解析</typeAliases>標籤
          typeAliasesElement(root.evalNode("typeAliases"));
          // 解析</plugins>標籤
          pluginElement(root.evalNode("plugins"));
          // 解析</objectFactory>標籤
          objectFactoryElement(root.evalNode("objectFactory"));
          // 解析</objectWrapperFactory>標籤
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          // 解析</reflectorFactory>標籤
          reflectorFactoryElement(root.evalNode("reflectorFactory"));
          settingsElement(settings);
    
          // read it after objectFactory and objectWrapperFactory issue #631
          // 解析</environments>標籤
          environmentsElement(root.evalNode("environments"));   //做主要分析
          // 解析</databaseIdProvider>標籤
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          // 解析</typeHandlers>標籤
          typeHandlerElement(root.evalNode("typeHandlers"));
          // 解析</mappers>標籤
          mapperElement(root.evalNode("mappers")); 				//做主要分析
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }
    

    呼叫parseConfiguration(XNode root)方法進行mybatis核心配置檔案的解析,我們在這裡主要分析(environments|mappers)這兩個標籤,其他配置就不詳細分析了;

  3. 呼叫environmentsElement(root.evalNode(“environments”)),解析核心配置檔案中的“environments”標籤;
    // 解析‘environments’標籤
    private void environmentsElement(XNode context) throws Exception {
        if (context != null) {
          if (environment == null) {
              //獲取到environments 的default 屬性
            environment = context.getStringAttribute("default");
          }
            //迴圈解析environments子節點的‘environment’標籤
          for (XNode child : context.getChildren()) {
            String id = child.getStringAttribute("id");
            if (isSpecifiedEnvironment(id)) {
              TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                //解析environment子節點的‘dataSource’標籤,使用建造者模式建立DataSourceFactory,生成DataSource物件
              DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
              DataSource dataSource = dsFactory.getDataSource();
                //將配置檔案中的environment解析完成後封裝成一個Environment物件,封裝在configuration物件中
              Environment.Builder environmentBuilder = new Environment.Builder(id)
                  .transactionFactory(txFactory)
                  .dataSource(dataSource);
              configuration.setEnvironment(environmentBuilder.build());
            }
          }
        }
      }
    
  4. 呼叫mapperElement(root.evalNode(“mappers”)),解析核心配置檔案中的“mappers”標籤
    /**
       * 解析<mappers>標籤
       * @param parent  mappers標籤對應的XNode物件
       * @throws Exception
       */
      private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
          // 獲取<mappers>標籤的子標籤
          for (XNode child : parent.getChildren()) {
        	// <package>子標籤
            if ("package".equals(child.getName())) {
              // 獲取mapper介面和mapper對映檔案對應的package包名
              String mapperPackage = child.getStringAttribute("name");
              // 將包下所有的mapper介面以及它的代理物件儲存到一個Map集合中,key為mapper介面型別,value為代理物件工廠
              configuration.addMappers(mapperPackage);
            } else {// <mapper>子標籤
              // 獲取<mapper>子標籤的resource屬性
              String resource = child.getStringAttribute("resource");
              // 獲取<mapper>子標籤的url屬性
              String url = child.getStringAttribute("url");
              // 獲取<mapper>子標籤的class屬性
              String mapperClass = child.getStringAttribute("class");
              // 它們是互斥的
              if (resource != null && url == null && mapperClass == null) {
                ErrorContext.instance().resource(resource);
                InputStream inputStream = Resources.getResourceAsStream(resource);
                // 專門用來解析mapper對映檔案
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                // 通過XMLMapperBuilder解析mapper對映檔案
                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());
                // 通過XMLMapperBuilder解析mapper對映檔案
                mapperParser.parse();
              } else if (resource == null && url == null && mapperClass != null) {
                Class<?> mapperInterface = Resources.classForName(mapperClass);
                // 將指定mapper介面以及它的代理物件儲存到一個Map集合中,key為mapper介面型別,value為代理物件工廠
                configuration.addMapper(mapperInterface);
              } else {
                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
              }
            }
          }
        }
      }
    

    到此為止解析Mybatis的核心配置檔案的過程就到此為止了,在上面程式碼中根據配置的不同,建立XMLMapperBuildermapper對映檔案進行解析;

    解析mapper對映檔案的過程,請聽下回分解;

相關文章