『手撕 Mybatis 源码』10 - 一级缓存

一级缓存

概述

在这里插入图片描述

  1. 一级缓存是 SqlSession 级别的缓存。在操作数据库时需要构造 SqlSession 对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的 SqlSession 之间的缓存数据区域(HashMap)是互相不影响的
  2. 二级缓存是 Mapper 级别的缓存,多个 SqlSession 去操作同一个 Mapper 的sql语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession
  3. 首先新增测试用例 firstLevelCacheTest,然后同时会发现如果使用(增删改)操作,不管是否已经 commit(),都会清理一级缓存
public class CacheTest {

  /**
   * 测试一级缓存
   */
  @Test
  public void firstLevelCacheTest() throws IOException {

    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

    // 2. (1)解析了配置文件,封装configuration对象 (2)创建了DefaultSqlSessionFactory工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    // 3.问题:openSession()执行逻辑是什么?
    // 3. (1)创建事务对象 (2)创建了执行器对象cachingExecutor (3)创建了DefaultSqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 发起第一次查询,查询ID为1的用户
    User user1 = sqlSession.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);

    // 更新操作(通过增删改执行操作,会清除缓存)
    User user = new User();
    user.setId(1L);
    user.setName("tom");
    sqlSession.update("com.itheima.mapper.UserMapper.updateUser",user);
    sqlSession.commit();

    // 发起第二次查询,查询ID为1的用户
    User user2 = sqlSession.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);

    System.out.println(user1 == user2);
    System.out.println(user1);
    System.out.println(user2);

    sqlSession.close();
  }
}

源码分析

  1. 首先我们主要关注下面的的流程,看看一级缓存的行为,首先执行一次 sqlSession.selectOne() 查询,然后执行一次更新语句,再次执行一次查询
public class CacheTest {

  /**
   * 测试一级缓存
   */
  @Test
  public void firstLevelCacheTest() throws IOException {
    ...
    // 发起第一次查询,查询ID为1的用户  
    User user1 = sqlSession.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);

    // 更新操作(通过增删改执行操作,会清除缓存)
    User user = new User();
    user.setId(1L);
    user.setName("tom");
    sqlSession.update("com.itheima.mapper.UserMapper.updateUser",user);
    sqlSession.commit();

    // 发起第二次查询,查询ID为1的用户
    User user2 = sqlSession.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);
   ...
  }
}
  1. 第一次调用 sqlSession.selectOne() 时,将会嵌套调用 selectList() 方法,得到 MappedStatement 后,使用 Executor 来执行查询
public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;
  ...
  private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      // 1. 根据传入的 statementId 即 user.findUserById,获取 MappedStatement 对象
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 2. 调用执行器的查询方法
      // wrapCollection(parameter)是用来装饰集合或者数组参数
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}
  1. Executor 实际使用的是 CachingExecutor,执行前,先生成 CacheKey,然后委托 SimpleExecutor 进行查询
public class CachingExecutor implements Executor {

  private final Executor delegate;
  ...
  //第一步
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取绑定的 SQL 语句,比如 "SELECT * FROM user WHERE id = ? "
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 1. 生成缓存Key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    ...
    // 2. 委托给 BaseExecutor 执行
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
}
  1. SimpleExecutor 首先从一级缓存 localCache(就是 PerpetualCache,一个 Map)拿取数据,如果获取不了数据就会从数据库查询结果
public abstract class BaseExecutor implements Executor {
  ...
  protected PerpetualCache localCache;
  ...
  @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.");
    }
    // 如果配置了 flushCacheRequired为 true,则会在执行器执行之前就清空本地一级缓存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      // 清空缓存
      clearLocalCache();
    }
    List<E> list;
    try {
      // 查询堆栈 + 1
      queryStack++;
      // 1. 从一级缓存中获取数据
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        // 已有缓存结果,则处理本地缓存结果输出参数(只有存储过程会走)
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 2. 没有缓存结果,则从数据库查询结果
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      // 查询堆栈数 -1
      queryStack--;
    }
    ...
    return list;
  }
}
  1. 查询数据的时候,首先会在一级缓存 localCache 插入占位符,然后从数据库查出数据后,将查询数据存入缓存中
public abstract class BaseExecutor implements Executor {
  ...
  protected PerpetualCache localCache;
  ...
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list; // key:1463130193:-1799038108:com.itheima.mapper.UserMapper.findByCondition:0:2147483647:SELECT id, name FROM  user WHERE id = ?:1:development
    // 1. 首先向本地缓存中存入一个 ExecutionPlaceholder 的枚举类占位 value
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      // 2. 执行doQuery方法
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      // 3. 执行完成移除这个key
      localCache.removeObject(key);
    }
    // 4. 查询结果存入缓存中
    localCache.putObject(key, list);
    // 如果 MappedStatement 的类型为CALLABLE,则向 localOutputParameterCache 缓存中存入 value 为 parameter 的缓存
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
}
  1. 查询到数据后,轮到执行更新语句
public class CacheTest {

  /**
   * 测试一级缓存
   */
  @Test
  public void firstLevelCacheTest() throws IOException {
    ...
    // 更新操作(通过增删改执行操作,会清除缓存)
    User user = new User();
    user.setId(1L);
    user.setName("tom");
    sqlSession.update("com.itheima.mapper.UserMapper.updateUser",user);
    sqlSession.commit();
   ...
  }
}
  1. 更新语句的执行同样创建好 MappedStatement 后,由 DefaultSqlSession 交由 CachingExecutor 执行更新操作
public class DefaultSqlSession implements SqlSession {
  ...
  private final Executor executor;
  ...
  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 1. 执行更新
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}
  1. CachingExecutor 再委派 SimpleExecutor 执行更新
public class CachingExecutor implements Executor {
  ...
  private final Executor delegate;
  ...
  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms);
    // 1. 执行更新
    return delegate.update(ms, parameterObject);
  }
}
  1. SimpleExecutor 执行更新前,就会先去清除一级缓存,不管是否需要 commit(),然后再执行更新操作
public abstract class BaseExecutor implements Executor {
  ...
  protected PerpetualCache localCache;
  ...
  @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.");
    }
    // 1. 先清理一级缓存
    clearLocalCache();
    // 2. 再执行更新操作
    return doUpdate(ms, parameter);
  }
  
  @Override
  public void clearLocalCache() {
    if (!closed) {
      // 1.1 清理一级缓存
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }
}
  1. 从一级缓存 PerpetualCache 的清理,就完全可以看到,一级缓存就是个 Map 对象保存
public class PerpetualCache implements Cache {

  private final String id;
  private final Map<Object, Object> cache = new HashMap<>();
  ...
  @Override
  public void clear() {
    cache.clear();
  }
}
  1. 更新完数据后,然后执行 commit() 操作
public class CacheTest {

  /**
   * 测试一级缓存
   */
  @Test
  public void firstLevelCacheTest() throws IOException {
    ...
    sqlSession.commit();
   ...
  }
}
  1. DefaultSqlSession 再次委托 CachingExecutor 执行 commit() 方法
public class DefaultSqlSession implements SqlSession {

  private final Executor executor;
  ...
  @Override
  public void commit() {
    commit(false);
  }
  @Override
  public void commit(boolean force) {
    try {
      // 1. 执行 commit() 方法
      executor.commit(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}
  1. CachingExecutor 则委派 SimpleExecutor 来执行 commit() 方法
public class CachingExecutor implements Executor {
  
  private final Executor delegate;
  ...
  @Override
  public void commit(boolean required) throws SQLException {
    // 1. 执行提交操作
    delegate.commit(required);
    tcm.commit();
  }
}
  1. SimpleExecutor 又会再次清理一级缓存,最后才会执行 commit() 方法
public class CachingExecutor implements Executor {

  private final Executor delegate;
  ...
  @Override
  public void commit(boolean required) throws SQLException {
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    // 1. 一级缓存清理
    clearLocalCache();
    flushStatements();
    if (required) {
      transaction.commit();
    }
  }
  
  @Override
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }
}
  1. 更新操作后,再一次执行查询 selectOne() 的流程,但由于一级缓存清空了,所以就没办法从一级缓存获取数据,又会再次从数据库中查询,然后存入一级缓存中。具体流程看回上面的查询流程
  2. 总结
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值