软件编程
位置:首页>> 软件编程>> java编程>> spring 整合mybatis后用不上session缓存的原因分析

spring 整合mybatis后用不上session缓存的原因分析

作者:mrr  发布时间:2021-12-09 10:11:03 

标签:spring,mybatis,session

因为一直用spring整合了mybatis,所以很少用到mybatis的session缓存。 习惯是本地缓存自己用map写或者引入第三方的本地缓存框架ehcache,Guava

所以提出来纠结下

实验下(spring整合mybatis略,网上一堆),先看看mybatis级别的session的缓存

放出打印sql语句

configuration.xml 加入


<settings>
   <!-- 打印查询语句 -->
   <setting name="logImpl" value="STDOUT_LOGGING" />
 </settings>

测试源代码如下:

dao类


/**
* 测试spring里的mybatis为啥用不上缓存
*
* @author 何锦彬 2017.02.15
*/
@Component
public class TestDao {
 private Logger logger = Logger.getLogger(TestDao.class.getName());
 @Autowired
 private SqlSessionTemplate sqlSessionTemplate;
 @Autowired
 private SqlSessionFactory sqlSessionFactory;
 /**
  * 两次SQL
  *
  * @param id
  * @return
  */
 public TestDto selectBySpring(String id) {
   TestDto testDto = (TestDto) sqlSessionTemplate.selectOne("com.hejb.TestDto.selectByPrimaryKey", id);
   testDto = (TestDto) sqlSessionTemplate.selectOne("com.hejb.TestDto.selectByPrimaryKey", id);
   return testDto;
 }
 /**
  * 一次SQL
  *
  * @param id
  * @return
  */
 public TestDto selectByMybatis(String id) {
   SqlSession session = sqlSessionFactory.openSession();
   TestDto testDto = session.selectOne("com.hejb.TestDto.selectByPrimaryKey", id);
   testDto = session.selectOne("com.hejb.TestDto.selectByPrimaryKey", id);
   return testDto;
 }
}

测试service类


@Component
public class TestService {
 @Autowired
 private TestDao testDao;
 /**
  * 未开启事务的spring Mybatis查询
  */
 public void testSpringCashe() {
   //查询了两次SQL
   testDao.selectBySpring("1");
 }
 /**
  * 开启事务的spring Mybatis查询
  */
 @Transactional
 public void testSpringCasheWithTran() {
   //spring开启事务后,查询1次SQL
   testDao.selectBySpring("1");
 }
 /**
  * mybatis查询
  */
 public void testCash4Mybatise() {
   //原生态mybatis,查询了1次SQL
   testDao.selectByMybatis("1");
 }
}

输出结果:

testSpringCashe()方法执行了两次SQL, 其它都是一次

源码追踪:

先看mybatis里的sqlSession

跟踪到最后 调用到 org.apache.ibatis.executor.BaseExecutor的query方法


try {
  queryStack++;
  list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; //先从缓存中取
  if (list != null) {
   handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); //注意里面的key是CacheKey
  } else {
   list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }

贴下是怎么取出缓存数据的代码


private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
 if (ms.getStatementType() == StatementType.CALLABLE) {
  final Object cachedParameter = localOutputParameterCache.getObject(key);//从localOutputParameterCache取出缓存对象
  if (cachedParameter != null && parameter != null) {
   final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
   final MetaObject metaParameter = configuration.newMetaObject(parameter);
   for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
    if (parameterMapping.getMode() != ParameterMode.IN) {
     final String parameterName = parameterMapping.getProperty();
     final Object cachedValue = metaCachedParameter.getValue(parameterName);
     metaParameter.setValue(parameterName, cachedValue);
    }
   }
  }
 }
}

发现就是从localOutputParameterCache就是一个PerpetualCache, PerpetualCache维护了个map,就是session的缓存本质了。

重点可以关注下面两个累的逻辑

PerpetualCache , 两个参数, id和map

CacheKey,map中存的key,它有覆盖equas方法,当获取缓存时调用.

这种本地map缓存获取对象的缺点,就我踩坑经验(以前我也用map去实现的本地缓存),就是获取的对象非clone的,返回的两个对象都是一个地址

而在spring中一般都是用sqlSessionTemplate,如下


<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
   <property name="dataSource" ref="dataSource" />
   <property name="configLocation" value="classpath:configuration.xml" />
   <property name="mapperLocations">
     <list>
       <value>classpath*:com/hejb/sqlmap/*.xml</value>
     </list>
   </property>
 </bean>
 <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
   <constructor-arg ref="sqlSessionFactory" />
 </bean>

在SqlSessionTemplate中执行SQL的session都是通过sqlSessionProxy来,sqlSessionProxy的生成在构造函数中赋值,如下:


this.sqlSessionProxy = (SqlSession) newProxyInstance(
   SqlSessionFactory.class.getClassLoader(),
   new Class[] { SqlSession.class },
   new SqlSessionInterceptor());

sqlSessionProxy通过JDK的 * 方法生成的一个代理类,主要逻辑在InvocationHandler对执行的方法进行了前后拦截,主要逻辑在invoke中,包好了每次执行对sqlsesstion的创建,common,关闭

代码如下:


private class SqlSessionInterceptor implements InvocationHandler {
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  // 每次执行前都创建一个新的sqlSession
  SqlSession sqlSession = getSqlSession(
    SqlSessionTemplate.this.sqlSessionFactory,
    SqlSessionTemplate.this.executorType,
    SqlSessionTemplate.this.exceptionTranslator);
  try {
  // 执行方法
   Object result = method.invoke(sqlSession, args);
   if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
    // force commit even on non-dirty sessions because some databases require
    // a commit/rollback before calling close()
    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 {
   if (sqlSession != null) {
    closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
   }
  }
 }
}

因为每次都进行创建,所以就用不上sqlSession的缓存了.

对于开启了事务为什么可以用上呢, 跟入getSqlSession方法

如下:


public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
 notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
 notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
 SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
 // 首先从SqlSessionHolder里取出session
 SqlSession session = sessionHolder(executorType, holder);
 if (session != null) {
  return session;
 }
 if (LOGGER.isDebugEnabled()) {
  LOGGER.debug("Creating a new SqlSession");
 }
 session = sessionFactory.openSession(executorType);
 registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
 return session;
}

在里面维护了个SqlSessionHolder,关联了事务与session,如果存在则直接取出,否则则新建个session,所以在有事务的里,每个session都是同一个,故能用上缓存了

以上所述是小编给大家介绍的spring 整合mybatis后用不上session缓存的原因分析网站的支持!

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com