之前的文章我已经基本讲解到了SqlSessionFactory、SqlSession、Excutor以及Mpper执行SQL过程,下面我来了解下myabtis的缓存,

它的缓存分为一级缓存和二级缓存,本文我们主要分析下一级缓存。

先看一个例子,代码还是之前(第一篇)的的demo

    public static void main(String[] args) throws Exception {
        SqlSessionFactory sessionFactory = null;
        String resource = "configuration.xml";
        sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
        SqlSession sqlSession = sessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        System.out.println(userMapper.findUserById(1));
        System.out.println(userMapper.findUserById(1));
    }

上面代码我们执行了两次userMapper.findUserById(1),结果如下图

从执行结果看,DB的查询只有1次,那么第二次的查询结果是在怎么来的呢?

一:什么是一级缓存

每当我们使用MyBatis开启一次和数据库的会话,MyBatis会创建出一个SqlSession对象表示一次数据库会话。 在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。

为了解决这一问题,减少资源的浪费,MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。

基本的流程示意图如下:

对于会话(Session)级别的数据缓存,我们称之为一级数据缓存,简称一级缓存。

二:如何执行缓存

我们知道mybatis的SQL执行最后是交给了Executor执行器来完成的,我们看下BaseExecutor类的源码

 @Override
     public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
       BoundSql boundSql = ms.getBoundSql(parameter);
       CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
       return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
    
    
    
    
     @SuppressWarnings("unchecked")
     @Override
     public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
       ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
       if (closed) {
         throw new ExecutorException("Executor was closed.");
       }
       if (queryStack == 0 && ms.isFlushCacheRequired()) {
         clearLocalCache();
       }
       List<E> list;
       try {
         queryStack++;
         list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;//localCache 本地缓存
         if (list != null) {
           handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
         } else {
           list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);  //如果缓存没有就走DB
         }
       } finally {
         queryStack--;
       }
       if (queryStack == 0) {
         for (DeferredLoad deferredLoad : deferredLoads) {
           deferredLoad.load();
         }
         // issue #601
         deferredLoads.clear();
         if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
           // issue #482
           clearLocalCache();
         }
       }
       return list;
     }
     
     
     
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);//清空现有缓存数据
    }
    localCache.putObject(key, list);//新的结果集存入缓存
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

通过上面的三个方法我们基本已经看明白了缓存的使用,它的本地缓存使用的是PerpetualCache类,内部其实还是一个HashMap,只是稍微做了封装而已。

我们再看下天的Key是如何生成的

 CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(Integer.valueOf(rowBounds.getOffset()));
    cacheKey.update(Integer.valueOf(rowBounds.getLimit()));
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (int i = 0; i < parameterMappings.size(); i++) {
      ParameterMapping parameterMapping = parameterMappings.get(i);
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

它是通过statementId,params,rowBounds,BoundSql来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果。

我们用一张图来看清楚一级缓存的基本流程(本图网上早来的,自己懒得画了)

三:一级缓存生命周期

1. MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,

Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

2. 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。

3. 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。

4.SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用。

Mybatis源码分析之Cache一级缓存原理(四)的更多相关文章

  1. Mybatis源码分析之Cache二级缓存原理 (五)

    一:Cache类的介绍 讲解缓存之前我们需要先了解一下Cache接口以及实现MyBatis定义了一个org.apache.ibatis.cache.Cache接口作为其Cache提供者的SPI(Ser ...

  2. mybatis源码分析之05一级缓存

    首先需要明白,mybatis的一级缓存就是指SqlSession缓存,Map缓存! 通过前面的源码分析知道mybatis框架默认使用的是DefaultSqlSession,它是由DefaultSqlS ...

  3. Guava 源码分析之Cache的实现原理

    Guava 源码分析之Cache的实现原理 前言 Google 出的 Guava 是 Java 核心增强的库,应用非常广泛. 我平时用的也挺频繁,这次就借助日常使用的 Cache 组件来看看 Goog ...

  4. mybatis源码分析之06二级缓存

    上一篇整合redis框架作为mybatis的二级缓存, 该篇从源码角度去分析mybatis是如何做到的. 通过上一篇文章知道,整合redis时需要在FemaleMapper.xml中添加如下配置 &l ...

  5. MyBatis源码分析(2)—— Plugin原理

    @(MyBatis)[Plugin] MyBatis源码分析--Plugin原理 Plugin原理 Plugin的实现采用了Java的动态代理,应用了责任链设计模式 InterceptorChain ...

  6. mybatis源码分析(7)-----缓存Cache(一级缓存,二级缓存)

    写在前面  MyBatis 提供查询缓存,用于减轻数据库压力,提高数据库性能. MyBatis缓存分为一级缓存和二级缓存. 通过对于Executor 的设计.也可以发现MyBatis的缓存机制(采用模 ...

  7. MyBatis源码分析(4)—— Cache构建以及应用

    @(MyBatis)[Cache] MyBatis源码分析--Cache构建以及应用 SqlSession使用缓存流程 如果开启了二级缓存,而Executor会使用CachingExecutor来装饰 ...

  8. MyBatis 源码分析 - 缓存原理

    1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...

  9. MyBatis源码分析(五):MyBatis Cache分析

    一.Mybatis缓存介绍 在Mybatis中,它提供了一级缓存和二级缓存,默认的情况下只开启一级缓存,所以默认情况下是开启了缓存的,除非明确指定不开缓存功能.使用缓存的目的就是把数据保存在内存中,是 ...

随机推荐

  1. 网速4M等于多少KB/S,等于多少kbps

    4M=512KB/S=4096Kbps 1KB/S=8Kbps 8倍速 转:http://zhidao.baidu.com/link?url=8GAyhcY9BbVstQr8pE3I7QP_M53Km ...

  2. Panorama和Pivot控件

    Windows Phone提供了Panorama和Pivot这两种控件供用户横向切换导航的方式来显示具有内容比较相关的页面.本文主要对这两个控件进行描述,包括如何使用,以及一些最佳实践. 其中包括如下 ...

  3. 福尔摩斯基本演绎法第一季/全集Elementary迅雷下载

    本季Elementary Season 1 第一季(2012)看点:<福尔摩斯:基本演绎法>由CBS出品,根据<福尔摩斯>系列改编,地点从19世纪大英帝国国势鼎盛的时期搬到了2 ...

  4. 犯罪现场调查第一季/全集CSI迅雷下载

    英文译名 CSI (第1季) (2000首播)CBS. 本季看点:<犯罪现场调查>赌城拉斯维加斯吸引着做发财梦的人,也吸引着形形色色的罪犯,该市警察局的犯罪现场调查局在全美国名列第二.该剧 ...

  5. 教你摆脱低级程序猿 项目中cocopads的安装使用

    小农今天聊聊一款作为iOS开发者必备的第三方管理软件.希望程序猿朋友们看到小农的这篇文章后.可以真正的学会怎样灵活管理你项目中的第三方. (一)CocoaPods是什么? 首先我们来认识一下这款第三方 ...

  6. 用IO流向存储器或SD卡中存入/读取字符的工具类

    FileManager package com.kale.utils; import java.io.BufferedReader; import java.io.File; import java. ...

  7. anaconda、pip配置国内镜像

    一.anaconda配置镜像查看源:conda config --show-sources在Mac and Linux下:conda config --add channels https://mir ...

  8. HTML5 <Audio>标签API整理(三)

    一.浏览器支持 Internet Explorer 9+, Firefox, Opera, Chrome, 和 Safari 都支持 <audio> 元素. 注意: Internet Ex ...

  9. Apache Ant 简介和配置

    Apache Ant 简介     Apache Ant是目前事实上的Java应用的标准build脚本工具.使它大受欢迎的一个主要愿意是它的和灵活,允许程序员创建自己的Task来对Ant进行扩展.   ...

  10. jQuery中attr()和prop()的区别,修改checked属性 jquery attr('checked' 不起作用 attr('checked' 不对

    在做复选框全选按钮的时候,出现了一个问题,使用语句$.attr('checked',true),将复选框的属性改为被选中,在chrome浏览器中第一次点击有效后面就不行了,IE8倒是没有问题. 百度了 ...