Mybatis源码分析之二级缓存

最近一周都在研究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就是一级缓存!

 

未完待续。。。。。。要和小姐姐吃饭去了。

相关文章
相关标签/搜索