Mybatis 之 一级缓存

1、一级缓存

1.1、在一个sqlSession中,对User表根据id进行两次查询,查看他们发出sql语句的情况

@Test
public void test1() {
// 根据 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();
    }

查看控制台打印情况:

1.2、同样是对user表进行两次查询,只不过两次查询之间进行了一次update操作。 

   @Test
    public void test2() {
    // 根据 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.commit(),会清空缓存信息
    // 则此次查询也会发出sql语句
        User u2 = userMapper.selectUserByUserId(1);
        System.out.println(u2);
        sqlSession.close();
    }

查看控制台打印情况:

1.3、总结

  1. 第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从 数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
  2. 如果中间sqlSession去执行commit操作(执行插入、更新、删除),则会清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
  3. 第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直 接从存中获取用户信息 

2、一级缓存原理探究与源码分析

        一级缓存到底是什么?一级缓存什么时候被创建、一级缓存的工作流程是怎样的?相信你现在应该会有这几个疑问,那么我们本节就来研究一下一级缓存的本质

        大家可以这样想,上面我们一直提到一级缓存,那么提到一级缓存就绕不开SqlSession,所以索性我们就直接从SqlSession,看看有没有创建缓存或者与缓存有关的属性或者方法。

        调研了一圈,发现上述所有方法中,好像只有clearCache()和缓存沾点关系,那么就直接从这个方法入手吧,分析源码时,我们要看它(此类)是谁,它的父类和子类分别又是谁,对如上关系了解了,你才 会这个类有更深的认识,分析了一圈,你可能会得到如下这个流程图。

         再深⼊分析,流程走到Perpetualcache中的clear()方法之后,会调⽤其cache.clear()方法,那 么这个cache是什么东西呢?点进去发现,cache其实就是private Map cache = new HashMap();也就是一个Map,所以说cache.clear()其实就是map.clear(),也就是说,缓存其实就是本地存放的一个map对象,每一个SqISession都会存放⼀个map对象的引⽤,那么这个cache是何时创建的呢?

        你觉得最有可能创建缓存的地方是哪里呢?我觉得是Executor,为什么这么认为?因为Executor是 执行器,用来执行SQL请求,而且清除缓存的方法也在Executor中执行,所以很可能缓存的创建也很 有可能在Executor中,看了一圈发现Executor中有一个createCacheKey方法,这个方法很像是创 建缓存的方法啊,跟进去看看,你发现createCacheKey方法是由BaseExecutor执行的,代码如下 

CacheKey cacheKey = new CacheKey();
//MappedStatement 的 id
// id就是Sql语句的所在位置包名+类名+ SQL名称
cacheKey.update(ms.getId());
// offset 就是 0
cacheKey.update(rowBounds.getOffset());
// limit 就是 Integer.MAXVALUE
cacheKey.update(rowBounds.getLimit());
//具体的SQL语句
cacheKey.update(boundSql.getSql());
//后⾯是update 了 sql中带的参数
cacheKey.update(value);
...
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}

        创建缓存key会经过一系列的update方法,udate方法由一个CacheKey这个对象来执行的,这个update方法最终由updateList的list来把五个值存进去,对照上面的代码和下面的图示,你应该能 理解五个值都是什么了。

        这里需要注意一下最后一个值,configuration.getEnvironment().getId()这是什么,这其实就是 定义在mybatis-config.xml中的标签,见如下。 

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </dataSource>
    </environment>
</environments>

        那么我们回归正题,那么创建完缓存之后该用在何处呢?总不会凭空创建一个缓存不使用吧?绝对不会的,经过我们对⼀级缓存的探究之后,我们发现⼀级缓存更多是用于查询操作,毕竟一级缓存也叫做查询缓存吧,为什么叫查询缓存我们一会儿说。我们先来看一下这个缓存到底用在哪了,我们跟踪到query方法如下:

@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 {
  ...
  list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; 
if (list != null) {
    //这个主要是处理存储过程⽤的。
  handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
  } else {
  list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, boundSql);
  }   ...
}
// queryFromDatabase ⽅法
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;
}

        如果查不到的话,就从数据库查,在queryFromDatabase中,会对localcache进行写入。localcache对象的put方法最终交给Map进行存放

private Map<Object, Object> cache = new HashMap<Object, Object>();
    @Override
    public void putObject(Object key, Object value) { cache.put(key, value);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

悠然予夏

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

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

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

打赏作者

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

抵扣说明:

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

余额充值