最近一周都在研究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语句之后,这条语句就是被缓存,以后再执行这条语句的时候,会直接从缓存中拿结果,而不是再次执行SQL
这也就是大家常说的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会被缓存,但是一旦执行新增或更新或删除操作,缓存就会被清除
一级二级缓存结论已经展示,下面带大家看下源码结构如何实现的,其实就看了好多源码情况,一般底层的结构存储缓存就是 map数据结构,具体什么缓存策略 无非就是 FIFO LRU LFU 定时,懒加载 什么的。
在分析MyBatis的一级缓存之前,我们先简单看下MyBatis中几个重要的类和接口:
Configuration类:主要MyBatis全局配置信息类
看这个图片是不是大家就豁然开朗了,就是 读取xml 解析各个节点信息,最终将这个解析到的全局信息存放到 configuration对象中
SqlSessionFactory:操作SqlSession的工厂接口,具体的实现类是DefaultSqlSessionFactory
SqlSession接口:执行sql,管理事务的接口,具体的实现类是DefaultSqlSession
Executor接口:sql执行器,SqlSession执行sql最终是通过该接口实现的,常用的实现类有SimpleExecutor和CachingExecutor,这些实现类都使用了装饰者设计模式(io 其实也用到了装饰者,装饰者就是解决了方法的增强)
-------------------------------------------------------------分割线---------------------------------------------------------------------------------------------------
基本分析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 执行器用了 执行sql
------------------------------------------------------分割线----------------------------------------------------------------------------------------------------------
现在,问题就剩下一个了,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就是一级缓存!
未完待续。。。。。。要和小姐姐吃饭去了。