因为一直用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. POJ3259负环判定

    题意:有n个顶点,m条边,然后有w个洞,过每个洞的时间为-ti,求是否会时光倒流 分析:就是求是否存在负圈,用Bellman-Floyd判定是否存在负圈即可,注意是无向图,所以路径是双向可达的 #in ...

  2. (中等) HDU 1542 Atlantis,扫描线。

    Problem Description There are several ancient Greek texts that contain descriptions of the fabled is ...

  3. MAC中使用Vim和GCC编译C程序

    1.打开终端 2.输入以下命令进入vim编辑器: vim a.c 3.进入编辑器后按i进入insert模式,然后键入以下代码: #include<stdio.h> int main(){ ...

  4. module_param()函数

    1.定义模块参数的方法: module_param(name, type, perm); 其中,name:表示参数的名字;     type:表示参数的类型;     perm:表示参数的访问权限; ...

  5. CentOS下成功修复了Windows的grub引导

    (转载) 以CentOS7和Windows为双系统,且后安装CentOS时,会出现开机没有Windows引导的问题.下图,倒数第二行可以看到Windows引导项: 修复运行终端(terminal),键 ...

  6. iOS 架构模式

    参考:http://www.cocoachina.com/ios/20160108/14916.html MVC , MVP , MVVM , VIPER

  7. IOS 创建和设置pch

    1.添加pch文件 2.修改工程配置文件 Building Settings->All->Apple LLVM 6.0 -Language -> Prefix Header

  8. pipe管道

    回顾: 进程间通信方式: 信号,管道 消息队列,共享内存,信号量 sokcet 信号: 本质就是软中断 signal(信号,函数指针); void func(int); kill(pid,signo) ...

  9. 前端MVVM框架:Knockout.JS(一)

    前言 在我们平时开发 Web 应用程序的时候,如果项目不算特别大的话,一般都是拿 jQuery 再配合一些前端 UI 框架就在项目上面应用了.如果页面逻辑稍微复杂的话,那个在写前端 JavaScrip ...

  10. Eclipse 输入出错时自动查找类

    经常会只记得类的前半部分名称,这时可以双击这个没写完的类名,然后会弹出选择框: