2017年2月16日 分析下为什么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都是同一个,故能用上缓存了
留下个下小思考
,CacheKey是怎么作为KEY来判断是否执行的是同一条SQL与参数的呢
2017年2月16日 分析下为什么spring 整合mybatis后为啥用不上session缓存的更多相关文章
- 分析下为什么spring 整合mybatis后为啥用不上session缓存
因为一直用spring整合了mybatis,所以很少用到mybatis的session缓存. 习惯是本地缓存自己用map写或者引入第三方的本地缓存框架ehcache,Guava 所以提出来纠结下 实验 ...
- 2017年4月16日 一周AnswerOpenCV佳作赏析
2017年4月16日 一周AnswerOpenCV佳作赏析 1.HelloHow to smooth edge of text in binary image, based on threshold. ...
- 2017年12月16日 ASP.NET基本用法
ASP.NET初级添加 利用css代码跟ASP.NET还有Javascript原生,LinQ来写增跟展示数据 首先介绍一个非常好用的控件,灵活并且循环展示数据库里面的数据 <asp:Repeat ...
- outlook 通讯录分类--2017年1月16日--对联系人分类管理
outlook功能多,复杂,导致打开界面就晕,通讯录分类 问:在Outlook 中,随着联系人数量的增多,亲朋好友.同事.客户的信息混杂在一起,每次发邮件都要用很长时间才能从联系人列表中找到需要的人. ...
- 2017年2月16日-----------乱码新手自学.net 之MVC模型
第二篇博文,最近学习的内容还是回到了正题:ASP.NET MVC5之上.虽然EF学了个一知半解,但是用这点知识,看MVC5的MODEL部分应该还是够了.尽管周末还要恶补一下EF才行. (一)MVC简述 ...
- [转载]Ubuntu17.04(Zesty Zapus)路线图发布:2017年4月13日发布
Canonical今天公布了Ubuntu 17.04(Zesty Zapus)操作系统的发布路线图,该版本于今年10月24日上线启动,toolchain已经上传且首个daily ISO镜像已经生成.面 ...
- 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 语言的接口设计是非侵入式的,接口编写者无须知 ...
- 【2017年9月10日更新】ABP配套代码生成器(ABP Code Generator)帮助文档,实现快速开发
ABP代码生成器介绍 ABP Code Generator 针对abp这个框架做了一个代码生成器,功能强大.分为两大功能点,一个是数据层,一个是视图层. 数据服务层:通过它,可以实现表设计.领域层初始 ...
- 猖獗的假新闻:2017年1月1日起iOS的APP必须使用HTTPS
一.假新闻如此猖獗 刚才一位老同事 打电话问:我们公司还是用的HTTP,马上就到2017年了,提交AppStore会被拒绝,怎么办? 公司里已经有很多人问过这个问题,回答一下: HTTP还是可以正常提 ...
随机推荐
- Java NIO 与 IO
我应该何时使用 IO,何时使用 NIO 呢?在本文中,我会尽量清晰地解析 Java NIO 和 IO 的差异.它们的使用场景,以及它们如何影响您的代码设计. Java NIO 和 IO 的主要区别 下 ...
- STM32单片机在Keil5下仿真的问题解决及GPIO口初始化、使用
STM32单片机在Keil5下仿真的问题解决及GPIO口初始化.使用 最近看了视频,里面有仿真,可以清楚看到GPIO口的数据变化,也想尝试下,DUG时却出现*** error 65: access v ...
- 搭建NDK环境
2014.07.14 搭建OK,但是目前只能手动编译c代码,具体不清楚.
- UVa 11631 - Dark roads
题目大意:政府为了减小开支决定关闭一些路灯,同时保证照亮的路能连接所有路口. 又是一个MST问题,Kruskal算法,不过数据规模比较大,又Submission Error了...扔这吧... #in ...
- sqlite3API函数
回顾: DDL 表的创建.修改.删除 create table 表名(字段名 字段类型 [约束],...); alter table 表名 {rename to 新名字 | add column 字段 ...
- windows下Nodejs环境部署
前言 Nodejs是基于v8引擎的轻量级框架,其特点为事件驱动.非阻塞.单线程.异步回调等. Nodejs相对于其他运行在服务器端的语言,容易学习,使用方便. 本文将介绍windows下Nodejs的 ...
- php 禁用eval( )函数
php的eval函数并不是系统组件函数,因此我们在php.ini中使用disable_functions是无法禁止它的. 但是eval()对于php安全来说具有很大的杀伤力,因此一般不用的情况下为了防 ...
- OSG和osgearth显示中文(转载)
osgEarth支持中文过程详解 OSG和osgearth显示中文 一.知识储备 要想很好的理解和解决这个问题,首先要了解什么是多字节和宽字节.说实话我之前也知道这两个字节到底有什么区别,只是简单 ...
- C# Winform窗口之间传值的多种方法浅析(转)
摘要http://www.jb51.net/article/63837.htm 这篇文章主要介绍了C# Winform窗口之间传值的多种方法浅析,本文起讲解了通过构造器传值.通过属性传递.通过事件携带 ...
- YII 1.0 扩展第三方类
扩展缩略图类在blog\protected\extensions 中建立 Image/CThumb.php 1. 自己瞎弄的,一点都不优雅 include_once Yii::app()->Ba ...