博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
分析下为什么spring 整合mybatis后为啥用不上session缓存
阅读量:6620 次
发布时间:2019-06-25

本文共 6729 字,大约阅读时间需要 22 分钟。

hot3.png

看了@的 Mybatis介绍之缓存

中间有提到

需要注意的是当Mybatis整合Spring后,直接通过Spring注入Mapper的形式,如果不是在同一个事务中每个Mapper的每次查询操作都对应一个全新的SqlSession实例

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

 

所以提出来纠结下

实验下(spring整合mybatis略,网上一堆),先看看mybatis级别的session的缓存

 

放出打印sql语句

configuration.xml 加入

 

测试源代码如下:

dao类

/** * 测试spring里的mybatis为啥用不上缓存 *  * @author 何锦彬 2017.02.15 */@Componentpublic 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类

@Componentpublic 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
) 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,如下

classpath*:com/hejb/sqlmap/*.xml

 

在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与参数的呢

 

转载于:https://my.oschina.net/u/867417/blog/839753

你可能感兴趣的文章
Scala简介和安装
查看>>
使用canvas绘制图像实现平铺效果
查看>>
你真的会用ABAP, Java和JavaScript里的constructor么?
查看>>
推荐一个有趣的Chrome扩展程序-查看任意网站的开发技术栈
查看>>
Apache默认虚拟主机
查看>>
商城系统针对开发者自有支付系统提供的解决方案
查看>>
mysql load data 批量导入使用
查看>>
MongoDB负载信息一目了然 阿里云HDM重磅发布MongoDB监控和诊断功能
查看>>
在guns框架和SpringBoot框架中展现视频列表遇到的坑
查看>>
108.构建maven项目
查看>>
【Stimulsoft购买指南】授权和订阅常见问题
查看>>
TeeChart Pro VCL/FMX教程(二):图表显示属性(General和Axis篇)
查看>>
最近面试java后端开发的感受:如果就以平时项目经验来面试,通过估计很难——再论面试前的准备...
查看>>
Qt之布局:改变控件位置关系QVBoxLayout
查看>>
K近邻算法的Python实现
查看>>
今天的学习
查看>>
程序员请不要假装很努力,因为结果不会陪你演戏
查看>>
“零确认”有望助力BCH得到广泛应用
查看>>
Navicat使用教程:接收MySQL/MariaDB问题通知
查看>>
How to write elegant code?
查看>>