Mybatis源碼解析,一步一步從淺入深(二):按步驟解析源碼

在文章:Mybatis源碼解析,一步一步從淺入深(一):建立準備工程,中咱們爲了解析mybatis源碼建立了一個mybatis的簡單工程(源碼已上傳github,連接在文章末尾),並實現了一個查詢功能。接下來就順着查詢功能的實現開始一步一步開始解析mybatis源碼。html

 

首先們觀察咱們的測試代碼類UserDaoTest:java

  

package com.test.learnmybatis;

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 org.junit.Test;

import com.zcz.learnmybatis.dao.UserDao;
import com.zcz.learnmybatis.entity.User;

import junit.framework.Assert;

public class UserDaoTest {
    @Test
    public void finUserById() {
        //2,獲取SqlSession
        SqlSession sqlSession = getSessionFactory().openSession();
        //3,獲取UserDao代理類
        UserDao userMapper = sqlSession.getMapper(UserDao.class);
        //4,執行查詢
        User user = userMapper.findUserById(1);
        Assert.assertNotNull("not find", user);
        
    }
    
    /**
     * 1,獲取SqlSessionFactory
     * @return
     */
    private static SqlSessionFactory getSessionFactory() {
        SqlSessionFactory sessionFactory = null;
        //配置文件名稱
        String resource = "configuration.xml";
        try {
            //使用配置文件構造SqlSessionFactory
            sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
        }catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        return sessionFactory;
    }
}

整個過程能夠分爲四個步驟git

  1,獲取SqlSessionFactorygithub

  2,獲取SqlSessionsql

  3,獲取UserDao代理類apache

  4,執行查詢設計模式

我接下來也會根據這四步進行源碼的解析。session

 


 

 

一,獲取SqlSessionFactorymybatis

   先來看一下靜態方法getSessionFactory。在這個方法中咱們讀取configuration.xml,並使用configuration.xml配置文件實例化了一個SqlSessionFactory。app

  

/**
     * 獲取SqlSessionFactory
     * @return
     */
    private static SqlSessionFactory getSessionFactory() {
        SqlSessionFactory sessionFactory = null;
        //配置文件名稱
        String resource = "configuration.xml";
        try {
            //使用配置文件構造SqlSessionFactory
            sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
        }catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        return sessionFactory;
    }

  

  代碼:sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));

  相信你們都能看懂這段代碼:

  1,Resources.getResourceAsReader(resource),讀取了配置文件configuration.xml,並返回一個字符輸入流(Reader),這樣configuration.xml中的配置信息就都被讀取到了一個字符輸入流中了。

    public static Reader getResourceAsReader(String resource)方法不過多解釋,你們又須要的話,我就在寫一篇文章去詳細闡述。

  2,使用new關鍵字建立了一個SqlSessionFactoryBuilder的匿名對象。

  3,調用匿名對象的build(Reader reader)方法,並將1中的字符輸入流做爲參數傳入。

  這樣SqlSessionFactory對象就建立成功了,接下來咱們詳細的分析一下build(Reader reader)方法,先看源碼

   

  這裏直接調用了public SqlSessionFactory build(Reader reader, String environment, Properties properties)方法,而且environment和properties是null;

  方法詳情以下:

  

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      //構造(XML配置解析器)XMLConfigBuilder對象
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      //調用build方法並返回SqlSessionFactory
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  代碼:XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);

  這段代碼的做用是使用上一步讀取的configuration.xml字符輸入流,實例化一個xml配置解析器(XMLConfigBuilder),而且咱們已經知道 environment, properties的值爲null;

  在XMLConfigBuilder對象的實例化過程當中初始化一個很是重要的對象:Configuration,而Configuration對象承載了configuration.xml中的全部配置內容,具體的初始化內容請查看:Mybatis源碼解析,一步一步從淺入深(三):實例化xml配置解析器(XMLConfigBuilder)

  代碼:return build(parser.parse());

    1,parser.parse()返回一個Configuration對象實例,這是一個及其重要的方法,由於解析configuration.xml並生成Configuration對象,都是在這個方法裏完成的。具體的執行細節,請查閱:Mybatis源碼解析,一步一步從淺入深(四):將configuration.xml的解析到Configuration對象實例Mybatis源碼解析,一步一步從淺入深(五):mapper節點的解析

    2,調用SqlSessionFactoryBuilder的public SqlSessionFactory build(Configuration config)方法建立一個SqlSessionFactory對象實例,咱們來看一下build(Configuration config)方法的內容:

      

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

    在這個方法中,使用parser.parse()解析出來的Configuration對象實例做爲參數,實例化了一個DefaultSqlSessionFactory類的對象。

    咱們看一下類DefaultSqlSessionFactory的聲明:

      

public class DefaultSqlSessionFactory implements SqlSessionFactory {
}

    DefaultSqlSessionFactory類實現了SqlSessionFactory接口,咱們知道java能夠經過多態讓SqlSessionFactory 父類引用指向DefaultSqlSessionFactory子類實例。

  

  到這裏咱們的SqlSessionFactory就建立完成了。

二,獲取SqlSession

  關鍵代碼:SqlSession sqlSession = getSessionFactory().openSession();

  在上一步咱們瞭解到,getSessionFactory()方法,返回一個DefaultSqlSessionFactory實例對象,那麼接下來咱們就看一下這個DefaultSqlSessionFactory的openSession方法:

 public SqlSession openSession() {
    //直接調用了openSessionFromDataSource方法
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 獲取運行環境
      final Environment environment = configuration.getEnvironment();
      // 從運行環境中獲取事務工廠
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 實例化事務
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 實例化sql執行器
      final Executor executor = configuration.newExecutor(tx, execType);
      // 返回默認SqlSession 實例化對象
      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();
    }
  }
 // 從運行環境中獲取事務工廠
  private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
    if (environment == null || environment.getTransactionFactory() == null) {
      return new ManagedTransactionFactory();
    }
    return environment.getTransactionFactory();
  }

   到這裏SqlSession也獲取成功了,獲取到的是DefaultSqlSession的示例對象。

三,獲取UserDao代理類

  關鍵代碼:UserDao userMapper = sqlSession.getMapper(UserDao.class);

  從上一步知道這裏的sqlSession是DefaultSqlSession的實例化對象,那麼就來看一下getMapper方法的源碼:

  public <T> T getMapper(Class<T> type) {
    // 這裏的configuration就是一開始一直陪伴着咱們的那個Configuration實例對象
    return configuration.<T>getMapper(type, this);
  }

  在這裏就有幾個奇怪的問題:

    1,爲何在之前的代碼流程中歷來沒有addMapper,而這裏卻有getMapper?

    2,UserDao明明是咱們定義的一個接口類,根本沒有定義實現類,那這個userMapper是什麼?是mybatis自動爲咱們生成的實現類嗎?

  帶着這兩個問題,咱們在文章Mybatis源碼解析,一步一步從淺入深(六):映射代理類的獲取進行了詳細分析。

四,執行查詢

  經上一系列的分析,已經基本明確了configuration.xml,userDao-mapping.xml文件的解析,映射代理類實例化對象的生成的整個過程。接下來就剩餘最後一個步驟:執行查詢。一塊兒來看一下吧。

  關鍵代碼:User user = userMapper.findUserById(1);

   看過文章Mybatis源碼解析,一步一步從淺入深(六):映射代理類的獲取的同窗們應該已經知道了,代理類對象在執行方法的時候,是調用了InvocationHandler實現類的Invoke方法:

 1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 2     if (Object.class.equals(method.getDeclaringClass())) {
 3       try {
 4         return method.invoke(this, args);
 5       } catch (Throwable t) {
 6         throw ExceptionUtil.unwrapThrowable(t);
 7       }
 8     }
 9     final MapperMethod mapperMethod = cachedMapperMethod(method);
10     return mapperMethod.execute(sqlSession, args);
11   }

  而真正執行查詢的代碼就在第10行,即:mapperMethod.execute(sqlSession, args);看源碼:

 1 //根據查詢類型執行不一樣的方法
 2 public Object execute(SqlSession sqlSession, Object[] args) {
 3     Object result;
 4     if (SqlCommandType.INSERT == command.getType()) {
 5     //insert
 6       Object param = method.convertArgsToSqlCommandParam(args);
 7       result = rowCountResult(sqlSession.insert(command.getName(), param));
 8     } else if (SqlCommandType.UPDATE == command.getType()) {
 9     //update
10       Object param = method.convertArgsToSqlCommandParam(args);
11       result = rowCountResult(sqlSession.update(command.getName(), param));
12     } else if (SqlCommandType.DELETE == command.getType()) {
13     //delete
14       Object param = method.convertArgsToSqlCommandParam(args);
15       result = rowCountResult(sqlSession.delete(command.getName(), param));
16     } else if (SqlCommandType.SELECT == command.getType()) {
17     //select
18       if (method.returnsVoid() && method.hasResultHandler()) {
19         executeWithResultHandler(sqlSession, args);
20         result = null;
21       } else if (method.returnsMany()) {
22         result = executeForMany(sqlSession, args);
23       } else if (method.returnsMap()) {
24         result = executeForMap(sqlSession, args);
25       } else {
26         //咱們的代碼執行的是這裏
27         Object param = method.convertArgsToSqlCommandParam(args);
28         result = sqlSession.selectOne(command.getName(), param);
29       }
30     } else {
31       throw new BindingException("Unknown execution method for: " + command.getName());
32     }
33     if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
34       throw new BindingException("Mapper method '" + command.getName() 
35           + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
36     }
37     return result;
38   }

  關鍵代碼:result = sqlSession.selectOne(command.getName(), param);

  別看這只是一行簡單的代碼,其實這句代碼的背後有不少的邏輯操做,我在文章:Mybatis源碼解析,一步一步從淺入深(七):執行查詢中進行了詳細的分析。請你們查閱;

  源代碼中的第28行代碼的執行結果就是咱們指望的查詢結果了。

五,總體結束  

  到這裏,mybatis的源碼解析系列文章就正式結束了,咱們的示例工程很簡單,只有一個Mapper文件,而且只有一個根據Id查詢的功能。這整個系列的文章,就是根據這個簡單的工程,經過斷點調試的方法,一步一步的跟蹤源代碼,最後明確的這個簡單的查詢是如何執行的。固然mybais還有更多的功能,例如insert,update等,其餘的標籤,其餘的功能,都沒有在本系列的文章中闡述,同時在mybatis中對設計模式的使用也是恰到好處,並且由於做者的水平,時間,精力有限。只能爲你們大概的介紹了一下。解釋不夠準確,信息不夠豐富也是本系列文章的一大遺憾,可是沒有關係,我會在接下來的學習和感悟中繼續補充並完善這一些列的文章,供你們閱讀參考。

  「學習如逆水行舟,不進則退」

  這句話送給你們,以期共勉。


 原創不易,轉載請聲明出處:http://www.javashuo.com/article/p-skvwktcj-cg.html 

相關文章
相關標籤/搜索