因为一直用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都是同一个,故能用上缓存了

留下个下小思考

,CacheKey是怎么作为KEY来判断是否执行的是同一条SQL与参数的呢

分析下为什么spring 整合mybatis后为啥用不上session缓存的更多相关文章

  1. 2017年2月16日 分析下为什么spring 整合mybatis后为啥用不上session缓存

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

  2. spring整合mybatis后,mybatis一级缓存失效的原因

    一般来说,可以在5个方面进行缓存的设计: 最底层可以配置的是数据库自带的query cache, mybatis的一级缓存,默认情况下都处于开启状态,只能使用自带的PerpetualCache,无法配 ...

  3. spring基础:什么是框架,框架优势,spring优势,耦合内聚,什么是Ioc,IOC配置,set注入,第三方资源配置,综合案例spring整合mybatis实现

    知识点梳理 课堂讲义 1)Spring简介 1.1)什么是框架 源自于建筑学,隶属土木工程,后发展到软件工程领域 软件工程中框架的特点: 经过验证 具有一定功能 半成品 1.2)框架的优势 提高开发效 ...

  4. Java开发学习(十四)----Spring整合Mybatis及Junit

    一.Spring整合Mybatis思路分析 1.1 环境准备 步骤1:准备数据库表 Mybatis是来操作数据库表,所以先创建一个数据库及表 create database spring_db cha ...

  5. Spring学习总结(六)——Spring整合MyBatis完整示例

    为了梳理前面学习的内容<Spring整合MyBatis(Maven+MySQL)一>与<Spring整合MyBatis(Maven+MySQL)二>,做一个完整的示例完成一个简 ...

  6. Spring学习总结(五)——Spring整合MyBatis(Maven+MySQL)二

    接着上一篇博客<Spring整合MyBatis(Maven+MySQL)一>继续. Spring的开放性和扩张性在J2EE应用领域得到了充分的证明,与其他优秀框架无缝的集成是Spring最 ...

  7. spring整合mybatis(hibernate)配置

    一.Spring整合配置Mybatis spring整合mybatis可以不需要mybatis-config.xml配置文件,直接通过spring配置文件一步到位.一般需要具备如下几个基本配置. 1. ...

  8. 简单探讨spring整合mybatis时sqlSession不需要释放关闭的问题

    https://blog.csdn.net/RicardoDing/article/details/79899686 近期,在使用spring和mybatis框架编写代码时,sqlSession不需要 ...

  9. Spring整合MyBatis(三)sqlSessionFactory创建

    摘要: 本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 目录 一.SqlSessionFactoryBean的初始化 二.获取 ...

随机推荐

  1. C socket udp方式发数据

    #define HOST_SERVER_IP "192.168.3.35" #define HOST_PORT 9501 #define SLEEP_TIME 1 #define ...

  2. AnsiString用法(转)

    源:AnsiString用法 //Ansistring 转 char void __fastcall TForm1::Button1Click(TObject *Sender) { AnsiStrin ...

  3. LPC1788 nand驱动

    Lpc 1788自带有emc接口用于驱动nandflash,norflash,sdram设备,对于nandflash驱动因为配置简单,时序也简单 首先,针对nandflash而言应当在系统中有三个地址 ...

  4. WebService 使用wsdl.exe生成代理类

    利用wsdl.exe生成webservice代理类: 根据提供的wsdl生成webservice代理类 1.开始->程序->Visual Studio 2005 命令提示 2.输入如下红色 ...

  5. BZOJ2318: Spoj4060 game with probability Problem

    #include<iostream> #include<algorithm> #include<cstring> #include<cstdio> #i ...

  6. 轻轻的扩展了一下IEnumerable<T>

    今天用EF写东西玩,觉得IEnumerable里面除了where().select(),是不是能添加点其他方法呢. 想做就做,F12到方法定义: public static IEnumerable&l ...

  7. RF & Microarray

    REF[24] 随机森林是一个很好适用于微阵列数据的分类算法: 1.即使大多数的预测变量都是噪音,RF仍然具有优秀的性能,因此不需要对基因进行预选择. 2.能够应用于变量数远远大于观测数据量的情况 3 ...

  8. Angular - - angular.forEach、angular.extend

    angular.forEach 调用迭代器函数取每一项目标的集合,它可以是一个对象或数组.迭代器函数与迭代器(value.key)一起调用,其中值是一个对象属性或数组元素的值,而数组元素是对象属性的关 ...

  9. MyBatis中的条件判断单引号双引号的使用

    对于字符串判断, <if test="aIn != 'A'" >会出现问题,系统会试图把'A'转成数字,改为 <if test='aIn != "A&q ...

  10. JSP 初始化参数

    JSP 初始化参数: tomcat启动的时候就会执行那个函数: xml: <?xml version="1.0" encoding="UTF-8"?> ...