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

发布时间 - 2026-01-10 23:05:47    点击率:

因为一直用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缓存的原因分析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!


# spring  # mybatis  # 整合  # 缓存  # session  # 深入浅出重构Mybatis与Spring集成的SqlSessionFactoryBean(上)  # Spring与Mybatis基于注解整合Redis的方法  # springboot与mybatis整合实例详解(完美融合)  # AngularJS整合Springmvc、Spring、Mybatis搭建开发环境  # 浅析mybatis和spring整合的实现过程  # 都是  # 两次  # 不上  # 小编  # 好了  # 在此  # 是怎么  # 而在  # 是从  # 给大家  # 在里面  # 就用  # 每次都  # 第三方  # 所述  # 创建一个  # 先看  # 给我留言  # 也用  # 就我 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel项目怎么部署到Linux_Laravel Nginx配置详解  哪家制作企业网站好,开办像阿里巴巴那样的网络公司和网站要怎么做?  韩国网站服务器搭建指南:VPS选购、域名解析与DNS配置推荐  Laravel如何部署到服务器_线上部署Laravel项目的完整流程与步骤  EditPlus中的正则表达式 实战(2)  网站建设要注意的标准 促进网站用户好感度!  Laravel怎么使用Blade模板引擎_Laravel模板继承与Component组件复用【手册】  如何在Ubuntu系统下快速搭建WordPress个人网站?  LinuxShell函数封装方法_脚本复用设计思路【教程】  网站制作报价单模板图片,小松挖机官方网站报价?  如何在 Pandas 中基于一列条件计算另一列的分组均值  详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)  如何在IIS管理器中快速创建并配置网站?  如何在 Telegram Web View(iOS)中防止键盘遮挡底部输入框  javascript事件捕获机制【深入分析IE和DOM中的事件模型】  专业型网站制作公司有哪些,我设计专业的,谁给推荐几个设计师兼职类的网站?  Win11怎么更改系统语言为中文_Windows11安装语言包并设为显示语言  Laravel安装步骤详细教程_Laravel环境搭建指南  如何在万网开始建站?分步指南解析  JavaScript模板引擎Template.js使用详解  网站制作软件有哪些,制图软件有哪些?  如何在新浪SAE免费搭建个人博客?  如何在云服务器上快速搭建个人网站?  Python3.6正式版新特性预览  如何在橙子建站中快速调整背景颜色?  Win11怎么关闭专注助手 Win11关闭免打扰模式设置【操作】  深圳网站制作平台,深圳市做网站好的公司有哪些?  Mybatis 中的insertOrUpdate操作  阿里云高弹*务器配置方案|支持分布式架构与多节点部署  香港服务器租用费用高吗?如何避免常见误区?  电商网站制作多少钱一个,电子商务公司的网站制作费用计入什么科目?  Python企业级消息系统教程_KafkaRabbitMQ高并发应用  PHP的CURL方法curl_setopt()函数案例介绍(抓取网页,POST数据)  Laravel如何实现数据导出到PDF_Laravel使用snappy生成网页快照PDF【方案】  WordPress 子目录安装中正确处理脚本路径的完整指南  历史网站制作软件,华为如何找回被删除的网站?  Python自然语言搜索引擎项目教程_倒排索引查询优化案例  企业在线网站设计制作流程,想建设一个属于自己的企业网站,该如何去做?  网页设计与网站制作内容,怎样注册网站?  Windows10电脑怎么设置虚拟光驱_Win10右键装载ISO镜像文件  Laravel如何处理异常和错误?(Handler示例)  Laravel表单请求验证类怎么用_Laravel Form Request分离验证逻辑教程  如何在阿里云虚拟服务器快速搭建网站?  如何在阿里云购买域名并搭建网站?  北京企业网站设计制作公司,北京铁路集团官方网站?  Laravel如何安装Breeze扩展包_Laravel用户注册登录功能快速实现【流程】  微信小程序 闭包写法详细介绍  大连企业网站制作公司,大连2025企业社保缴费网上缴费流程?  如何续费美橙建站之星域名及服务?  如何在万网主机上快速搭建网站?