likes
comments
collection
share

mybatis一级缓存,无意中对缓存数据的修改,外部可直接修改内部缓存数据是否合理?

作者站长头像
站长
· 阅读数 5

1、前置知识

大家都知道mybatisd的一级缓存。

  1. 一级缓存(Local Cache):
    • 一级缓存是SqlSession 级别的缓存,它是 MyBatis 中默认启用且无法关闭的特性。
    • 当在一个 SqlSession 生命周期内执行相同的 SQL 查询时,如果先前已经执行过该查询并且结果还在缓存中,MyBatis 将直接从缓存返回结果,而不再向数据库发送请求。
    • 一级缓存的内容随着 SqlSession 的关闭而失效,另外,当执行任何 insert、update 或 delete 操作后,MyBatis 都会清空 SqlSession 中的一级缓存,以确保数据的一致性。

2、问题复现

首先,数据库中有两条数据 mybatis一级缓存,无意中对缓存数据的修改,外部可直接修改内部缓存数据是否合理?

这里使用的是原生mybatis,方便复现问题。

public class Demo {
    public static void main(String[] args) throws IOException {
        String resouce = "mybatis-config.xml";
        InputStream in = Resources.getResourceAsStream(resouce);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        SqlSession session = sqlSessionFactory.openSession();
        UserInfoMapper userInfoMapper = session.getMapper(UserInfoMapper.class);
        List<UserInfo> userInfoList1 = userInfoMapper.selectAll();
        System.out.println("userInfoList1: " + userInfoList1.size());
        userInfoList1.add(new UserInfo(3, "李华", 30));
        List<UserInfo> userInfoList2 = userInfoMapper.selectAll();
        System.out.println("userInfoList2: " + userInfoList2.size());
    }
}

到这里,大家可以猜一下userInfoList1和userInfoList2的size的输出是多少,我原本认为的是都是2,但是结果确出人意料。。, mybatis一级缓存,无意中对缓存数据的修改,外部可直接修改内部缓存数据是否合理? 是2和3,并且userInfoList1和userInfoList2是同一个对象。

3、分析

看到这应该也都能想到是mybatis的一级缓存导致的

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 {
    // 执行sql查询方法
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    localCache.removeObject(key);
  }
  // 查询到list后加入缓存
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  // 返回list
  return list;
}

从源码中可以看到mybatis加入缓存的list和返回结果的list是同一个引用,也就是同一个对象。 这也解释了为什么userInfoList1的size是2,但是userInfoList2的size是3;查询到userInfoList1的两条数据之后,缓存userInfoList1;对userInfoList1新增一条数据,此时userInfoList1的size就是3,再次查询,命中缓存,返回userInfoList1,userInfoList2的size就是3。

4、思考

mybatis一级缓存的数据引用直接暴露在外,外部可直接进行修改,这样是否合理呢?

而且稍有不慎像示例中这样无意中对数据进行了修改,那么后续同一个sqlsession的这个查询全是错误数据。虽然mybatis在和spring进行整合后,对sqlsession进行了线程安全的处理,但同一线程下拿到查询的数据后进行修改,缓存的数据也同样被修改,一样会出现问题。

个人看法:像这种比较重要的缓存数据通常在mybatis内部单独复制一份缓存起来更合理些,这样缓存数据不对外暴露,更安全;对返回数据进行修改后也不会影响缓存数据,减少问题的出现。

以上是个人见解,当然,mybatis作为一个成熟的orm框架,这样做应该也有原因,欢迎大家进行讨论,指点迷津。

转载自:https://juejin.cn/post/7352091152584245259
评论
请登录