文章摘自: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存在兩個不足。
- 我們需要自己手動關閉sqlsesion,我們知道,人總是不可靠的。忘關sqlsession 是有很大概率發生的
- 線程安全問題:DefaultSqlSession是線程不安全的Sqlsession 。也就是說DefaultSqlSession不能是單例,
如何解決這兩個問題?
自動關閉Session問題:
- 我們可以自己做一個切面,專門處理session關閉問題
- Mybatis為我們提供了升級版的DefaultSqlSession, SqlSessionManager可以解決這個問題
線程安全問題:
- 既然不能共用,很自然的,我們每次使用DefaultSqlSession的時候都從SqlSessionFactory當中獲取一個就可以了啊。
- 但是我們仍然想使用單例版的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
最后的執行時序
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();
}
}
總結
DefaultSqlSession
與SqlSessionManager
是Mybatis 默認提供的兩個sqlsesion;SqlSessionTemplate
是Mybatis與Spring整合時用的sqlsesionDefaultSqlSession
是單例線程不安全的,SqlSessionManager與SqlSessionTemplate 是單例線程安全的SqlSessionManager
與SqlSessionTemplate
都對通過動態代理技術對DefaultSqlSession 自動關閉問題進行了優化SqlSessionManager
是自己管理sqlsession,SqlSessionTemplate
外包給TransactionSynchronizationManager
管理sqlsession。