通过简单的人话来理解MyBatis的缓存

文章目录

1.MyBatis缓存概述

1.1.缓存是啥?

1.通俗的讲:缓存就是临时的数据;
  比如:我们下载一款游戏,开始玩的时候,可能比较慢,这个时候这块游戏应用实际上在下载一些实际你选择的游戏场景的资源;
       一旦下载完成,你玩了第一局之后,再次玩第二局就能够很快的进入(暂且不考虑中途游戏应用有突然更新)游戏中,
       这就是因为你的游戏应用已经缓存了当前游戏场景的资源;

1.2.MyBatis缓存体系结构

1.3.代码结构

在这里插入图片描述

2.一级缓存

2.1.一级缓存概述

1.一级缓存也称为本地缓存
2.一级缓存主要是针对的同一个SqlSession对象的缓存:PerpetualCache localCache;
  如果同一个SqlSession执行的SQL一模一样,会直接使用缓存中的查询结果

2.2.一级缓存的开启:默认开启

2.3.一级缓存的存放位置

在这里插入图片描述

2.4.一级缓存的关闭的方式

2.4.1.setting 属性中的flushCache

在这里插入图片描述

2.5.一级缓存的案例说明

2.5.1.相同SqlSession查询

2.5.1.1.代码测试
  @Test
  public void firstLevelDemoSameSqlSession() throws IOException {
    String resource = "mybatis-config-session-cache-first.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    SqlSession session = sqlSessionFactory.openSession(); // ExecutorType.BATCH

    try {
      BlogMapper mapper = session.getMapper(BlogMapper.class);
      Blog blog = mapper.selectByPrimaryKey(1L);
      System.out.println("blog ="+blog.toString());
      Blog blog1 = mapper.selectByPrimaryKey(1L);
      System.out.println("blog1="+blog1);

      BlogMapper mapper1 = session.getMapper(BlogMapper.class);
      Blog blog2 = mapper1.selectByPrimaryKey(1L);
      System.out.println("blog2="+blog2);

    } finally {
      session.close();
    }
  }

在这里插入图片描述

2.5.1.2.控台输出

在这里插入图片描述

2.5.2.不同SqlSession查询

2.5.2.1.代码测试
@Test
  public void firstLevelDemoDifferentSqlSession() throws IOException {
    String resource = "mybatis-config-session-cache-first.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    SqlSession session = sqlSessionFactory.openSession(); // ExecutorType.BATCH
    SqlSession session1 = sqlSessionFactory.openSession(); // ExecutorType.BATCH

    try {
      BlogMapper mapper = session.getMapper(BlogMapper.class);
      Blog blog = mapper.selectByPrimaryKey(1L);
      System.out.println(blog.toString());

      BlogMapper mapper1 = session1.getMapper(BlogMapper.class);
      Blog blog1 = mapper1.selectByPrimaryKey(1L);
      System.out.println(blog1);
    } finally {
      session.close();
    }
  }

在这里插入图片描述

2.5.2.2.控台输出

在这里插入图片描述

2.5.3.相同SqlSession,更新操作会清理缓存

2.5.3.1.代码测试
@Test
  public void firstLevelDemoSameSqlSessionQueryAndUpdate() throws IOException {
    String resource = "mybatis-config-session-cache-first.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    SqlSession session = sqlSessionFactory.openSession(); // ExecutorType.BATCH

    try {
      BlogMapper mapper = session.getMapper(BlogMapper.class);
      Blog blog = mapper.selectByPrimaryKey(1L);
      System.out.println("blog ="+blog.toString());

      Blog updateRecord=new Blog();
      updateRecord.setBid(blog.getBid());
      updateRecord.setName("U"+blog.getName());
      mapper.updateByPrimaryKey(updateRecord);
      session.commit();

      Blog blog1 = mapper.selectByPrimaryKey(1L);
      System.out.println("blog1="+blog1);


    } finally {
      session.close();
    }
  }
一级缓存是在 BaseExecutor 中的 update()方法中调用 clearLocalCache()清空的 (无条件),query 中会判断。
2.5.3.2.控台输出

在这里插入图片描述

1.同一个session中途一旦有更新操作(update/delete),那么一级缓存中的该Session中缓存会清理掉

2.5.4.不同SqlSession更新,相同SqlSession查询

2.5.4.1.代码测试
@Test
  public void firstLevelDemoDifferentSqlSessionUpdateAndQuery() throws IOException {
    String resource = "mybatis-config-session-cache-first.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    SqlSession session = sqlSessionFactory.openSession(); // ExecutorType.BATCH

    try {
      BlogMapper mapper  = session.getMapper(BlogMapper.class);
      Blog blog = mapper.selectByPrimaryKey(1L);
      System.out.println("blog="+blog);

      SqlSession session1 = sqlSessionFactory.openSession(); // ExecutorType.BATCH
      BlogMapper mapper1 = session1.getMapper(BlogMapper.class);
      Blog updateRecord=new Blog();
      updateRecord.setBid(1L);
      updateRecord.setName("frank");
      mapper1.updateByPrimaryKey(updateRecord);
      session1.commit();

      Blog blog1 = mapper.selectByPrimaryKey(1L);
      System.out.println("blog1="+blog1);


    } finally {
      session.close();
    }
  }
2.5.4.2.控台输出

在这里插入图片描述

1.在这个案例中,session进行了第一次查询,返回的name 是gaoxinfu
  紧接着,session1进行更新操作,name变为frank
  然后,session进行再次查询,由于命中了缓存,所以直接拿了缓存中的内容,name为gaoxinfu
2.这样的话,使用一级缓存的时候,因为缓存不能跨会话共享,不同的会话之间对于相同的数据 可能有不一样的缓存。
  在有多个会话或者分布式环境下,会存在脏数据的问题。如果要 解决这个问题,就要用到二级缓存。  

2.5.5.案例源码地址

https://gitee.com/gaoxinfu_admin/open-source/blob/master/mybatis/mybatis-3-master/demo-open-source-mybatis/src/main/java/com/gaoxinfu/demo/open/source/mybatis/cache/first/FirstLevelDemo.java

2.6.一级缓存源码使用位置

2.6.1.一级缓存源码使用位置-查询

BaseExecutor.query方法

 @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;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //实时查询数据库
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } 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;
  }

在这里插入图片描述

2.6.2.一级缓存源码使用位置-更新

在这里插入图片描述

  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    clearLocalCache();
    return doUpdate(ms, parameter);
  }
  @Override
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }
同一个session中途一旦有更新操作(update/delete),那么一级缓存中的该Session中缓存会清理掉

2.6.3.附:关于上面的key,如何定义

在这里插入图片描述

3.二级缓存

3.1.二级缓存概述

1.从上面的”不同SqlSession更新,Session相同查询“中我们知道,一级缓存存在一个问题:一级缓存不能跨会话共享数据;
2.二级缓存主要是解决上面一级缓存中的问题(一级缓存不能共享数据),
3.二级缓存的有效范围是namespace级别的,可以被多个SqlSession共享(只要是同一个接口里面的相同方法,都可以共享);

3.2.二级缓存范围与顺序

1.二级缓存,相同的Namespace,由于不同的SqlSession的数据可以进行数据的共享
  因此,二级缓存的查询范围是要大于一级缓存的;  

3.3.二级缓存开启 :(默认开启)

3.3.1.开启方式:cacheEnabled=true

 <!-- 控制全局缓存(二级缓存)-->
        <setting name="cacheEnabled" value="true"/>

在这里插入图片描述

3.3.2.源码code:对上面配置cacheEnabled=true使用

Configuration.newExecutor方法

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {

    /*
    1.获取执行器类型
     */
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

    /*
    2.获取执行器类型实例
     */
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }

    /*
    3.判断是否是二级缓存  配置文件中的cacheEnabled属性
     */
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }

    /*
    4.插件 调用interceptorChain中的所有的interceptor,进行包装
     */
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

在这里插入图片描述

3.3.3.二级缓存与一级缓存的查询顺序:先查询二级缓存,再次查询一级缓存

作为一个作用范围更广的缓存,它肯定是在 SqlSession 的外层,否则不可能被多个 SqlSession 共享。
而一级缓存是在 SqlSession 内部的,肯定是工作在一级缓存之前,也就是只有取不到二级缓存的情况下才到一个会话中去取一级缓存。

3.4.二级缓存存放位置:CachingExecutor.TransactionalCacheManager

1.对于二级缓存的处理,有一个装饰器类CachingExecutor 进行特殊的处理,
  如果配置中已经开启了二级缓存,那么创建 Executor 对象的时候会对通过CachingExecutor对Executor进行装饰。
2.CachingExecutor 对于查询请求,会判断二级缓存是否有缓存结果,如果有就直接返回,
  如果没有委派交给真正的查询器Executor实现类,比如 SimpleExecutor来执行查询,再走到一级缓存的流程。
  最后会把结果缓存起来,并且返回给用户。

在这里插入图片描述

3.5.二级缓存的关闭

3.5.1.友情提示

1.这里注意下,虽然,我们上面配置了cacheEnabled=true,但是并不意味着你的二级缓存已经生效,只是说我们在进行Executor选择的时候会使用
  CachingExecutor;
2.因为具体你的二级缓存有么有生效,取决于你的每一个SQL中有没有配置 useCache这个属性,如配置为false,那么二级缓存不会生效

3.5.2.useCache 属性配置样例

  <select id="selectBlogList" resultMap="BaseResultMap" useCache="true">
        select bid, name, author_id authorId from blog
  </select>

3.5.3.友情提示:源码code证明

CachingExecutor.query

 @Override
  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);
      /**
       * useCache 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来
       */
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

在这里插入图片描述

3.6.二级缓存Cache节点:相关策略介绍

在这里插入图片描述

type

在这里插入图片描述

eviction :LRU,FIFO,SOFT,WEAK

1.这里你具体配置那个,其实实际上代表这一个回收策略处理的类,mybatis配置文件解析的时候会进行解析,对应类处理

在这里插入图片描述
在这里插入图片描述

3.7.如何理解二级缓存可以被不同SqlSession共享

3.7.1.首先进入CachingExecutor.query方法

 @Override
  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);
      /**
       * useCache 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来
       */
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

在这里插入图片描述

3.7.2.其次,我们接下来进入tcm.putObject(cache, key, list);分析究竟如何实现

3.7.2.1.备注:TransactionalCacheManager tcm 理解
1.CachingExecutor 中的TransactionalCacheManager 只是缓存的一个管理工具,
  或者你就认为他就是一个Util类而已,并不会存储任何东西
public class CachingExecutor implements Executor {

  private final Executor delegate;
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();

  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }
}
3.7.2.2.TransactionalCacheManager.putObject
 public void putObject(Cache cache, CacheKey key, Object value) {
 	//getTransactionalCache(cache) 这个只是为了创建一个带有cache的构造对象TransactionalCache,本质上TransactionalCache还是cache
    getTransactionalCache(cache).putObject(key, value);
  }
  /**
   * 存在的话返回,不存在new一个
   * @param cache
   * @return
   */
  private TransactionalCache getTransactionalCache(Cache cache) {
    /**
     *     下面这行代码跟下面这个功能是一样的
     *     if (transactionalCaches.get(cache)!=null){
     *       return transactionalCaches.get(cache);
     *     }else{
     *       TransactionalCache newTransactionalCache=new  TransactionalCache(cache);
     *       transactionalCaches.put(cache,newTransactionalCache);
     *       return newTransactionalCache;
     *     }
     */
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  }
  
  @Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }

在这里插入图片描述

3.7.3.第三,进入TransactionalCache.putObject方法

3.7.3.1.TransactionalCache类源码
/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache.decorators;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;

/**
 * The 2nd level cache transactional buffer.
 *
 * 二级缓存
 * <p>
 * This class holds all cache entries that are to be added to the 2nd level cache during a Session.
 * Entries are sent to the cache when commit is called or discarded if the Session is rolled back.
 * Blocking cache support has been added. Therefore any get() that returns a cache miss
 * will be followed by a put() so any lock associated with the key can be released.
 *
 *
 * 在二级缓存中使用,可一次存入多个缓存,移除多 个缓存
 * 在TransactionalCacheManager 中用 Map 维护对应关系
 *
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class TransactionalCache implements Cache {

  private static final Log log = LogFactory.getLog(TransactionalCache.class);

  /**
   * 委派设计模式
   */
  private final Cache delegate;

  /**
   * 提交前是否clear
   */
  private boolean clearOnCommit;

  //待提交的Map缓存
  private final Map<Object, Object> entriesToAddOnCommit;

  //记录缓存未命中的CacheKey对象:就是我们查询缓存的时候,如果没有,我们加入这个队列进行统计使用
  private final Set<Object> entriesMissedInCache;

  public TransactionalCache(Cache delegate) {
    this.delegate = delegate;
    this.clearOnCommit = false;
    this.entriesToAddOnCommit = new HashMap<>();
    this.entriesMissedInCache = new HashSet<>();
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public Object getObject(Object key) {
    // issue #116
    Object object = delegate.getObject(key);
    if (object == null) {
      entriesMissedInCache.add(key);
    }
    // issue #146
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  @Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }

  @Override
  public Object removeObject(Object key) {
    return null;
  }

  @Override
  public void clear() {
    clearOnCommit = true;
    entriesToAddOnCommit.clear();
  }

  public void commit() {
    if (clearOnCommit) {
      delegate.clear();
    }
    flushPendingEntries();
    reset();
  }

  public void rollback() {
    unlockMissedEntries();
    reset();
  }

  private void reset() {
    clearOnCommit = false;
    entriesToAddOnCommit.clear();
    entriesMissedInCache.clear();
  }

  private void flushPendingEntries() {
    //将待提交的Map缓存 委托给包装的Cache类
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }

  private void unlockMissedEntries() {
    for (Object entry : entriesMissedInCache) {
      try {
        delegate.removeObject(entry);
      } catch (Exception e) {
        log.warn("Unexpected exception while notifiying a rollback to the cache adapter."
            + "Consider upgrading your cache adapter to the latest version.  Cause: " + e);
      }
    }
  }

}

3.7.3.2.TransactionalCache.putObject
  //待提交的Map缓存
  private final Map<Object, Object> entriesToAddOnCommit;
  @Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }

3.7.4.总结

我们最终将数据put到了TransactionalCache(其本质是MapperStatement中的cache);
所以我们下次,不同的SqlSession调用同一个MapperStatement 缓存是可以共享的

3.8.二级环境案例演示

3.8.1.【非必须操作步骤】首先开启二级缓存:cacheEnabled=true 或者不配置该节点 默认使用

<setting name="cacheEnabled" value="ture"/>

在这里插入图片描述

3.8.2.【必须操作步骤】创建演示二级缓存的方法: BlogSpecMapper.selectBlogListForSecondCacheDemo

  List<Blog> selectBlogListForSecondCacheDemo(RowBounds rowBounds);

在这里插入图片描述

3.8.3.【必须操作步骤】创建演示二级缓存的SQL:

  <select id="selectBlogListForSecondCacheDemo" resultMap="BaseResultMap" useCache="true">
    select bid, name, author_id authorId from blog
  </select>

在这里插入图片描述

3.8.4.【必须操作步骤】BlogMapper.xml中添加cache节点配置

如果使用二级缓存,在namespace中是必须要配置的

  <cache type="org.apache.ibatis.cache.impl.PerpetualCache" >
    <property name="size" value="1024"/><!--1 hour-->
    <property name="eviction" value="LRU"/><!--1 hour-->
    <property name="flushInterval" value="120000"/>
    <property name="readOnly" value="false"/>
  </cache>

在这里插入图片描述

3.8.5.测试验证

3.8.5.1.【由于未commit导致无法生效】
3.8.5.1.1.【code】
@Test
  public void testNotCommit() throws IOException {
    String resource = "mybatis-config-session-cache-second.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);


    try {
      SqlSession session = sqlSessionFactory.openSession(); // ExecutorType.BATCH
      BlogMapper mapper = session.getMapper(BlogMapper.class);
      Blog blog = mapper.selectByPrimaryKeyForSecondCache(1L);
      System.out.println("blog ="+blog.toString());

      SqlSession session1 = sqlSessionFactory.openSession(); // ExecutorType.BATCH
      BlogMapper mapper1 = session1.getMapper(BlogMapper.class);
      Blog blog1 = mapper1.selectByPrimaryKeyForSecondCache(1L);
      System.out.println("blog1 ="+blog1.toString());

    } finally {
    }
  }
3.8.5.1.2.【控台输出】

在这里插入图片描述

3.8.5.2.【commit后再次查询】<—由于查询java结果对象未序列化失败
3.8.5.2.1.【code】
 @Test
  public void testAfterCommit() throws IOException {
    String resource = "mybatis-config-session-cache-second.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);


    try {
      SqlSession session = sqlSessionFactory.openSession(); // ExecutorType.BATCH
      BlogMapper mapper = session.getMapper(BlogMapper.class);
      Blog blog = mapper.selectByPrimaryKeyForSecondCache(1L);
      System.out.println("blog ="+blog.toString());
      session.commit();

      SqlSession session1 = sqlSessionFactory.openSession(); // ExecutorType.BATCH
      BlogMapper mapper1 = session1.getMapper(BlogMapper.class);
      Blog blog1 = mapper1.selectByPrimaryKeyForSecondCache(1L);
      System.out.println("blog1 ="+blog1.toString());

    } finally {
    }
  }
3.8.5.2.2.【控台输出】
Opening JDBC Connection
Thu Jul 09 14:18:25 CST 2020 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Created connection 892529689.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3532ec19]
==>  Preparing: select bid, name, author_id from blog where bid = ? 
==> Parameters: 1(Long)
<==    Columns: bid, name, author_id
<==        Row: 1, frank, null
<==      Total: 1
blog =Blog{bid=1, name='frank', authorId=null,hashCode= 1418621776}

org.apache.ibatis.exceptions.PersistenceException: 
### Error committing transaction.  Cause: org.apache.ibatis.cache.CacheException: Error serializing object.  Cause: java.io.NotSerializableException: com.gaoxinfu.demo.open.source.mybatis.db.model.Blog
### Cause: org.apache.ibatis.cache.CacheException: Error serializing object.  Cause: java.io.NotSerializableException: com.gaoxinfu.demo.open.source.mybatis.db.model.Blog

	at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.commit(DefaultSqlSession.java:229)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.commit(DefaultSqlSession.java:220)
	at com.gaoxinfu.demo.open.source.mybatis.cache.second.SecondLevelDemo.testAfterCommit(SecondLevelDemo.java:55)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.apache.ibatis.cache.CacheException: Error serializing object.  Cause: java.io.NotSerializableException: com.gaoxinfu.demo.open.source.mybatis.db.model.Blog
	at org.apache.ibatis.cache.decorators.SerializedCache.serialize(SerializedCache.java:100)
	at org.apache.ibatis.cache.decorators.SerializedCache.putObject(SerializedCache.java:56)
	at org.apache.ibatis.cache.decorators.LoggingCache.putObject(LoggingCache.java:54)
	at org.apache.ibatis.cache.decorators.SynchronizedCache.putObject(SynchronizedCache.java:45)
	at org.apache.ibatis.cache.decorators.TransactionalCache.flushPendingEntries(TransactionalCache.java:140)
	at org.apache.ibatis.cache.decorators.TransactionalCache.commit(TransactionalCache.java:122)
	at org.apache.ibatis.cache.TransactionalCacheManager.commit(TransactionalCacheManager.java:53)
	at org.apache.ibatis.executor.CachingExecutor.commit(CachingExecutor.java:126)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.commit(DefaultSqlSession.java:226)
	... 24 more
Caused by: java.io.NotSerializableException: com.gaoxinfu.demo.open.source.mybatis.db.model.Blog
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at java.util.ArrayList.writeObject(ArrayList.java:766)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1140)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at org.apache.ibatis.cache.decorators.SerializedCache.serialize(SerializedCache.java:96)
	... 32 more


Process finished with exit code 255
3.8.5.3.【Blog对象序列化】

在这里插入图片描述
再次执行testAfterCommit方法
在这里插入图片描述

3.8.6.总结

1.我们这里之所以查询后提交,一般没有这样操作的,只是我们这里相当于模拟了一下update等操作
  如果其他的session进行更新了数据之后,必然会进行commit,
  所以我们能够共享到其他session的数据

在这里插入图片描述

putObject操作只是将缓存加入到map中,并没有实际性的提交,只有实际性的调用commit方法之后,才会进入

TransactionalCacheManager.commit()


  public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.commit();
    }
  }

TransactionalCache.commit()

  public void commit() {
    if (clearOnCommit) {
      delegate.clear();
    }
    flushPendingEntries();
    reset();
  }

4.至此,Mybatis缓存已经讲解完毕,中间可能有些涉及到篇幅等问题,没有详细描述,欢迎大家留言讨论

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东山富哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值