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

 更新时间:2017年02月16日 10:50:23   投稿:mrr  
因为一直用spring整合了mybatis,所以很少用到mybatis的session缓存。什么原因呢?下面小编给大家介绍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缓存的原因分析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

相关文章

  • Springboot整合Jedis实现单机版或哨兵版可切换配置方法

    Springboot整合Jedis实现单机版或哨兵版可切换配置方法

    这篇文章主要介绍了Springboot整合Jedis实现单机版或哨兵版可切换配置方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-11-11
  • java项目如何引入其他jar包

    java项目如何引入其他jar包

    通常在lib文件夹中存放从外部引入的jar包,所以把JAR文件复制进去。 然后修改编译脚本,不需要去编译tool文件夹里面的java类,直接把jar包添加到classpath,下文将详细介绍
    2021-10-10
  • mybatis mybatis-plus-generator+clickhouse自动生成代码案例详解

    mybatis mybatis-plus-generator+clickhouse自动生成代码案例详解

    这篇文章主要介绍了mybatis mybatis-plus-generator+clickhouse自动生成代码案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • Springboot 1.5.7整合Kafka-client代码示例

    Springboot 1.5.7整合Kafka-client代码示例

    这篇文章主要介绍了Springboot 1.5.7整合Kafka-client代码示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • Java中的权重算法(如Dubbo的负载均衡权重)详解

    Java中的权重算法(如Dubbo的负载均衡权重)详解

    这篇文章主要介绍了Java中的权重算法(如Dubbo的负载均衡权重)详解,负载均衡,其含义就是指将负载进行平衡、分摊到多个操作单元上进行运行,例如FTP服务器、Web服务器、企业核心应用服务器和其它主要任务服务器等,从而协同完成工作任务,需要的朋友可以参考下
    2023-08-08
  • Java反射机制基础详解

    Java反射机制基础详解

    这篇文章主要介绍了JAVA 反射机制的相关知识,文中讲解的非常细致,代码帮助大家更好的理解学习,感兴趣的朋友可以了解下,希望能给你带来帮助
    2021-08-08
  • SpringBoot中GlobalExceptionHandler异常处理机制详细说明

    SpringBoot中GlobalExceptionHandler异常处理机制详细说明

    Spring Boot的GlobalExceptionHandler是一个全局异常处理器,用于捕获和处理应用程序中发生的所有异常,这篇文章主要给大家介绍了关于Java中GlobalExceptionHandler异常处理机制的相关资料,需要的朋友可以参考下
    2024-03-03
  • Myeclipse清理项目缓存的几大方法

    Myeclipse清理项目缓存的几大方法

    今天小编就为大家分享一篇关于Myeclipse清理项目缓存的几大方法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12
  • 使用Java进行图像处理的一些基础操作

    使用Java进行图像处理的一些基础操作

    这篇文章主要介绍了使用Java进行图像处理的一些基础操作,就载入和输出相关的知识进行了讲解,需要的朋友可以参考下
    2015-10-10
  • Java中两种基本的输入方式小结

    Java中两种基本的输入方式小结

    这篇文章主要介绍了Java中两种基本的输入方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-05-05

最新评论