解決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è)試案例:
@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"}
可以看到,兩次查詢(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è)參考,也希望大家多多支持腳本之家。
相關(guān)文章
關(guān)于阿里巴巴TransmittableThreadLocal使用解讀
文章主要介紹了三種ThreadLocal的使用:ThreadLocal、InheritableThreadLocal和TransmittableThreadLocal,ThreadLocal和InheritableThreadLocal在單線(xiàn)程和部分情況下可以正常工作,但TransmittableThreadLocal在處理線(xiàn)程池時(shí)表現(xiàn)更佳2025-02-02
mybatis如何使用注解實(shí)現(xiàn)一對(duì)多關(guān)聯(lián)查詢(xún)
這篇文章主要介紹了mybatis如何使用注解實(shí)現(xiàn)一對(duì)多關(guān)聯(lián)查詢(xún)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
關(guān)于JAVA中stream流的基礎(chǔ)處理(獲取對(duì)象字段和對(duì)象批量處理等)
這篇文章主要介紹了關(guān)于JAVA中stream流的基礎(chǔ)處理,包含獲取對(duì)象字段、按字段排序、按字段去重、對(duì)象批量處理、指定字段轉(zhuǎn)數(shù)組等內(nèi)容,需要的朋友可以參考下2023-03-03
Spring?AOP實(shí)現(xiàn)多數(shù)據(jù)源動(dòng)態(tài)切換
本文主要介紹了Spring?AOP實(shí)現(xiàn)多數(shù)據(jù)源動(dòng)態(tài)切換,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
SpringDataJpa多表操作的實(shí)現(xiàn)
開(kāi)發(fā)過(guò)程中會(huì)有很多多表的操作,他們之間有著各種關(guān)系,本文主要介紹了SpringDataJpa多表操作的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
使用ScheduledThreadPoolExecutor踩過(guò)最痛的坑
這篇文章主要介紹了使用ScheduledThreadPoolExecutor踩過(guò)最痛的坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08
Java中ThreadLocal線(xiàn)程變量的實(shí)現(xiàn)原理
本文主要介紹了Java中ThreadLocal線(xiàn)程變量的實(shí)現(xiàn)原理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06

