溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

使用spring與mybatis時(shí)出現(xiàn)一級(jí)緩存失效如何解決

發(fā)布時(shí)間:2020-11-30 15:53:38 來源:億速云 閱讀:640 作者:Leah 欄目:開發(fā)技術(shù)

這篇文章給大家介紹使用spring與mybatis時(shí)出現(xiàn)一級(jí)緩存失效如何解決,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。

mybatis的一級(jí)緩存是默認(rèn)開啟的,作用域是sqlSession,是基 HashMap的本地緩存。不同的SqlSession之間的緩存數(shù)據(jù)區(qū)域互不影響。

當(dāng)進(jìn)行select、update、delete操作后并且commit事物到數(shù)據(jù)庫之后,sqlSession中的Cache自動(dòng)被清空

<setting name="localCacheScope" value="SESSION"/>

結(jié)論

spring結(jié)合mybatis后,一級(jí)緩存作用:

在未開啟事物的情況之下,每次查詢,spring都會(huì)關(guān)閉舊的sqlSession而創(chuàng)建新的sqlSession,因此此時(shí)的一級(jí)緩存是沒有啟作用的

在開啟事物的情況之下,spring使用threadLocal獲取當(dāng)前資源綁定同一個(gè)sqlSession,因此此時(shí)一級(jí)緩存是有效的

案例

情景一:未開啟事物

@Service("countryService")
public class CountryService {

 @Autowired
 private CountryDao countryDao;

 // @Transactional 未開啟事物
 public void noTranSactionMethod() throws JsonProcessingException {
  CountryDo countryDo = countryDao.getById(1L);
  CountryDo countryDo1 = countryDao.getById(1L);
  ObjectMapper objectMapper = new ObjectMapper();
  String json = objectMapper.writeValueAsString(countryDo);
  String json1 = objectMapper.writeValueAsString(countryDo1);
  System.out.println(json);
  System.out.println(json1);
 }
}

測試案例:

@Test
public void transactionTest() throws JsonProcessingException {
 countryService.noTranSactionMethod();
}

結(jié)果:

[DEBUG] SqlSessionUtils Creating a new SqlSession
[DEBUG] SpringManagedTransaction JDBC Connection [com.mysql.jdbc.JDBC4Connection@14a54ef6] will not be managed by Spring
[DEBUG] getById ==> Preparing: SELECT * FROM country WHERE country_id = ?
[DEBUG] getById ==> Parameters: 1(Long)
[DEBUG] getById <==  Total: 1
[DEBUG] SqlSessionUtils Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3359c978]
[DEBUG] SqlSessionUtils Creating a new SqlSession
[DEBUG] SqlSessionUtils SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2aa27288] was not registered for synchronization because synchronization is not active
[DEBUG] SpringManagedTransaction JDBC Connection [com.mysql.jdbc.JDBC4Connection@14a54ef6] will not be managed by Spring
[DEBUG] getById ==> Preparing: SELECT * FROM country WHERE country_id = ?
[DEBUG] getById ==> Parameters: 1(Long)
[DEBUG] getById <==  Total: 1
[DEBUG] SqlSessionUtils Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2aa27288]
{"countryId":1,"country":"Afghanistan","lastUpdate":"2006-02-15 04:44:00.0"}
{"countryId":1,"country":"Afghanistan","lastUpdate":"2006-02-15 04:44:00.0"}

可以看到,兩次查詢,都創(chuàng)建了新的sqlSession,并向數(shù)據(jù)庫查詢,此時(shí)緩存并沒有起效果

情景二: 開啟事物

打開@Transactional注解:

@Service("countryService")
public class CountryService {

 @Autowired
 private CountryDao countryDao;

 @Transactional
 public void noTranSactionMethod() throws JsonProcessingException {
  CountryDo countryDo = countryDao.getById(1L);
  CountryDo countryDo1 = countryDao.getById(1L);
  ObjectMapper objectMapper = new ObjectMapper();
  String json = objectMapper.writeValueAsString(countryDo);
  String json1 = objectMapper.writeValueAsString(countryDo1);
  System.out.println(json);
  System.out.println(json1);
 }
}

使用原來的測試案例,輸出結(jié)果:

[DEBUG] SqlSessionUtils Creating a new SqlSession
[DEBUG] SqlSessionUtils Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@109f5dd8]
[DEBUG] SpringManagedTransaction JDBC Connection [com.mysql.jdbc.JDBC4Connection@55caeb35] will be managed by Spring
[DEBUG] getById ==> Preparing: SELECT * FROM country WHERE country_id = ?
[DEBUG] getById ==> Parameters: 1(Long)
[DEBUG] getById <==  Total: 1
[DEBUG] SqlSessionUtils Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@109f5dd8]
// 從當(dāng)前事物中獲取sqlSession
[DEBUG] SqlSessionUtils Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@109f5dd8] from current transaction
[DEBUG] SqlSessionUtils Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@109f5dd8]
{"countryId":1,"country":"Afghanistan","lastUpdate":"2006-02-15 04:44:00.0"}
{"countryId":1,"country":"Afghanistan","lastUpdate":"2006-02-15 04:44:00.0"}

可以看到,兩次查詢,只創(chuàng)建了一次sqlSession,說明一級(jí)緩存起作用了

跟蹤源碼

從SqlSessionDaoSupport作為路口,這個(gè)類在mybatis-spring包下,sping為sqlSession做了代理

public abstract class SqlSessionDaoSupport extends DaoSupport {

 private SqlSession sqlSession;

 private boolean externalSqlSession;

 public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
 if (!this.externalSqlSession) {
  this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
 }
 }
 //....omit
}

創(chuàng)建了SqlSessionTemplate后,在SqlSessionTemplate中:

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
 PersistenceExceptionTranslator exceptionTranslator) {

 notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
 notNull(executorType, "Property 'executorType' is required");

 this.sqlSessionFactory = sqlSessionFactory;
 this.executorType = executorType;
 this.exceptionTranslator = exceptionTranslator;
 //代理了SqlSession
 this.sqlSessionProxy = (SqlSession) newProxyInstance(
  SqlSessionFactory.class.getClassLoader(),
  new Class[] { SqlSession.class },
  new SqlSessionInterceptor());
}

再看SqlSessionInterceptor,SqlSessionInterceptor是SqlSessionTemplate的內(nèi)部類:

public class SqlSessionTemplate implements SqlSession, DisposableBean {
 // ...omit..
 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 {
   Object result = method.invoke(sqlSession, args);
   //如果尚未開啟事物(事物不是由spring來管理),則sqlSession直接提交
   if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
   // force commit even on non-dirty sessions because some databases require
   // a commit/rollback before calling close()
   // 手動(dòng)commit
   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 {
   //一般情況下,默認(rèn)都是關(guān)閉sqlSession
   if (sqlSession != null) {
   closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
   }
  }
  }
 }
}

再看getSqlSession方法,這個(gè)方法是在SqlSessionUtils.java中的:

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

 notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
 notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
 //獲取holder
 SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
 //從sessionHolder中獲取SqlSession
 SqlSession session = sessionHolder(executorType, holder);
 if (session != null) {
 return session;
 }

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

 //如果sqlSession不存在,則創(chuàng)建一個(gè)新的
 session = sessionFactory.openSession(executorType);
 //將sqlSession注冊(cè)在sessionHolder中
 registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

 return session;
}

private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
  PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
 SqlSessionHolder holder;
 //在開啟事物的情況下
 if (TransactionSynchronizationManager.isSynchronizationActive()) {
  Environment environment = sessionFactory.getConfiguration().getEnvironment();

  //由spring來管理事物的情況下
  if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
  if (LOGGER.isDebugEnabled()) {
   LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
  }

  holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
  //將sessionFactory綁定在sessionHolde相互綁定
  TransactionSynchronizationManager.bindResource(sessionFactory, holder);
  TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
  holder.setSynchronizedWithTransaction(true);
  holder.requested();
  } else {
  if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
   if (LOGGER.isDebugEnabled()) {
   LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
   }
  } else {
   throw new TransientDataAccessResourceException(
    "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
  }
  }
 } else {
  if (LOGGER.isDebugEnabled()) {
  LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
  }
 }

再看TransactionSynchronizationManager.bindResource的方法:

public abstract class TransactionSynchronizationManager {

 //omit...
 private static final ThreadLocal<Map<Object, Object>> resources =
   new NamedThreadLocal<Map<Object, Object>>("Transactional resources");

  // key:sessionFactory, value:SqlSessionHolder(Connection)
  public static void bindResource(Object key, Object value) throws IllegalStateException {
  Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
  Assert.notNull(value, "Value must not be null");
  //從threadLocal類型的resources中獲取與當(dāng)前線程綁定的資源,如sessionFactory,Connection等等
  Map<Object, Object> map = resources.get();
  // set ThreadLocal Map if none found
  if (map == null) {
   map = new HashMap<Object, Object>();
   resources.set(map);
  }
  Object oldValue = map.put(actualKey, value);
  // Transparently suppress a ResourceHolder that was marked as void...
  if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
   oldValue = null;
  }
  if (oldValue != null) {
   throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
    actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
  }
  if (logger.isTraceEnabled()) {
   logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
    Thread.currentThread().getName() + "]");
  }
  }
}

這里可以看到,spring是如何做到獲取到的是同一個(gè)SqlSession,前面的長篇大論,就是為使用ThreadLocal將當(dāng)前線程綁定創(chuàng)建SqlSession相關(guān)的資源,從而獲取同一個(gè)sqlSession

關(guān)于使用spring與mybatis時(shí)出現(xiàn)一級(jí)緩存失效如何解決就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI