因为一直用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. 路由器、交换机学习之IP地址、使用网络掩码划分子网

    局域网子网划分 对于C类IP地址来说(192.168.1.X,其中前面的192.168.1为网络号,后面的X为主机号,这样的网络中可以有254台主机,其中.0为局域网地址,.255为广播地址)进行子网 ...

  2. html 上传文件

    1.html代码 <form id="form1" action="TestYield" method="post" enctype= ...

  3. web前端面试第三波~

    快来测试测试自己掌握能力吧! 1. class.forname的作用?为什么要用? 1).获取Class对象的方式:类名.class.对象.getClass().Class.forName(" ...

  4. 【动态规划】Gym - 101102A - Coins

    Hasan and Bahosain want to buy a new video game, they want to share the expenses. Hasan has a set of ...

  5. App外包开发周期一般多长?

    很多人问我,开发一个app要用多长时间.事实上开发一款app没有固定周期的,得因产品而论,你软件的功能需求决定了app外包开发的周期.但是除了app本身以外,人为因素往往对开发周期也有一定的影响.例如 ...

  6. UVa 507 - Jill Rides Again

    题目大意:最大和子序列问题.由于具有最大和的子序列具有一下性质:第一项不为负数,并且从第一项开始累加,中间不会有和出现负数,因为一旦有负数我们可以抛弃前边的部分以得到更大的子序列和,这将会产生矛盾. ...

  7. 创业类网站建设日志1——搭建服务器svn以及前端开发环境

    1.需要在linux环境的服务器下搭建node和npm还有Grunt,所以先需要一个叫putty的工具连接服务器命令行终端 2.双击putty工具,在HostName一栏输入项目服务器地址:172.1 ...

  8. iOS 之 static

    函数体内的static变量的作用范围是该函数体,在内存中只分配一次,下次调用时仍维持上次的值. 模块内的static全局变量可以被模块内的所有函数访问,但是不能被模块外的其它函数访问. 模块内的sta ...

  9. ES6 - 变量的解构赋值学习笔记

    变量的解析赋值 数组的解析赋值 es6允许按照一定的模式,从数组和对象中提取值,对变量进行赋值,这就称为解构. var a = 1; var b = 2; var c = 3; //在es6中允许写成 ...

  10. enum 用法

    public enum WeekDay { SUN(, "Sunday", "SUN"), MON(, "Monday", "MO ...