解決spring結(jié)合mybatis時(shí)一級(jí)緩存失效的問(wèn)題
之前了解到mybatis的一級(jí)緩存是默認(rèn)開(kāi)啟的,作用域是sqlSession,是基 HashMap的本地緩存。不同的SqlSession之間的緩存數(shù)據(jù)區(qū)域互不影響。
當(dāng)進(jìn)行select、update、delete操作后并且commit事物到數(shù)據(jù)庫(kù)之后,sqlSession中的Cache自動(dòng)被清空
<setting name='localCacheScope' value='SESSION'/>
結(jié)論
spring結(jié)合mybatis后,一級(jí)緩存作用:
在未開(kāi)啟事物的情況之下,每次查詢(xún),spring都會(huì)關(guān)閉舊的sqlSession而創(chuàng)建新的sqlSession,因此此時(shí)的一級(jí)緩存是沒(méi)有啟作用的
在開(kāi)啟事物的情況之下,spring使用threadLocal獲取當(dāng)前資源綁定同一個(gè)sqlSession,因此此時(shí)一級(jí)緩存是有效的
案例
情景一:未開(kāi)啟事物
@Service('countryService')public class CountryService { @Autowired private CountryDao countryDao; // @Transactional 未開(kāi)啟事物 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); }}
測(cè)試案例:
@Testpublic 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'}
可以看到,兩次查詢(xún),都創(chuàng)建了新的sqlSession,并向數(shù)據(jù)庫(kù)查詢(xún),此時(shí)緩存并沒(méi)有起效果
情景二: 開(kāi)啟事物
打開(kāi)@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); }}
使用原來(lái)的測(cè)試案例,輸出結(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'}
可以看到,兩次查詢(xún),只創(chuàng)建了一次sqlSession,說(shuō)明一級(jí)緩存起作用了
跟蹤源碼
從SqlSessionDaoSupport作為路口,這個(gè)類(lèi)在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)部類(lè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); //如果尚未開(kāi)啟事物(事物不是由spring來(lái)管理),則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; //在開(kāi)啟事物的情況下 if (TransactionSynchronizationManager.isSynchronizationActive()) { Environment environment = sessionFactory.getConfiguration().getEnvironment(); //由spring來(lái)管理事物的情況下 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類(lèi)型的resources中獲取與當(dāng)前線(xiàn)程綁定的資源,如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,前面的長(zhǎng)篇大論,就是為使用ThreadLocal將當(dāng)前線(xiàn)程綁定創(chuàng)建SqlSession相關(guān)的資源,從而獲取同一個(gè)sqlSession
以上這篇解決spring結(jié)合mybatis時(shí)一級(jí)緩存失效的問(wèn)題就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持好吧啦網(wǎng)。
相關(guān)文章:
1. python爬蟲(chóng)實(shí)戰(zhàn)之制作屬于自己的一個(gè)IP代理模塊2. Ajax返回值類(lèi)型與用法實(shí)例分析3. 如何在jsp界面中插入圖片4. 詳解盒子端CSS動(dòng)畫(huà)性能提升5. 使用FormData進(jìn)行Ajax請(qǐng)求上傳文件的實(shí)例代碼6. HTML 絕對(duì)路徑與相對(duì)路徑概念詳細(xì)7. asp批量添加修改刪除操作示例代碼8. .NET6打包部署到Windows Service的全過(guò)程9. 解決ajax請(qǐng)求后臺(tái),有時(shí)收不到返回值的問(wèn)題10. css代碼優(yōu)化的12個(gè)技巧
