分析下为什么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都是同一个,故能用上缓存了

留下个下小思考

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

时间: 2024-08-08 13:39:08

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

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

这些天由于项目存在数据访问的性能问题,研究了下缓存在各个阶段的应用,一般来说,可以在5个方面进行缓存的设计: 1.最底层可以配置的是mysql自带的query cache, 2.mybatis的一级缓存,默认情况下都处于开启状态,只能使用自带的PerpetualCache,无法配置第三方缓存 3.mybatis的二级缓存,可以配置开关状态,默认使用自带的PerpetualCache,但功能比较弱,能够配置第三方缓存, 4.service层的缓存配置,结合spring,可以灵活进行选择 5.针对实

spring源码剖析(八)spring整合mybatis原理

前言 MyBatis相信很多人都会使用,但是当MyBatis整合到了Spring中,我们发现在Spring中使用更加方便了.例如获取Dao的实例,在Spring的我们只需要使用注入的方式就可以了使用Dao了,完全不需要调用SqlSession的getMapper方法去获取Dao的实例,更不需要我们去管理SqlSessionFactory,也不需要去创建SqlSession之类的了,对于插入操作也不需要我们commit. 既然那么方便,Spring到底为我们做了哪些工作呢,它如何将MyBatis整

Spring整合MyBatis完整示例

为了梳理前面学习的内容<Spring整合MyBatis(Maven+MySQL)一>与<Spring整合MyBatis(Maven+MySQL)二>,做一个完整的示例完成一个简单的图书管理功能,主要使用到的技术包含Spring.MyBatis.Maven.MySQL及简单MVC等.最后的运行效果如下所示: 项目结构如下: 一.新建一个基于Maven的Web项目 1.1.创建一个简单的Maven项目,项目信息如下: 1.2.修改层面信息,在项目上右键选择属性,再选择“Project

spring整合mybatis(hibernate)配置

一.Spring整合配置Mybatis spring整合mybatis可以不需要mybatis-config.xml配置文件,直接通过spring配置文件一步到位.一般需要具备如下几个基本配置. 1.配置数据源(连接数据库最基本的属性配置,如数据库url,账号,密码,和数据库驱动等最基本参数配置) 1 <!-- 导入properties配置文件 --> 2 <context:property-placeholder location="classpath*:/jdbc.prop

Spring整合MyBatis(一)MyBatis独立使用

摘要: 本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. MyBatis本是Apache的一个开源项目iBatis,2010年这个项目由Apache Software Foundation迁移到了Google Code,并且改名为MyBatis. MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索.MyBatis使用简单的XML或注解用于配

Spring整合MyBatis(三)sqlSessionFactory创建

摘要: 本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 目录 一.SqlSessionFactoryBean的初始化 二.获取 SqlSessionFactoryBean 实例 通过Spring整合MyBatis的示例,我们感受到了Spring为用户更加快捷地进行开发所做的努力,开发人员的工作效率由此得到了显著的提升.但是,相对于使用来说,我们更想知道其背后所隐藏的秘密,Spring整合MyBatis是如何实现的呢?通过分析整合示例

Spring整合Mybatis的注意事项

初学 Spring 整合 Mybatis,虽然网贴无数,但是每次试行下来,总会发生这样那样的问题.最终经过数天的不断尝试,总算是成功运行了,遇到的多数坑也一一绕过,特此记录已备查: 一.关于依赖包 网上的很多帖子杂七杂八加入了各种依赖包,有时看的人头晕脑胀,经过实测,实际需要的依赖包,只有下面三组: <!-- 1.基础Spring依赖 --> <dependency> <groupId>org.springframework</groupId> <ar

spring整合mybatis遇到的bug java.lang.IllegalArgumentException: Property &#39;sqlSessionFactory&#39; or &#39;sqlSessionTemplate&#39; are required

出bug的原因:mybatis-spring版本问题. 查看SqlSessionDaoSupport源码 1.2以上的版本: 1.1.1版本: 解决方法:1.2版本移除了@Autowired的注解,所以如果是1.2版本以上,要在BaseDaoImpl里面手动 注入SetSessionTemplate或者SetSessionFactory spring整合mybatis遇到的bug java.lang.IllegalArgumentException: Property 'sqlSessionFa

Spring整合Mybatis解决 Property &#39;sqlSessionFactory&#39; or &#39;sqlSessionTemplate&#39; are required

在Spring4和Mybatis3整合的时候,dao层注入'sqlSessionFactory'或'sqlSessionTemplate'会报错解决办法如下: package com.alibaba.webx.MyWebxTest.myWebX.module.dao.impl; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionTemplate; import org.m