Mybatis系列之一级/二级缓存

缓存的概念大家应该都知道,所以,这里我们基于ORM框架Mybatis,来讲解一下他自带的缓存


一级缓存介绍

一级缓存是Mybatis默认开启的一个缓存机制,它跟二级缓存的区别就在于作用域大小不同,一级缓存的作用域相对比二级缓存要小,它的作用域只是基于SqlSession的(SqlSession主要是啥,后面再补充),缓存的存在主要是为了便利我们的数据查询,废话不多说,接下来我们来体验一下


一级缓存代码体验

源码

 //根据 sqlSessionFactory 产⽣ session
 SqlSession sqlSession = sessionFactory.openSession();

 UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

 //第⼀次查询,发出sql语句,并将查询出来的结果放进缓存中
 User u1 = userMapper.selectUserByUserId(1);
 System.out.println(u1);

 //第⼆次查询,由于是同⼀个sqlSession对象,所以会在缓存中查询结果
 //有两种处理逻辑,缓存如果有,则直接从缓存中取出来,不会走数据库,反之直接走数据库
 User u2 = userMapper.selectUserByUserId(1);
 System.out.println(u2);

 sqlSession.close();

观察日志

然后我们对user表在进行两次查询,和上面代码的区别就在于,在两次查询中间进行以此修改,再观察一下日志打印情况,先上源码:

 //根据 sqlSessionFactory 产⽣ session
 SqlSession sqlSession = sessionFactory.openSession();

 UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

 //第⼀次查询,发出sql语句,并将查询的结果放⼊缓存中
 User u1 = userMapper.selectUserByUserId( 1 );
 System.out.println(u1);

 //第⼆步进⾏了⼀次更新操作,sqlSession.commit()
 u1.setSex("⼥");
 userMapper.updateUserByUserId(u1);
 sqlSession.commit();

 //第⼆次查询,由于是同⼀个sqlSession,且上面的修改操作触发了sqlSession.commit(),
 //所以在commit之后会清空缓存信息
 //则此次查询也会发出sql语句
 User u2 = userMapper.selectUserByUserId(1);
 System.out.println(u2);

 sqlSession.close();

日志打印:

Mybatis系列之一级/二级缓存_第1张图片

这两次源码进行查询的代码和日志对比就发现了:如果执行了新增、更新或者删除,sqlSession就会commit,默认会清空SqlSession中的一级缓存,这样做的目的,大家了解缓存的都清楚,这么做就是为了防止读取的数据不是最新的,避免了脏读


源码剖析 

1、查看SqlSession类中的方法

public interface SqlSession extends Closeable {
   T selectOne(String statement);
   T selectOne(String statement, Object parameter);
   List selectList(String statement);
   List selectList(String statement, Object parameter);
   List selectList(String statement, Object parameter, RowBounds rowBounds);
   Map selectMap(String statement, String mapKey);
   Map selectMap(String statement, Object parameter, String mapKey);
   Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
   Cursor selectCursor(String statement);
   Cursor selectCursor(String statement, Object parameter);
   Cursor selectCursor(String statement, Object parameter, RowBounds rowBounds);
  void select(String statement, Object parameter, ResultHandler handler);
  void select(String statement, ResultHandler handler);
  void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
  int insert(String statement);
  int insert(String statement, Object parameter);
  int update(String statement);
  int update(String statement, Object parameter);
  int delete(String statement);
  int delete(String statement, Object parameter);
  void commit();
  void commit(boolean force);
  void rollback();
  void rollback(boolean force);
  List flushStatements();
  @Override
  void close();
  void clearCache();
  Configuration getConfiguration();
   T getMapper(Class type);
  Connection getConnection();
}

乍看一下这些方法,能找到的唯一跟缓存有关系的也就是倒数第四个方法clearCache,看方法名就是清理缓存,既然只有这个方法跟缓存有关系,那我们就从它开始分析,它的父类的调用流程这里就不展示了

只需要知道有一个类PerpetualCache,看一下源码

public class PerpetualCache implements Cache {

  private String id;

  private Map cache = new HashMap();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

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

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  @Override
  public void clear() {
    cache.clear();
  }

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

  @Override
  public boolean equals(Object o) {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    if (this == o) {
      return true;
    }
    if (!(o instanceof Cache)) {
      return false;
    }

    Cache otherCache = (Cache) o;
    return getId().equals(otherCache.getId());
  }

  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }

}

最开始会声明一个HashMap的全局变量,然后还有一个clear方法,看进去其实它调用的是上面HashMap对象的clear,清空一级缓存其实就是清空Map数据,一级缓存其实就是本地存放的一个Map对象

那清空缓存ok了,创建缓存是谁负责在什么时候创建的呢?其他拐弯抹角的话不说了,我们直接看执行器Executor

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;

  int update(MappedStatement ms, Object parameter) throws SQLException;

   List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

   List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

   Cursor queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

  List flushStatements() throws SQLException;

  void commit(boolean required) throws SQLException;

  void rollback(boolean required) throws SQLException;

  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

  boolean isCached(MappedStatement ms, CacheKey key);

  void clearLocalCache();

  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class targetType);

  Transaction getTransaction();

  void close(boolean forceRollback);

  boolean isClosed();

  void setExecutorWrapper(Executor executor);

}

上面的类中,有一个createCacheKey,看名字就知道是创建缓存,我们点进去看一下这个方法的实现

@Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    //封装mapper.xml中我们写标签中的namespace+id的值,