環(huán)境:mybatis-spring 2.0.3
Mybatis提供了一個工具類SqlSessionTemplate,通過它可以操作所有Mapper配置文件的SQL語句
@Test
public void testInsert() {
Product product = (Product) super.context.getBean("product");
// Mapper接口全限定名稱+Sql語句標簽的Id
String sql = "com.hyc.dao.ProductMapper.insertProduct";
int add = super.template.insert(sql, product);
System.out.println(add > 0 ? "插入成功" : "插入失敗");
}
打開SqlSessionTemplate源碼,根據(jù)注釋內(nèi)容,它是由Spring管理的線程安全類,與Spring的事務(wù)管理器協(xié)作確保真實的SqlSession被關(guān)聯(lián)到當前的Spring事務(wù)。更進一步,它基于Spring事務(wù)配置管理session的生命周期,包括關(guān)閉、提交、回滾
同時,因為是線程安全的,所以可以作為單例對象被所有的Dao使用
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
// SqlSession代理,通過它執(zhí)行數(shù)據(jù)庫操作
// 通過這個代理對象,可以獲取到專屬于當前線程的SqlSession對象
private final SqlSession sqlSessionProxy;
// 構(gòu)造器
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory,
ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
// ...
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// 使用JDK動態(tài)代理創(chuàng)建一個SqlSession代理對象
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
// 原生mybatis采用DefaultSqlSession發(fā)起數(shù)據(jù)庫操作
// mybatis-spring則通過一個代理對象,它代理的是原生DefaultSqlSession
// 之所以使用代理,是為了獲取當前線程專屬的DefaultSqlSession
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.selectOne(statement, parameter);
}
}
根據(jù)上述代碼段,可以知道SqlSessionTemplate也實現(xiàn)了SqlSession接口,同時組合了另一個SqlSession接口代理對象sqlSessionProxy執(zhí)行具體的數(shù)據(jù)庫操作,代理對象的增強邏輯如下
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 獲取一個SqlSession
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
// 通過反射調(diào)用目標對象
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) {
// 關(guān)閉SqlSession
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) {
// 關(guān)閉SqlSession
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
在SqlSessionInterceptor中可以看到,代理對象還是先獲取了一個SqlSession對象
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
SqlSessionTemplate作為一個全局單例的Bean,必然在多個Dao層中共同使用,那么不可避免會遇到Servelt容器的多線程環(huán)境所導致的線程安全問題,對此,它通過SqlSessionUtils解決了線程安全的問題
public final class SqlSessionUtils {
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory,
ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
// ...
// SqlSessionTemplate的線程安全性由此保證
// TransactionSynchronizationManager保證了線程安全
// 追根揭底,這個類采用的技術(shù)還是ThreadLocal線程隔離技術(shù)
SqlSessionHolder holder = (SqlSessionHolder)
TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
// ...
// 如果當前線程沒有session,則通過SessionFactory創(chuàng)建
// 這里就又回到了mybatis的DefaultSessionFactory,
// 創(chuàng)建Mybatis原生的DefaultSqlSession
session = sessionFactory.openSession(executorType);
// 注冊上面創(chuàng)建的session對象
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
// 使用SqlSessionHolder封裝SqlSession,保存到ThreadLocal對象中
private static void registerSessionHolder(SqlSessionFactory sessionFactory,
ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
SqlSessionHolder holder;
// ...
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
// 將當前的SqlSession保存到ThreadLocal
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager.registerSynchronization(
new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
// ...
}
}
Spring提供了TransactionSynchronizationManager管理每個線程的資源和事務(wù)的同步狀態(tài),它采用了ThreadLocal線程隔離保證線程安全性
public abstract class TransactionSynchronizationManager {
// mybatis使用它保存當前線程信息
// Key為SqlSessionFactory value為SqlSessionHolder
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
// 獲取當前多線程專屬的SqlSessionHolder
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doGetResource(actualKey);
// ...
return value;
}
// 從ThreadLocal獲取SqlSessionHolder
private static Object doGetResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.get(actualKey);
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
// mybatis應(yīng)用這個方法將SqlSessionHolder保存到ThreadLocal
public static void bindResource(Object key, Object value)
throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Map<Object, Object> map = resources.get();
// set ThreadLocal Map if none found
if (map == null) {
map = new HashMap<>();
// 保存到ThreadLocal
resources.set(map);
}
// ...
}
}
繼續(xù)回到SqlSessionInterceptor,獲取到當前線程專屬的SqlSession之后,通過它處理完畢之后會關(guān)閉當前SqlSession,這也是Mybatis集成Spring之后一級緩存失效的原因
// 通過反射執(zhí)行DefaultSqlSession對象的同名方法
Object result = method.invoke(sqlSession, args);
// 關(guān)閉當前SqlSession
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
關(guān)閉··SqlSesion··會判斷當前這個對象是否被Spring管理,沒有直接關(guān)閉,有則引用減一,交給Spring關(guān)閉
public final class SqlSessionUtils {
public static void closeSqlSession(SqlSession session,
SqlSessionFactory sessionFactory) {
//...
SqlSessionHolder holder = (SqlSessionHolder)
TransactionSynchronizationManager.getResource(sessionFactory);
if ((holder != null) && (holder.getSqlSession() == session)) {
// ...
// SqlSession引用減一
holder.released();
} else {
// ...
session.close(); // 直接關(guān)閉
}
}
// 一個事務(wù)結(jié)束前會判斷當前SqlSession的引用是否為0
// 為0則表示可以回收該SqlSession
public void beforeCompletion() {
// Issue #18 Close SqlSession and deregister it now
// because afterCompletion may be called from a different thread
if (!this.holder.isOpen()) {
// ...
// 從ThreadLocal中移除
TransactionSynchronizationManager.unbindResource(sessionFactory);
this.holderActive = false;
// ...
this.holder.getSqlSession().close();
}
}
}
當SqlSession的引用變?yōu)?,那么就會從ThreadLocal中移除
public abstract class TransactionSynchronizationManager {
// mybatis使用它保存當前線程信息
// Key為SqlSessionFactory value為SqlSessionHolder
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
public static Object unbindResource(Object key)
throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils
.unwrapResourceIfNecessary(key);
Object value = doUnbindResource(actualKey);
// ...
return value;
}
public static Object unbindResourceIfPossible(Object key) {
Object actualKey = TransactionSynchronizationUtils
.unwrapResourceIfNecessary(key);
return doUnbindResource(actualKey);
}
private static Object doUnbindResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
// ...
return value;
}
}