spring整合mybatis後,mybatis一級快取失效的原因
一般來說,可以在5個方面進行快取的設計:
1.最底層可以配置的是資料庫自帶的query cache,
2.mybatis的一級快取,預設情況下都處於開啟狀態,只能使用自帶的PerpetualCache,無法配置第三方快取
3.mybatis的二級快取,可以配置開關狀態,預設使用自帶的PerpetualCache,但功能比較弱,能夠配置第三方快取,
4.service層的快取配置,結合spring,可以靈活進行選擇
5.針對實際業務情況,直接快取部分html頁面,直接返回給客戶端。
在測試過程中,發現mybatis的一級快取沒有起作用,失效了。經過調研,發現是由於以下原因引起的:
1.mybatis的一級快取生效的範圍是sqlsession,是為了在sqlsession沒有關閉時,業務需要重複查詢相同資料使用的。一旦sqlsession關閉,則由這個sqlsession快取的資料將會被清空。
2.spring對mybatis的sqlsession的使用是由template控制的,sqlSessionTemplate又被spring當作resource放在當前執行緒的上下文裡(threadlocal),spring通過mybatis呼叫資料庫的過程如下:
a,我們需要訪問資料
b,spring檢查到了這種需求,於是去申請一個mybatis的sqlsession(資源池),並將申請到的sqlsession與當前執行緒繫結,放入threadlocal裡面
c,sqlSessionTemplate從threadlocal獲取到sqlsession,去執行查詢
d,查詢結束,清空threadlocal中與當前執行緒繫結的sqlsession,釋放資源
e,我們又需要訪問資料
f,返回到步驟b
通過以上步驟後發現,同一執行緒裡面兩次查詢同一資料所使用的sqlsession是不相同的,所以,給人的印象就是結合spring後,mybatis的一級快取失效了。
而在spring中一般都是用sqlSessionTemplate,如下
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:configuration.xml" /> <property name="mapperLocations"> <list> <value>classpath*:com/hejb/sqlmap/*.xml</value> </list> </property> </bean> <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg ref="sqlSessionFactory" /> </bean>
在SqlSessionTemplate中執行SQL的session都是通過sqlSessionProxy來,sqlSessionProxy的生成在建構函式中賦值,如下:
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
sqlSessionProxy通過JDK的動態代理方法生成的一個代理類,主要邏輯在InvocationHandler對執行的方法進行了前後攔截,主要邏輯在invoke中,包好了每次執行對sqlsesstion的建立,commit,關閉
程式碼如下:
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 {
// 執行方法
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
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);
}
}
}
}
因為每次都進行建立,所以就用不上sqlSession的快取了.
對於開啟了事務為什麼可以用上呢, 跟入getSqlSession方法
如下:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// 首先從SqlSessionHolder裡取出session
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;
}
在裡面維護了個SqlSessionHolder,關聯了事務與session,如果存在則直接取出,否則則新建個session,所以在有事務的裡,每個session都是同一個,故能用上快取了