mybatis一级缓存,无意中对缓存数据的修改,外部可直接修改内部缓存数据是否合理?
1、前置知识
大家都知道mybatisd的一级缓存。
- 一级缓存(Local Cache):
- 一级缓存是
SqlSession 级别
的缓存,它是 MyBatis 中默认启用且无法关闭的特性。 - 当在一个 SqlSession 生命周期内执行相同的 SQL 查询时,如果先前已经执行过该查询并且结果还在缓存中,MyBatis 将直接从缓存返回结果,而不再向数据库发送请求。
- 一级缓存的内容随着 SqlSession 的关闭而失效,另外,当执行任何 insert、update 或 delete 操作后,MyBatis 都会清空 SqlSession 中的一级缓存,以确保数据的一致性。
- 一级缓存是
2、问题复现
首先,数据库中有两条数据
这里使用的是原生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,但是结果确出人意料。。, 是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