解決spring結合mybatis時一級快取失效的問題
之前瞭解到mybatis的一級快取是預設開啟的,作用域是sqlSession,是基 HashMap的本地快取。不同的SqlSession之間的快取資料區域互不影響。
當進行select、update、delete操作後並且commit事物到資料庫之後,sqlSession中的Cache自動被清空
<setting name="localCacheScope" value="SESSION"/>
結論
spring結合mybatis後,一級快取作用:
在未開啟事物的情況之下,每次查詢,spring都會關閉舊的sqlSession而建立新的sqlSession,因此此時的一級快取是沒有啟作用的
在開啟事物的情況之下,spring使用threadLocal獲取當前資源繫結同一個sqlSession,因此此時一級快取是有效的
案例
情景一:未開啟事物
@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(); }
結果:
[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,"lastUpdate":"2006-02-15 04:44:00.0"}
可以看到,兩次查詢,都建立了新的sqlSession,並向資料庫查詢,此時快取並沒有起效果
情景二: 開啟事物
開啟@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); } }
使用原來的測試案例,輸出結果:
[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] // 從當前事物中獲取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,"lastUpdate":"2006-02-15 04:44:00.0"}
可以看到,兩次查詢,只建立了一次sqlSession,說明一級快取起作用了
跟蹤原始碼
從SqlSessionDaoSupport作為路口,這個類在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 }
建立了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的內部類:
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() // 手動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 { //一般情況下,預設都是關閉sqlSession if (sqlSession != null) { closeSqlSession(sqlSession,SqlSessionTemplate.this.sqlSessionFactory); } } } } }
再看getSqlSession方法,這個方法是在SqlSessionUtils.java中的:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory,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不存在,則建立一個新的 session = sessionFactory.openSession(executorType); //將sqlSession註冊在sessionHolder中 registerSessionHolder(sessionFactory,executorType,exceptionTranslator,session); return session; } private static void registerSessionHolder(SqlSessionFactory sessionFactory,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,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中獲取與當前執行緒繫結的資源,如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是如何做到獲取到的是同一個SqlSession,前面的長篇大論,就是為使用ThreadLocal將當前執行緒繫結建立SqlSession相關的資源,從而獲取同一個sqlSession
以上這篇解決spring結合mybatis時一級快取失效的問題就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。