前言

最近在使用mybatis的时候发现了一个问题:当我进行更新操作时,通过id查询条件查出一个User对象,并修改user的姓名,在进行update函数前,通过切面去记录他的变更信息到变更记录表中。

public User update(User user) {
    User oldUser = new User();
    oldUser.setId(user.getId());
    oldUser = this.daoSupport.selectOne(oldUser);
    oldUser.setName(user.getName());
    return this.daoSupport.update(oldUser);
}

在切面中,通过这个对象的主键去数据库查询到变更前的的值,和变更后的值做对比,不相同的属性即为实际变更的属性。但是发现,在切面里通过主键查询出来的对象的各个属性值为变更后的值,这明明是在update之前的切面,变更内容并没有保存到数据库中,为何查询出来的值为变更后的值呢?
为了排除切面的影响,我在查询并修改user的姓名后,在update语句前,再次查询user。

public User update(User user) {
    User oldUser = new User();
    oldUser.setId(user.getId());
    oldUser = this.daoSupport.selectOne(oldUser);
    oldUser.setName(user.getName());
    User oldUser1 = new User();
    oldUser1.setId(user.getId());
    oldUser1 = this.daoSupport.selectOne(oldUser1);
    return this.daoSupport.update(oldUser);
}

此时,也确实是变更后的值,并且通过断点发现两个user为同一个对象,也就是说对象指针地址一样,那肯定是变更后的值。猜测是存在着一种缓存机制,使得第二次查询使用了第一次查询的结果,但是这个结果是一个对象指针。

MyBatis缓存

再网上查询得知,mybatis确实有一种缓存机制,并且分为一级缓存和二级缓存。

在了解一级缓存前,先了解一下mybatis的SqlSession。
在mybatis的基础写法中,我们都是先获取SqlSession,然后通过SqlSession再去进行我的定义的mapper操作,当然DaoSupport定义了一些基本mapper操作且不需要手动定义SqlSession,但是本质上也是通过SqlSession对数据库进行操作。

通过阅读源码得知,mybatis的一级缓存,就是在一个SqlSession的生命周期内,在进行一次查询时,将查询结果和查询条件存起来,在进行下一次查询时,先去匹配查询条件,若查询条件相同,便不在去查询数据库,而是将上一次的查询结果返回,当然这里返回的是对象指针。那么就不难理解出现的问题了。

解决

解决很好解决。
一种方法是全局关闭缓存,设置 mybatis.configuration.local-cache-scope=statement

一种方法时是第二次查询前清空缓存。

SqlSession sqlSession = SqlSessionUtils.getSqlSession(sqlSessionFactory);
sqlSession.clearCache();

其中sqlSessionFactory时通过依赖注入的。

源码阅读

关于详细机制阅读,推荐两篇文章
【不懂就问】MyBatis的一级缓存竟然还会引来麻烦?
聊聊MyBatis缓存机制
(另外推荐一下美团技术团队的文章,写的都很好,大家可以阅读一下,上面的第二篇文章和上次的雪花算法都来自于那里。美团技术团队


小强Zzz
1.2k 声望32 粉丝