Mybatis3.3.x技術內幕(一):SqlSession和SqlSessionFactory列傳

前言:我長大了,成年了,有需求,但我單身,因此我要討個媳婦,要求是:漂亮、高挑、身材好、笑容甜美……
java

和A相親:漂亮,不夠高挑。sql

和B相親:高挑,身材不夠好。數據庫

和C相親:身材好,笑容不夠甜美。apache

……安全

好煩,沒有符合要求的,我是一個痛苦的完美主義者,我怒了,我決定依照完美的幻想,本身造一個。但不知道個人決定是對是錯,請佛指點,佛面帶慈祥的微笑,曰:你不造一個,誰造一個?網絡

因而,造以前,我給她取了一個清新典雅的名字:Mybatis3.3.x技術內幕session

wocao,好耳熟的名字,好像在哪兒聽過。app

以上即是面對已有的那麼多有關Mybatis源碼分析的文章,而我爲何決定新造一個「東風鐵甲」的故事。ide



一部經典的《Mybatis3.3.x技術內幕》,就從SqlSession和SqlSessionFactory提及。源碼分析

1. SqlSession和SqlSessionFactory的接口定義

SqlSession:

public interface SqlSession extends Closeable {
    <T> T selectOne(String var1);
    <T> T selectOne(String var1, Object var2);
    <E> List<E> selectList(String var1);
    <E> List<E> selectList(String var1, Object var2);
    <E> List<E> selectList(String var1, Object var2, RowBounds var3);
    <K, V> Map<K, V> selectMap(String var1, String var2);
    <K, V> Map<K, V> selectMap(String var1, Object var2, String var3);
    <K, V> Map<K, V> selectMap(String var1, Object var2, String var3, RowBounds var4);
    void select(String var1, Object var2, ResultHandler var3);
    void select(String var1, ResultHandler var2);
    void select(String var1, Object var2, RowBounds var3, ResultHandler var4);
    int insert(String var1);
    int insert(String var1, Object var2);
    int update(String var1);
    int update(String var1, Object var2);
    int delete(String var1);
    int delete(String var1, Object var2);
    void commit();
    void commit(boolean var1);
    void rollback();
    void rollback(boolean var1);
    List<BatchResult> flushStatements();
    void close();
    void clearCache();
    Configuration getConfiguration();
    <T> T getMapper(Class<T> var1);
    Connection getConnection();
}

SqlSession,數據庫的C、R、U、D及事務處理接口,你懂的。

SqlSessionFactory:

public interface SqlSessionFactory {
    SqlSession openSession();
    SqlSession openSession(boolean var1);
    SqlSession openSession(Connection var1);
    SqlSession openSession(TransactionIsolationLevel var1);
    SqlSession openSession(ExecutorType var1);
    SqlSession openSession(ExecutorType var1, boolean var2);
    SqlSession openSession(ExecutorType var1, TransactionIsolationLevel var2);
    SqlSession openSession(ExecutorType var1, Connection var2);
    Configuration getConfiguration();
}

不解釋,你懂的。

2. SqlSession和SqlSessionFactory的類結構圖

                                                      (Made In Intellij Idea IDE)

SqlSession實現類:DefaultSqlSession和SqlSessionManager

SqlSessionFactory實現類:DefaultSqlSessionFactory和SqlSessionManager

3. DefaultSqlSession和DefaultSqlSessionFactory源碼分析

org.apache.ibatis.session.defaults.DefaultSqlSession.java部分源碼:

private Configuration configuration;
private Executor executor;

 @Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  
  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

總結:彷佛一切的一切,都是從配置對象Configuration中取出材料來,委託給執行器Executor去處理。


org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.java部分源碼:

public class DefaultSqlSessionFactory implements SqlSessionFactory {

  private final Configuration configuration;

  public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }
    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);
      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並返回,這裏出現了那個貫穿Mybatis執行流程的Executor接口,很是重要的接口,後續會對其進行仔細分析。

4. SqlSessionManager源碼分析(重點)

SqlSessionManager同時實現了SqlSession和SqlSessionFactory接口。

org.apache.ibatis.session.SqlSessionManager.java部分源碼。

public class SqlSessionManager implements SqlSessionFactory, SqlSession {

  private final SqlSessionFactory sqlSessionFactory;
  // proxy
  private final SqlSession sqlSessionProxy;
  // 保持線程局部變量SqlSession的地方
  private ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();

  private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
    // 這個proxy是重點
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }

  public static SqlSessionManager newInstance(Reader reader) {
    return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, null, null));
  }

  public static SqlSessionManager newInstance(Reader reader, String environment) {
    return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, environment, null));
  }
  //...
  // 設置線程局部變量sqlSession的方法
  public void startManagedSession() {
    this.localSqlSession.set(openSession());
  }

  public void startManagedSession(boolean autoCommit) {
    this.localSqlSession.set(openSession(autoCommit));
  }
  //...
  @Override
  public <T> T selectOne(String statement, Object parameter) {
    return sqlSessionProxy.<T> selectOne(statement, parameter);
  }

  @Override
  public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
    return sqlSessionProxy.<K, V> selectMap(statement, mapKey);
  }
  //...

變量sqlSessionFactory:至關於DefaultSqlSessionFactory的實例(不是proxy)。

變量sqlSessionProxy:是JDK動態代理出來的proxy(是proxy

動態代理的目的,是爲了經過攔截器InvocationHandler,加強目標target的方法調用。

target:DefaultSqlSession的實例。

全部的調用sqlSessionProxy代理對象的C、R、U、D及事務方法,都將通過SqlSessionInterceptor攔截器,並最終由目標對象target實際完成數據庫操做。

org.apache.ibatis.session.SqlSessionInterceptor.java的源碼。

private class SqlSessionInterceptor implements InvocationHandler {
    public SqlSessionInterceptor() {
        // Prevent Synthetic Access
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
      if (sqlSession != null) {
        try {
          // 一、存在線程局部變量sqlSession(不提交、不回滾、不關閉,可在線程生命週期內,自定義sqlSession的提交、回滾、關閉時機,達到複用sqlSession的效果)
          return method.invoke(sqlSession, args);
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      } else {
      // 二、不存在線程局部變量sqlSession,建立一個自動提交、回滾、關閉的SqlSession(提交、回滾、關閉,將sqlSession的生命週期徹底限定在方法內部)
        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();
        }
      }
    }
  }

注意:SqlSession的生命週期,必須嚴格限制在方法內部或者request範圍(也稱之爲Thread範圍),線程不安全,線程之間不能共享。(官方文檔有明確說明)

一、request範圍使用SqlSession

sqlSessionManager.startManagedSession();
try {
    sqlSessionManager.query1();
    sqlSessionManager.query2();
    sqlSessionManager.update1();
    sqlSessionManager.update2();
    //...
}catch (Throwable t) {
    sqlSessionManager.rollback();
} finally {
    sqlSessionManager.close();
}

一次性執行了一系列的方法業務,最後統一異常回滾,統一關閉sqlSession,全程建立1次sqlSession,銷燬1次sqlSession。只是個例子,具體如何使用線程本地變量sqlSession,徹底取決於你本身。

二、method範圍使用SqlSession

SqlSessionManager.query1();
SqlSessionManager.query2();

以上僞代碼,各自分別開啓了一個SqlSession,並銷燬了各自的SqlSession。即,建立了2次SqlSession,銷燬了2次SqlSession。


注:SqlSessionManager彷佛是廢棄不使用的了,可是,它並不妨礙咱們探究其源碼


版權提示:文章出自開源中國社區,若對文章感興趣,可關注個人開源中國社區博客(http://my.oschina.net/zudajun)。(通過網絡爬蟲或轉載的文章,常常丟失流程圖、時序圖,格式錯亂等,仍是看原版的比較好)

相關文章
相關標籤/搜索