解决spring结合mybatis时一级缓存失效的问题

网友投稿 317 2023-02-17

解决spring结合mybatis时一级缓存失效的问题

之前了解到mybatis的一级缓存是默认开启的,作用域是sqlSession,是基 HashMap的本地缓存。不同的SqlSession之间的缓存数据区域互不影响。

当进行select、update、delete操作后并且commit事物到数据库之后,sqlSession中的Cache自动被清空

结论

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 {uWewWzQDa

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,"country":"Afghanistan","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 synchronuWewWzQDaization 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,"country":"Afghanistan","lastUpdate":"2006-02-15 04:44:00.0"}

{"countryId":1,"country":"Afghanistan","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, 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不存在,则创建一个新的

session = sessionFactory.openSession(executorType);

//将sqlSession注册在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> resources =

new NamedThreadLocal>("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 map = resources.get();

// set ThreadLocal Map if none found

if (map == null) {

map = new HashMap();

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

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:购买机票api接口平台(机票订购平台)
下一篇:直播平台开发大数据(网络直播平台大数据)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~