最近一周都在研究mybatis源码,其实就mybatis源码相对于Spring框架源码来讲,mybatis仍是简单的,主要就是封装jdbc,而后应用各类设计模式优化总体架构:如 在mybatis中用到了如下的设计模式,
构建者(建立sqlSession对象用到了)
责任链(mybatis中主要特色之一就是大量的handler,他们就是经过责任链来增长,执行)
装饰者(mybatis中主要就是 Executor 主要这些:单例执行器SimpleExecutor,批量执行器BatchExecutor,以及一个缓存执行器CachingExecutor )面试若是问执行器,能够简单来讲 两类 即BaseExecutor CachingExecutor,具体在看状况回答 下图是idea查看源码,建议你们看这文章的时候,使用idea跟踪源码一步一步分析,看看类图,比较记忆深入,本人学习源码也是一个一个断点跟踪。
下面说下官方的一个mybatis缓存的介绍
MyBatis支持声明式数据缓存(declarative data caching)。当一条SQL语句被标记为“可缓存”后,首次执行它时从数据库获取的全部数据会被存储在一段高速缓存中,从此执行这条语句时就会从高速缓存中读取结果,而不是再次命中数据库。MyBatis提供了默认下基于Java HashMap的缓存实现,以及用于与OSCache、Ehcache、Hazelcast和Memcached链接的默认链接器。MyBatis还提供API供其余缓存实现使用。面试
重点的那句话就是:MyBatis执行SQL语句以后,这条语句就是被缓存,之后再执行这条语句的时候,会直接从缓存中拿结果,而不是再次执行SQLsql
这也就是你们常说的MyBatis一级缓存,一级缓存的做用域scope是SqlSession。数据库
MyBatis同时还提供了一种全局做用域global scope的缓存,这也叫作二级缓存,也称做全局缓存。设计模式
下面给你们演示 一级缓存
同个session进行两次相同查询:缓存
同个session进行两次相同查询: @Test public void test() { SqlSession sqlSession = sqlSessionFactory.openSession(); try { User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1); log.debug(user); User user2 = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1); log.debug(user2); } finally { sqlSession.close(); } } MyBatis只进行1次数据库查询: ==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 1(Integer) <== Total: 1 User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014} User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014} 同个session进行两次不一样的查询: @Test public void test() { SqlSession sqlSession = sqlSessionFactory.openSession(); try { User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1); log.debug(user); User user2 = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 2); log.debug(user2); } finally { sqlSession.close(); } } MyBatis进行两次数据库查询: ==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 1(Integer) <== Total: 1 User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014} ==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 2(Integer) <== Total: 1 User{id=2, name='FFF', age=50, birthday=Sat Dec 06 17:12:01 CST 2014} 不一样session,进行相同查询: @Test public void test() { SqlSession sqlSession = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); try { User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1); log.debug(user); User user2 = (User)sqlSession2.selectOne("org.format.mybatis.cache.UserMapper.getById", 1); log.debug(user2); } finally { sqlSession.close(); sqlSession2.close(); } } MyBatis进行了两次数据库查询: ==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 1(Integer) 同个session,查询以后更新数据,再次查询相同的语句: @Test public void test() { SqlSession sqlSession = sqlSessionFactory.openSession(); try { User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1); log.debug(user); user.setAge(100); sqlSession.update("org.format.mybatis.cache.UserMapper.update", user); User user2 = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1); log.debug(user2); sqlSession.commit(); } finally { sqlSession.close(); } } 更新操做以后缓存会被清除: ==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 1(Integer) <== Total: 1 User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014} ==> Preparing: update USERS SET NAME = ? , AGE = ? , BIRTHDAY = ? where ID = ? ==> Parameters: format(String), 23(Integer), 2014-10-12 23:20:13.0(Timestamp), 1(Integer) <== Updates: 1 ==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 1(Integer) <== Total: 1 User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
很明显,结果验证了一级缓存的概念,在同个SqlSession中,查询语句相同的sql会被缓存,可是一旦执行新增或更新或删除操做,缓存就会被清除session
一级二级缓存结论已经展现,下面带你们看下源码结构如何实现的,其实就看了好多源码状况,通常底层的结构存储缓存就是 map数据结构,具体什么缓存策略 无非就是 FIFO LRU LFU 定时,懒加载 什么的。
在分析MyBatis的一级缓存以前,咱们先简单看下MyBatis中几个重要的类和接口:
Configuration类:主要MyBatis全局配置信息类
看这个图片是否是你们就豁然开朗了,就是 读取xml 解析各个节点信息,最终将这个解析到的全局信息存放到 configuration对象中数据结构
SqlSessionFactory:操做SqlSession的工厂接口,具体的实现类是DefaultSqlSessionFactory
SqlSession接口:执行sql,管理事务的接口,具体的实现类是DefaultSqlSession
Executor接口:sql执行器,SqlSession执行sql最终是经过该接口实现的,经常使用的实现类有SimpleExecutor和CachingExecutor,这些实现类都使用了装饰者设计模式(io 其实也用到了装饰者,装饰者就是解决了方法的加强)
-------------------------------------------------------------分割线---------------------------------------------------------------------------------------------------mybatis
基本分析mybatis须要了解的一本概念若是上面你们都清楚了,那么就能够往下面走了,相信我,下面so easy,若是上面还有模糊,请百度~
一级缓存的做用域是SqlSession,那么咱们就先看一下SqlSession的select过程:
认真看了上面文章就知道,每一个sql的执行其实就是经过 session对象来调用方法,那么session 是怎么来的呢? 1.读取配置文件,生成enviroment对象放入全局对象 configuration对象, 2.由sessionFactory openSession 生成sqlSession对象(构建者模式生成的,经过configuration对象中存储的属性配置)
这是DefaultSqlSession(SqlSession接口实现类,MyBatis默认使用这个类)的selectList源码(咱们例子上使用的是selectOne方法,调用selectOne方法最终会执行selectList方法):
看这里能够先看下 下面的第二张图片 介绍 MappedStatement 这个就是 mybatis对sql解析后 存放的对象
接下来咱们看下DefaultSqlSession中的executor接口属性具体是哪一个实现类。
DefaultSqlSession的构造过程(DefaultSqlSessionFactory内部)由于sqlSession 是由factory生成的,咱们就须要看factory的类实现:
Executor根据ExecutorType的不一样而建立,最经常使用的是SimpleExecutor,本文的例子也是建立这个实现类。 最后咱们发现若是cacheEnabled这个属性为true的话,那么executor会被包一层装饰器,这个装饰器是CachingExecutor。其中cacheEnabled这个属性是mybatis总配置文件中settings节点中cacheEnabled子节点的值,默认就是true,也就是说咱们在mybatis总配置文件中不配cacheEnabled的话,它也是默认为打开的。架构
executor = (Executor) interceptorChain.pluginAll(executor); 这行代码就是 应用了责任链设计模式,不在这次解说返回,读者自行百度
Executor根据ExecutorType的不一样而建立,最经常使用的是SimpleExecutor,本文的例子也是建立这个实现类。 最后咱们发现若是cacheEnabled这个属性为true的话,那么executor会被包一层装饰器,这个装饰器是CachingExecutor。其中cacheEnabled这个属性是mybatis总配置文件中settings节点中cacheEnabled子节点的值,默认就是true,也就是说咱们在mybatis总配置文件中不配cacheEnabled的话,它也是默认为打开的。
你们看到这其实 已经看得差很少了,就剩下下面一小点了,总结上面就是 默认给咱们生成了个 单例的执行器,而后因为默认配置 cacheEanbled,全部采用装饰者,最终先由cachingExecutor 执行器用了 执行sqlapp
------------------------------------------------------分割线----------------------------------------------------------------------------------------------------------
如今,问题就剩下一个了,CachingExecutor执行sql的时候到底作了什么?
回到sql 执行query方法咱们继续跟踪
此时应该看得就是 CachingExecutor 代码中的query方法了
带着这个问题,咱们继续走下去(CachingExecutor的query方法):
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, parameterObject, boundSql); if (!dirty) { cache.getReadWriteLock().readLock().lock(); try { @SuppressWarnings("unchecked") List<E> cachedList = (List<E>) cache.getObject(key); if (cachedList != null) return cachedList; } finally { cache.getReadWriteLock().readLock().unlock(); } } List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks return list; } } return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
其中Cache cache = ms.getCache();这句代码中,这个cache实际上就是个二级缓存,因为咱们没有开启二级缓存(二级缓存的内容下面会分析),所以这里执行了最后一句话。这里的delegate也就是SimpleExecutor,SimpleExecutor没有Override父类的query方法,所以最终执行了SimpleExecutor的父类BaseExecutor的query方法。
BaseExecutor的属性localCache是个PerpetualCache类型的实例,PerpetualCache类是实现了MyBatis的Cache缓存接口的实现类之一,内部有个Map<Object, Object>类型的属性用来存储缓存数据。 这个localCache的类型在BaseExecutor内部是写死的。 这个localCache就是一级缓存!
未完待续。。。。。。要和小姐姐吃饭去了。