mybatis的快取機制

小強Zzz發表於2023-04-02

前言

最近在使用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快取機制
(另外推薦一下美團技術團隊的文章,寫的都很好,大家可以閱讀一下,上面的第二篇文章和上次的雪花演算法都來自於那裡。美團技術團隊

相關文章