1. 程式人生 > >mybatis原始碼分析:啟動過程

mybatis原始碼分析:啟動過程

mybatis在開發中作為一個ORM框架使用的比較多,所謂ORM指的是Object Relation Mapping,直譯過來就是物件關係對映,這個對映指的是java中的物件和資料庫中的記錄的對映,也就是一個java物件對映資料庫中的一條記錄。瞭解了mybatis的背景及作用下面看mybatis的使用及從原始碼分析啟動過程。

一、概述

要使用mybatis必須要引入mybatis的jar包,由於我這裡需要檢視原始碼,使用的mybatis原始碼作為依賴。首先需要下載原始碼,可執行從github上下載,mybatis下載下來是maven工程,按照maven匯入的方式進行匯入即可,詳細的步驟在這裡不在贅述。

引入了mybatis的依賴便可以開發mybatis的程式,我這裡使用的原始碼版本為:3-3.4.x版本。

1、核心配置檔案

mybatis核心配置檔案,一般命名為mybatis-config.xml,說是核心配置檔案一點也不錯,這個檔案包含了使用mybatis的時候的所有配置,只有正確載入了此檔案,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>
    <settings>
        <!-- 設定日誌輸出為LOG4J -->
        <setting name="logImpl" value="LOG4J" />
        <!--將以下畫線方式命名的資料庫列對映到 Java 物件的駝峰式命名屬性中-->
        <setting name= "mapUnderscoreToCamelCase" value="true" />
    </settings>
    <!--簡化類名稱空間 -->
    <typeAliases>
       
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="UNPOOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url"
                    value="jdbc:mysql://127.0.0.1:3306/test" />
                <property name="username" value="user" />
                <property name="password" value="user" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--常規做法-->
        <mapper resource="cn/com/mybatis/dao/UserMapper.xml"/>
        <mapper resource="cn/com/mybatis/dao/MenuMapper.xml"/>
        <!--第二種做法-->
        <!--  
        <package name="cn.com.mybatis.dao"/>
        -->
    </mappers>
</configuration>

上面是一個mybatis-config.xml檔案的例項,在configuration標籤中配置了mappers、settings、environments等標籤,這些標籤代表的意思及如何解析在後面會詳細分析。

這裡sql的配置方式有註解和對映檔案兩種方式,這裡採用對映檔案的方式,所以在mybatis-config.xml檔案中配置了Mapper檔案,下面看UserMapper.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="cn.com.mybatis.dao.UserMapper">
  <select id="selectUser" resultType="hashmap">
    select * from e_user 
  </select>
</mapper>

上面的UserMapper.xml只有一個select標籤,另外在mapper標籤中配置了namespace屬性,這個屬性很關鍵,代表的是一個應對映檔案對應的介面。下面看UserMapper介面,

package cn.com.mybatis.dao;

import java.util.HashMap;
import java.util.List;

public interface UserMapper {

    public List<HashMap> selectUser();
}

細心的讀者會發現介面中的方法名和對映檔案中的select標籤的id是一樣的,沒錯這裡必須是一致,必須一一對應,至於為什麼要保持一致,後面會通過原始碼分析,並且在一同一個namespace中不能包含同名的方法,也就是對映檔案中的id不允許重複。

有了上面的這些配置,便可以開始mybatis之旅了,下面看下每個檔案的位置,

二、詳述

上面已經把mybatis的環境及程式碼已經分析了,下面看測試程式碼,

package cn.com.mybatis.test;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;

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 cn.com.mybatis.dao.UserMapper;

public class TestMybatis {

    public static void main(String[] args) throws IOException {
        // TODO Auto-generated method stub
        //載入核心配置檔案
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//生成一個SqlSessionFactoryBuilder物件 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//建立一個SqlSessionFactory物件 SqlSessionFactory factory = builder.build(inputStream);
//獲得一個SqlSession物件 SqlSession session=factory.openSession();
//獲得一個UserMapper UserMapper userMapper=session.getMapper(UserMapper.class); List<HashMap> users=userMapper.selectUser(); System.out.println(users.size()); } }

上面的程式碼,即使用mybatis的過程,下面來分析。

1、讀取配置檔案

下面看讀取mybatis核心檔案的程式碼,

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

這裡就一句話,把mybatis-config.xml轉化為InputStream物件,這裡mybatis-config.xml檔案是在類路徑下(WEB-INF/classes)下,這裡Resources類是如何讀取檔案,後續詳細分析,只要明白這裡會獲得InputStream就好。

2、建立SqlSessionFactoryBuilder

下面需要建立一個SqlSessionFactoryBuilder物件,看這個類名可以猜到應該使用的是建造者模式,

SqlSessionFactoryBuilder  builder  = new SqlSessionFactoryBuilder();

看下具體的SqlSessionFactoryBuilder類,下面是其所有的方法,

可以看到都是build方法,那麼其構造方法也就是預設的。在SqlSessionFactoryBuilder類中所有的方法都是build方法,這是標準的建造者模式,可以看到返回值都是SqlSessionFactory。在mybatis中很多地方都使用了建造者模式,後邊會進行專門的分析。

下面看生成SqlSessionFactory,

SqlSessionFactory  factory  = builder.build(inputStream);

呼叫的SqlSessionFactoryBuilder的build(InputStream)方法,

public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

又呼叫下面的方法,

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.
      }
    }
  }

在上面的程式碼中,使用inputStream生成一個XMLConfigBuilder,這裡又是一個建造者模式,看XMLConfigBuilder的構造方法,

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
//呼叫下面的構造方法 this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); } private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//初始化父類BaseBuilder類的configuration super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; }

下面看其parse()方法,此方法構造的是在這裡建造的物件是Configuration物件,

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
//解析mybatis-config.xml檔案中的<configuration>標籤,把該標籤中的內容 parseConfiguration(parser.evalNode("/configuration")); return configuration; }

從上面的程式碼中,可以看出呼叫parseConfiguration方法,這個方法就是解析<configuration>標籤,如下,

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);
      //解析別名標籤,例<typeAlias alias="user" type="cn.com.bean.User"/>
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析外掛標籤
      pluginElement(root.evalNode("plugins"));
      //解析objectFactory標籤,此標籤的作用是mybatis每次建立結果物件的新例項時都會使用ObjectFactory,如果不設定
      //則預設使用DefaultObjectFactory來建立,設定之後使用設定的
      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"));
      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);
    }
  }

上面的方法後面會逐一進行分析,主要就是解析核心配置檔案mybatis-config.xml中的配置,並放到Configuration物件中。

再回到上面的build(parse.parse())方法,其定義如下,

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

從上面的程式碼,可以看到使用configuration生成一個DefaultSqlSessionFactory物件。

3、建立SqlSessionFactory

上面分析到SqlSessionFactoryBuilder最後會返回一個DefaultSqlSessionFactory物件,

public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }

可以看到,把Configuration物件直接賦值給了DefaultSqlSessionFactory物件的configuration屬性。

4、建立SqlSession

 

SqlSession session=factory.openSession();

上面的程式碼呼叫factory的openSession()方法,也就是DefaultSqlSesssionFactory的openSession()方法,

@Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

呼叫了下面的方法,

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        //獲得mybatis核心配置檔案中的environment資訊,包括dataSource id trasacationFactory
      final Environment environment = configuration.getEnvironment();
      //獲得transactionFactory,如果environment中沒有則使用ManagedTransactionFactory
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

上面的方法返回了一個DefaultSqlSession物件,具體的過程就是使用上面的引數構造一個DefaultSqlSession物件。

5、獲取Mapper物件

使用下面的程式碼獲取一個Mapper物件,有了Mapper物件便可以呼叫方法,進行資料庫操作,

UserMapper userMapper=session.getMapper(UserMapper.class);

上面的程式碼從DefaultSqlSession中呼叫getMapper返回一個UserMapper物件,這裡UserMapper是一個代理物件,至於為什麼是代理物件,先不分析,先了解其過程,

@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

可以看出是從DefaultSqlSession的Configuration中獲得該Mapper,下面繼續看,

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//返回的是下面的方法 return mapperRegistry.getMapper(type, sqlSession); } //呼叫此方法 @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }

從上面中可以看到從knownMappers中根據type,這裡就是UserMapper.class返回一個MapperProxyFactory,最後返回MapperProxyFactory的一個例項,

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

下面看newInstance方法,

@SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
//JDK動態代理 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }

到這裡我們可以看到最終返回的是一個代理物件,而且是JDK動態代理的一個物件,從我們只編寫了介面也可以猜出這裡返回的應該是一個JDK動態代理的類,因為JDK動態代理要求必須有介面。

6、執行操作

執行操作,則直接呼叫其方法即可,

List<HashMap> users=userMapper.selectUser();

從上面的分析制定useMapper是代理物件,那麼代理類便是上面的MapperProxy類,那麼執行selectUser方法,便會執行MapperProxy的invoke方法,那麼該類肯定也會實現InvocationHandler介面,下面,

在看其invoke方法,

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
//呼叫的是execute方法 return mapperMethod.execute(sqlSession, args); }

從上面可以看到呼叫的是mapperMethod.execute方法,並且把sqlSession方法作為引數傳進去。那也就是說最後呼叫的sqlSession的方法,下面看,

可以看到呼叫的sqlSession的方法,從這裡大體可以看出sqlSession是個重要的類。

三、總結

上面分析了mybatis的啟動過程,包括載入核心配置檔案(mybatis-config.xml)、SqlSessionFactory、SqlSession、執行操作資料庫方法。這裡僅僅分析了其執行過程,很多細節後續會一一分析,像載入配置檔案、Configuration類、DefaultSqlSession以及如何通過介面找到對應的Mapper檔案等內容。

 

有不正之處,歡迎指