MyBatis源碼學習系列:02-核心接口SqlSessionFactory和SqlSession

要操做數據,咱們須要先看一下兩個核心接口:SqlSessionFactory和SqlSession。
在初始化的時候咱們初始化了一個DefaultSqlSessionFactory的實例,它就實現了SqlSessionFactory接口,下面是SqlSessionFactory的接口代碼:java

public interface SqlSessionFactory {
	
  //打開一個SqlSession
  SqlSession openSession();
  //打開SqlSession,並指定是否自動提交
  SqlSession openSession(boolean autoCommit);
  //打開SqlSession,並指定數據庫鏈接對象
  SqlSession openSession(Connection connection);
  //打開SqlSession,並指定事務隔離級別
  SqlSession openSession(TransactionIsolationLevel level);
  
  //下面四個和上面四個同樣,但能夠指定執行器類型,MyBatis提供了SIMPLE,REUSE,BATCH三種執行器。
  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);
  Configuration getConfiguration();
}

此接口很簡單,除了一個返回當前的配置信息也就是Configuration對象外,還提供openSession及其重載方法。能夠指定是否自動提交,數據庫鏈接,事務隔離級別以及MyBatis執行器類型等信息。事務隔離級別後面單獨介紹。執行器類型後面介紹執行器的時候介紹,此工廠接口主要用來獲取SqlSession實例,進而操做數據庫。sql

SqlSessionFactory接口默認有兩個實現類:
數據庫

首先看下DefaultSqlSessionFactory。session

此類實現的一系列openSession方法及其重載方法在方法內部都調用了openSessionFromDataSource和openSessionFromConnection兩個私有方法。    app

//根據執行器類型信息以及指定的JDBC鏈接對象建立SqlSession
  private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
    try {
      boolean autoCommit;
      try {
    	//獲取是否自動提交信息 
        autoCommit = connection.getAutoCommit();
      } catch (SQLException e) {
        // Failover to true, as most poor drivers
        // or databases won't support transactions
        autoCommit = true;
      }  
     
      //從配置信息中獲取當前使用的環境信息
      final Environment environment = configuration.getEnvironment();
      
      //根據當前使用的環境配置信息獲取事務工廠類對象
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      
      //根據事務工廠類建立一個SqlSession的事務對象
      final Transaction tx = transactionFactory.newTransaction(connection);
      
      //根據事務對象以及執行器類型建立執行器對象
      final Executor executor = configuration.newExecutor(tx, execType);
      
      //建立默認的SqlSession實例對象
      return new DefaultSqlSession(configuration, executor, autoCommit);
      
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
//根據執行器類型信息,事務隔離級別配置以及是否自動提交建立SqlSession
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      //從配置信息中獲取當前使用的環境配置信息
      final Environment environment = configuration.getEnvironment();
      //根據當前使用的環境配置信息建立事務工廠
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //使用當前配置的DataSource信息建立事務對象
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //建立執行器
      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();
    }
  }

上面兩個方法分別從指定的Connection對象或者配置文件中的DataSource信息中建立一個DefaultSqlSession類的對象。能夠看出來。DefaultSqlSessionFactory這個默認的工廠類在內部實際上仍是根據傳入方法的參數,來決定是如何建立一個SqlSession對象。ide

接下來看一下SqlSessionFactory接口的另外一個實現類SqlSessionManager:fetch

SqlSessionManager這個類除了實現了SqlSessionFactory接口外,還實現了SqlSession接口,這裏咱們先關注紅框的部分。ui

這個類的主要做用是能夠不用先建立一個工廠類,能夠直接根據配置信息來獲取SqlSession對象。下面看代碼:this

//內部持有一個默認工廠類對象
  private final SqlSessionFactory sqlSessionFactory;
  private final SqlSession sqlSessionProxy;
  private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();
  
  //構造方法私有化,必須經過newInstance方法建立
  private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
	//設置SqlSession工廠對象
    this.sqlSessionFactory = sqlSessionFactory;
    //建立SqlSession代理類
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }
  //使用Reader字符流傳遞配置信息來構造SqlSessionManager類
  public static SqlSessionManager newInstance(Reader reader) {
	//這裏和以前的過程相似,仍是使用SqlSessionFactoryBuilder類來加載配置信息,
	//而後build出一個SqlSessionFactory工廠類的實例,最後建立manager的對象
    return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, null, null));
  }

雖然咱們不用顯式的先建立一個工廠類,直接使用SqlSessionManager獲取SqlSession。但SqlSessionManager在內部會自動建立一個DefaultSqlSessionFactory實例。而後直接經過SqlSessionFactory的接口的實現方法中調用內部的工廠類去建立一個SqlSession對象。線程

 

此類除了實現了Factory接口從而能夠建立新的SqlSession對象外,還實現了SqlSession接口。意思就是說能夠直接經過SqlSessionManager類來執行具體的數據庫操做。

 

當使用SqlSessionManager類來操做SqlSession接口方法時,並非直接每次建立一個SqlSession對象去操做。而是使用了ThreadLocal類來保存當前線程中的SqlSession對象。下面看代碼:

//內部持有一個默認工廠類對象
  private final SqlSessionFactory sqlSessionFactory;
  
  //SqlSession代理類,經過SqlSession接口方法操做數據庫時使用的時此代理類。
  private final SqlSession sqlSessionProxy;
  //保存了當前線程的SqlSession信息
  private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();
  
  //構造方法私有化,必須經過newInstance方法建立
  private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
	//設置SqlSession工廠對象
    this.sqlSessionFactory = sqlSessionFactory;
    //建立SqlSession代理類
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }

由上面代碼可見,在實例化SqlSessionManager的時候,已經爲咱們建立好了一個SqlSession的代理類。可是這個代理類在建立的時候,沒有任何當前配置環境的人任何信息,也就是說,其代理類對象只是一個實現了SqlSession接口的普通對象。並不能去和數據庫進行交互。那麼還能夠從哪裏拿到真正的SqlSession對象呢?SqlSessionManager類中提供了一組startManagedSession及其重載方法,在方法內部會調用openSession來建立一個SqlSession對象,並保存到當前線程環境中。

public void startManagedSession() {
    this.localSqlSession.set(openSession());
  }
  public void startManagedSession(boolean autoCommit) {
    this.localSqlSession.set(openSession(autoCommit));
  }

也就是說,若是須要用管理類來獲取SqlSession對象從而操做數據庫的話,必須先調用startManagedSession。而後就能夠直接調用SqlSessionManager類的SqlSession接口方法了。例如如下方法。

這些方法內部都調用了localSqlSession中保存的SqlSession對象信息,看下面代碼:

@Override
  public void commit(boolean force) {
    //獲取當前線程環境中的SqlSession對象
    final SqlSession sqlSession = localSqlSession.get();
    if (sqlSession == null) {//若是爲空則提示必須調用startManagedSession方法來進行建立
      throw new SqlSessionException("Error:  Cannot commit.  No managed session is started.");
    }
    sqlSession.commit(force);
  }
  @Override
  public void rollback() {
    final SqlSession sqlSession = localSqlSession.get();
    if (sqlSession == null) {
      throw new SqlSessionException("Error:  Cannot rollback.  No managed session is started.");
    }
    sqlSession.rollback();
  }

除了上面介紹的幾個方法外,還有一些SqlSession接口的方法並非直接從localSqlSession中獲取對象後直接進行調用。而是使用的代理類來執行具體方法。看到此處有點迷惑,咱們知道,在初始化的時候,該代理類並不能直接使用,那麼爲何這裏會直接使用代理類來實現呢?咱們繼續看。在建立代理類的時候,傳入了一個InvocationHandler接口的實現類SqlSessionInterceptor。咱們知道,在使用JDK動態代理時,必須傳入一個InvocationHandler接口的實現。全部調用代理類的方法,最終都會調用該InvocationHandler接口實現類的invoke方法。所以能夠在invoke方法中進行邏輯處理,那咱們看下SqlSessionInterceptor時如何作的:

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //雖然在SqlSession接口方法調用的時候沒有從localSession中獲取SqlSession實例。
      //但在代理類內部的方法攔截器中依然進行了調用。因此咱們剛纔的迷惑爲何會使用一個不能用的代理類來操做數據庫的緣由就清楚了
      final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
      
     
      if (sqlSession != null) {
    	//localSqlSession中存在SqlSession對象則直接調用期對應方法。
        try {
          return method.invoke(sqlSession, args);
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      } else {
    	//localSqlSession中不存在SqlSession對象,
    	//則默認新打開一個SqlSession。而後調用其對應方法,並進行commit。
        final SqlSession autoSqlSession = openSession();
        try {
          final Object result = method.invoke(autoSqlSession, args);
          autoSqlSession.commit();
          return result;
        } catch (Throwable t) {
          autoSqlSession.rollback();
          throw ExceptionUtil.unwrapThrowable(t);
        } finally {
          autoSqlSession.close();
        }
      }
    }

從代碼上看,在攔截器內部依然會從localSqlSession中獲取SqlSession對象,若是未獲取到,則會從新生成一個SqlSession對象。也就是說,在調用SqlSessionManager對象的commit,getConnection等方法時,必須顯式的調用startManagedSession來建立一個當前線程環境的SqlSession對象。而調用insert,update等方法時則能夠直接調用,不用先初始化。

SqlSession接口:

此接口時MyBatis與數據庫交互的直接核心接口。能夠用來進行增刪改查等操做。下面先看下SqlSession的方法信息和類繼承信息。

 SqlSession接口的實現類:

實際上這兩個實現類咱們在以前的分析中都遇到過了。其中對SqlSessionManager還作了詳細分析。而DefaultSqlSession類咱們知道在SqlSessionFactory類生成SqlSession的時候,就是建立了DefaultSqlSession的實例。而DefaultSqlSession內部也是經過工廠方法建立的。也就是說目前全部的SqlSession調用都直接或間接的使用DefaultSqlSession實例進行數據庫操做。

先看下DefaultSqlSession的部分源碼:

public class DefaultSqlSession implements SqlSession {
	
  //持有當前配置信息
  private final Configuration configuration;
  
  //具體執行數據庫操做的執行器
  private final Executor executor;
  
  //是否自動提交
  private final boolean autoCommit;
  
  //髒數據標誌:已插入或更新的數據但未提交時標記爲true,提交後或關閉後標記爲false,在提交或回滾時判斷是否須要執行提交或回滾
  private boolean dirty;
  
  
  private List<Cursor<?>> cursorList;
  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

上面主要是DefaultSqlSession的部分源碼,主要介紹了其屬性和構造器。

其中須要重點介紹的是Executor類的對象executor。這個類是SqlSession接口對象真正和數據庫交互的內部對象。而SqlSession接口是提供給外部用戶使用的。這個對象放到後面介紹,暫時知道這個對象時作什麼用的就行。

@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //從配置信息中獲取已經映射的語句
      MappedStatement ms = configuration.getMappedStatement(statement);
      
      //使用執行器執行查詢
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

上面是DefaultSqlSession中查詢列表的方法。能夠看出最終使用executor來執行具體的查詢任務。

未完....

相關文章
相關標籤/搜索