因为一直用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与参数的呢

2017年2月16日 分析下为什么spring 整合mybatis后为啥用不上session缓存的更多相关文章

  1. 分析下为什么spring 整合mybatis后为啥用不上session缓存

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

  2. 2017年4月16日 一周AnswerOpenCV佳作赏析

    2017年4月16日 一周AnswerOpenCV佳作赏析 1.HelloHow to smooth edge of text in binary image, based on threshold. ...

  3. 2017年12月16日 ASP.NET基本用法

    ASP.NET初级添加 利用css代码跟ASP.NET还有Javascript原生,LinQ来写增跟展示数据 首先介绍一个非常好用的控件,灵活并且循环展示数据库里面的数据 <asp:Repeat ...

  4. outlook 通讯录分类--2017年1月16日--对联系人分类管理

    outlook功能多,复杂,导致打开界面就晕,通讯录分类 问:在Outlook 中,随着联系人数量的增多,亲朋好友.同事.客户的信息混杂在一起,每次发邮件都要用很长时间才能从联系人列表中找到需要的人. ...

  5. 2017年2月16日-----------乱码新手自学.net 之MVC模型

    第二篇博文,最近学习的内容还是回到了正题:ASP.NET MVC5之上.虽然EF学了个一知半解,但是用这点知识,看MVC5的MODEL部分应该还是够了.尽管周末还要恶补一下EF才行. (一)MVC简述 ...

  6. [转载]Ubuntu17.04(Zesty Zapus)路线图发布:2017年4月13日发布

    Canonical今天公布了Ubuntu 17.04(Zesty Zapus)操作系统的发布路线图,该版本于今年10月24日上线启动,toolchain已经上传且首个daily ISO镜像已经生成.面 ...

  7. 19.go语言基础学习(下)——2019年12月16日

    2019年12月16日16:57:04 5.接口 2019年11月01日15:56:09 5.1 duck typing 1. 2. 接口 3.介绍 Go 语言的接口设计是非侵入式的,接口编写者无须知 ...

  8. 【2017年9月10日更新】ABP配套代码生成器(ABP Code Generator)帮助文档,实现快速开发

    ABP代码生成器介绍 ABP Code Generator 针对abp这个框架做了一个代码生成器,功能强大.分为两大功能点,一个是数据层,一个是视图层. 数据服务层:通过它,可以实现表设计.领域层初始 ...

  9. 猖獗的假新闻:2017年1月1日起iOS的APP必须使用HTTPS

    一.假新闻如此猖獗 刚才一位老同事 打电话问:我们公司还是用的HTTP,马上就到2017年了,提交AppStore会被拒绝,怎么办? 公司里已经有很多人问过这个问题,回答一下: HTTP还是可以正常提 ...

随机推荐

  1. mysql show命令

    MySQL中有很多的基本命令,show命令也是其中之一,在很多使用者中对show命令的使用还容易产生混淆,本文汇集了show命令的众多用法. 1. show tables或show tables fr ...

  2. Photoshop 画基本图形

    作者:zccst 1,画三角形 按下“U”选择“多边形工具”(是个六边形的图标),在上方的菜单栏中的“边”后面输入“3”,然后在画布中拖动就是三角形了,画好后按下CTRL+ENTER键转化成选区,然后 ...

  3. USB入门基础知识(转)

    源:USB入门基础知识 相关名词: 主机(Host) 设备(Device) 接口(Interface) 管道(Pipe) 管道是主机与设备端点数据传输的连接通道,代表了主机的数据缓冲区与设备端点之间交 ...

  4. [转]六款常用的linux C/C++ IDE

    之前在windows下开发习惯啦,linux改用vim开发代码,但是前期还是不熟悉看代码效率感觉有点低.由于看代码需要各种跳转查找我个人觉得还是IDE方便些,以前在windows下就挺喜欢使用code ...

  5. 编写高质量JavaScript代码的基本要点记录

    原文:深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点 1.最小全局变量(Minimizing Globals)的重要性 JavaScript通过函数管理作用域.在 ...

  6. ui主线程控件的更新就让这个activity的异步任务做完整

    项目中使用的SingleMessageView,控件实例化后,点击用户头像,此时跳转到UserInfo里查看这个用户的头像.用户名.签名.标签. 之前,师兄在SingleMessage里写了个头像的点 ...

  7. iOS 约束,设置文字控制的高度

    在使用xib或者storyboard进行界面开发和设置约束时,会遇到文字控件,下个控件到文字控件的距离设置. 由于文字控件的大小和文字本身的大小是不同的,那就会造成“设置位置”与设计图的比例不同. 怎 ...

  8. zepto animate

    // Zepto.js // (c) 2010-2013 Thomas Fuchs // Zepto.js may be freely distributed under the MIT licens ...

  9. OC-Objection 学习笔记之一:简单的开始

    Objection 统一管理对象的引用问题,我想这就是这种技术的意义吧. 废话不说,咱们直接上步骤吧: 1:协议 我们的意识里要知道,一切围绕协议来进行. 下面的协议是一个视图的协议,该协议简单到不能 ...

  10. angularjs---服务(service / factory / provider)

    初angularJs时  常写一些不够优雅的代码  !我总结了一下看看各位有没有中枪的!-----( 这里只针对服务service及其相关! ) 以下做法不太优雅 兄弟controller 之间的相同 ...