1. 程式人生 > >spring整合mybatis後,mybatis一級快取失效的原因

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都是同一個,故能用上快取了