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文件等內容。

 

有不正之處,歡迎指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM