Mybatis三種SqlSession的區別


文章摘自:https://blog.csdn.net/wuqinduo/article/details/103736862

三個SqlSession

DefaultSqlSession與SqlSessionManager 與SqlSessionTemplate 是我們常見的3種sqlsesion

在這里插入圖片描述

從類圖可以看出他們三個都實現了了SqlSession,也就是他們都可以表示一個會話。與其他不同的是SqlSessionManager實現了SqlSessionFactory

這三種sqlsession的區別是啥?他們的應用場景是啥呢?

這一切我們從DefaultSqlSession開始說起。

DefaultSqlSession

DefaultSqlSession 是SqlSession的默認實現。

當我們單獨使用Mybatis時,我們通常使用DefaultSqlSession 來執行SQL,操作數據庫。

String resource = "mybatis-config.xml";
// 讀取配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 構建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 獲取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
    User user = sqlSession.selectOne("MyMapper.selectUser", 1);
    System.out.println(user);
} finally {
    sqlSession.close();
}

但是DefaultSqlSession存在兩個不足。

  1. 我們需要自己手動關閉sqlsesion,我們知道,人總是不可靠的。忘關sqlsession 是有很大概率發生的
  2. 線程安全問題:DefaultSqlSession是線程不安全的Sqlsession 。也就是說DefaultSqlSession不能是單例,

如何解決這兩個問題?

自動關閉Session問題

  1. 我們可以自己做一個切面,專門處理session關閉問題
  2. Mybatis為我們提供了升級版的DefaultSqlSession, SqlSessionManager可以解決這個問題

線程安全問題

  1. 既然不能共用,很自然的,我們每次使用DefaultSqlSession的時候都從SqlSessionFactory當中獲取一個就可以了啊。
  2. 但是我們仍然想使用單例版的Sqlsession怎么辦?別慌,Mybatis為我們提供了升級版的DefaultSqlSession ,SqlSessionManager可以解決這個問題。

SqlSessionManager

個人認為,與其說SqlSessionManager是DefaultSqlSession 的升級版,不如說SqlSessionManager是DefaultSqlSession代理版(或者封裝版)

為什么這樣說?來看看SqlSessionManager能干嗎

1.獲取DefaultSqlSession的能力

String resource = "mybatis-config.xml";
// 讀取配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionManager sqlSessionManager =SqlSessionManager.newInstance(inputStream);

//獲取一個SqlSession
SqlSession session = sqlSessionManager.openSession();
//SqlSessionManager 類的openSession
public SqlSession openSession() {
    return sqlSessionFactory.openSession();
}

從這個角度看,其實他跟SqlSession沒有什么區別。他只是封裝了SqlSession ,具體工作還是交給SqlSession去做的.
sqlSessionManager.openSession() = sqlSessionFactory.openSession()

得到的Sqlsession自然也是DefaultSqlSession

2.解決DefaultSqlSession的不足

2.1解決自動關閉問題

為了解決自動關閉問題,SqlSessionManager使用了代理技術來實現了自動關閉問題。

使用JDK動態代理技術,動態生成代理對象sqlSessionProxy ,並用內部類SqlSessionInterceptor來對SqlSession的執行方法進行增強。

private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
    //使用JDK代理技術,生成一個代理對象
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
}

我們以insert為例來看看

sqlSessionManager.insert()

    public int insert(String statement) {
    return sqlSessionProxy.insert(statement);
}

當執行insert()方法時,sqlSessionManager內部是調用sqlsessionProxy代理對象的insert方法。之后執行增強器的SqlSessionInterceptor#invoke方法。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
    if (sqlSession != null) {// 當使用線程本地變量
        try {
            return method.invoke(sqlSession, args);
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    } else {//不使用線程本地變量。
        //從sqlSessionFactory獲取一個DefaultSqlSession
        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
        }
    }
}

invoke方法內部,調用openSession() 從sqlSessionFactory中獲取一個DefaultSqlSession,執行對應的方法,並在finally中執行關閉sqlsession

最后的執行時序

20191227182619633

sqlSessionManager.insert() 的背后依然是DefaultSqlSession.insert 。並且幫助我們close 了DefaultSqlSession。

開發人員再也不必擔心,忘記關閉DefaultSqlSession 了。

在執行sqlSessionManager的sqlsession方法時, 其本質也是每次都創建DefaultSqlSession ,不正是線程安全的嗎?

單例是sqlSessionManager ;但真正執行的是DefaultSqlSession

2.1解決線程安全問題。

解決線程安全問題,sqlSessionManager 還有另一個方式,那就是使用線程本地變量,不同於每次執行CURD操作都重新獲取一個DefaultSqlSession 。 線程本地變量這種方式是一個線程內使用同一個請求,這就大大節省了創建DefaultSqlSession 的時間,並且是線程安全的。

使用:

sqlSessionManager.startManagedSession();//綁定Session到線程本地變量
sqlSessionManager.insert()

原理:

private ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();

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

當startManagedSession()開始線程本地變量時,會從sqlSessionFactory獲取一個session 放入到線程本地localSqlSession中,綁定到當前線程。

當我們執行sqlSessionManager.insert方法時,執行到增強器的invoke方法時,會從localSqlSession獲取綁定到當前線程的sqlsession

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //從線程本地變量里獲取
    final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
    if (sqlSession != null) {
        try {
            return method.invoke(sqlSession, args);
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }
}

小結:我們可以看出,SqlSessionManager通過動態代理技術+線程本地變量,升級了DefaultSqlSession的使用。

SqlSessionTemplate

SqlSessionTemplate 是Mybatis與Spring 整合時的線程安全sqlsession .

來看其構造方法

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
          PersistenceExceptionTranslator exceptionTranslator) {
    
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        this.sqlSessionProxy = (SqlSession) newProxyInstance(
            SqlSessionFactory.class.getClassLoader(),
            new Class[] { SqlSession.class },
            new SqlSessionInterceptor());
      }
1234567891011

會發現他和SqlSessionManager 類似,SqlSessionTemplate 也使用了JDK動態代理技術來實現。SqlSessionTemplate 也有一個內部類增強器SqlSessionInterceptor。

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //獲取連接
        SqlSession sqlSession = getSqlSession(
            SqlSessionTemplate.this.sqlSessionFactory,
            SqlSessionTemplate.this.executorType,
            SqlSessionTemplate.this.exceptionTranslator);
        try {
            //執行sqlsession 目標方法
            Object result = method.invoke(sqlSession, args);
            if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                sqlSession.commit(true);
            }
            return result;
        } catch (Throwable t) {
            Throwable unwrapped = unwrapThrowable(t);
            if (SqlSessionTemplate.this.exceptionTranslator != null && 
                unwrapped instanceof PersistenceException) {
                // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                sqlSession = null;
                Throwable translated = SqlSessionTemplate.this.exceptionTranslator.
                    translateExceptionIfPossible((PersistenceException) unwrapped);
                if (translated != null) {
                    unwrapped = translated;
                }
            }
            throw unwrapped;
        } finally {
            if (sqlSession != null) {
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
        }
    }
}

我們也從自動關閉與線程安全兩個角度來看看SqlSessionTemplate

1.解決線程安全問題

不同於SqlSessionManager 自己管理session的方式,SqlSessionTemplate 把session的管理外包出去了

SqlSessionTemplate 把獲取sqlsession的工作交給了SqlSessionUtils去做了。

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, 
                                       PersistenceExceptionTranslator exceptionTranslator) {
    //從事務同步器中獲取Sqlsession。
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.
        getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
        return session;
    }

    if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Creating a new SqlSession");
    }

    session = sessionFactory.openSession(executorType);

    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
}

SqlSessionUtils 會先嘗試從TransactionSynchronizationManager事務同步器中獲取sqlsesion,獲取不到再從工廠內獲取。

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);//從事務同步器中獲取sqlsession
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
        return session;
    }
    session = sessionFactory.openSession(executorType);
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
    return session;
}

TransactionSynchronizationManager 在spring源碼系列11:事務代理對象的執行 一文提過。是spring事務實現原理的重要組件。

TransactionSynchronizationManager 本身也是一個線程本地變量管理器。

從這一點來看,他和SqlSessionManager 是一樣的。只是管理的方式不同,一個自己管,一個外包

2.解決自動關閉問題

同SqlSessionManager一樣,在執行完session后,也會幫助close.
不同的是,

  • session 如果是由TransactionSynchronizationManager管理的,則只會更新引用計數器,讓Spring在托管事務結束時調用close回調,關閉session。
  • session不是由TransactionSynchronizationManager 管理的,則直接關閉session
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    if ((holder != null) && (holder.getSqlSession() == session)) {
        //計數減1
        holder.released();
    } else {
        //關閉session
        session.close();
    }
}

總結

  • DefaultSqlSessionSqlSessionManager 是Mybatis 默認提供的兩個sqlsesion;SqlSessionTemplate是Mybatis與Spring整合時用的sqlsesion
  • DefaultSqlSession 是單例線程不安全的,SqlSessionManager與SqlSessionTemplate 是單例線程安全的
  • SqlSessionManagerSqlSessionTemplate 都對通過動態代理技術對DefaultSqlSession 自動關閉問題進行了優化
  • SqlSessionManager是自己管理sqlsession,SqlSessionTemplate外包給TransactionSynchronizationManager管理sqlsession。


免責聲明!

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



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