?思維導圖:
?
? MyBatis 提供了一級緩存和二級緩存機制,用于提高數據庫查詢的性能,減少對數據庫的訪問次數。(本質上是減少IO次數)。
一級緩存
1. 概念
? 一級緩存也稱為會話緩存,它是基于 SqlSession 的緩存。在同一個 SqlSession 中,執行相同的 SQL 查詢時,MyBatis 會優先從一級緩存中獲取結果,而不是再次訪問數據庫。
2. 工作原理
2.1緩存結構:
? 在?SqlSession?內部,一級緩存是一個?PerpetualCache?對象,它本質上是一個?HashMap,鍵是根據查詢的 SQL 語句、參數、環境等信息生成的唯一標識,值是查詢結果。
? 查詢流程:當調用?SqlSession?的查詢方法時,MyBatis 會先將查詢的 SQL 語句、參數等信息組合成一個唯一的緩存鍵。然后在?PerpetualCache?這個?HashMap?中查找該鍵對應的值。如果找到了,就直接返回該值;如果沒找到,就會執行 SQL 查詢,將查詢結果存入?PerpetualCache?中,下次再執行相同查詢時就可以直接從緩存中獲取結果。
2.2 緩存命中的條件
相同的?SqlSession:必須是在同一個?SqlSession?實例中執行相同的查詢,一級緩存才會生效。
相同的 SQL 語句:查詢的 SQL 語句必須完全相同,包括 SQL 中的參數占位符和參數值。
相同的環境:查詢的環境(如數據庫連接、事務等)也必須相同。
代碼示例:
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;public class FirstLevelCacheDetailExample {public static void main(String[] args) throws Exception {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 創建第一個 SqlSessiontry (SqlSession sqlSession1 = sqlSessionFactory.openSession()) {// 第一次查詢User user1 = sqlSession1.selectOne("com.example.UserMapper.selectUserById", 1);System.out.println("第一次查詢結果: " + user1);// 第二次查詢,使用相同的 SqlSessionUser user2 = sqlSession1.selectOne("com.example.UserMapper.selectUserById", 1);System.out.println("第二次查詢結果: " + user2);// 兩次查詢結果相同,說明使用了一級緩存System.out.println("兩次查詢結果是否相同: " + (user1 == user2));// 執行更新操作sqlSession1.update("com.example.UserMapper.updateUser", new User(1, "New Name"));// 第三次查詢User user3 = sqlSession1.selectOne("com.example.UserMapper.selectUserById", 1);System.out.println("第三次查詢結果: " + user3);// 由于執行了更新操作,一級緩存已清空,user3 是重新查詢數據庫得到的結果System.out.println("第一次查詢結果和第三次查詢結果是否相同: " + (user1 == user3));}}
}
? 在上述代碼中,前兩次查詢使用相同的 SqlSession 和相同的查詢條件,所以第二次查詢會從一級緩存中獲取結果。而執行更新操作后,一級緩存被清空,第三次查詢會重新訪問數據庫。
3. 緩存失效情況
- 不同的?SqlSession:每個?SqlSession?都有自己獨立的一級緩存,不同的?SqlSession?之間的緩存是不共享的。
- 執行?insert、update、delete?操作:當在同一個?SqlSession?中執行這些操作時,會清空該?SqlSession?的一級緩存,以保證數據的一致
4. 優缺點
優點:
- 提高性能:在同一個 SqlSession 中多次執行相同查詢時,避免了重復的數據庫訪問,減少了數據庫的負載,提高了查詢性能。
- 簡單易用:一級緩存是 MyBatis 內置的,無需額外配置,默認開啟,使用方便。
缺點:
- 作用范圍小:只在同一個 SqlSession 中有效,不同的 SqlSession 之間無法共享緩存,限制了緩存的使用范圍。
- 數據一致性問題:如果在同一個 SqlSession 中執行了 insert、update、delete 操作,會清空該 SqlSession 的一級緩存,但如果在不同的 SqlSession 中對同一數據進行了修改,一級緩存可能會返回舊數據,導致數據不一致。
二級緩存
1. 工作原理
緩存結構:
二級緩存也是基于 PerpetualCache 實現的,但它是基于 SqlSessionFactory 的。每個 Mapper 可以有自己獨立的二級緩存,也可以多個 Mapper 共享同一個二級緩存。
查詢流程:當一個 SqlSession 執行查詢操作時,MyBatis 會先檢查該 Mapper 對應的二級緩存中是否存在該查詢結果。如果存在,則直接從二級緩存中獲取結果;如果不存在,則執行 SQL 查詢,并將查詢結果存入二級緩存中。在多個 SqlSession 之間,只要它們是由同一個 SqlSessionFactory 創建的,就可以共享二級緩存。
2. 配置詳解
全局配置:在 mybatis-config.xml 中開啟二級緩存的全局開關。
<settings><setting name="cacheEnabled" value="true"/>
</settings>
?Mapper 配置:在 Mapper 映射文件中配置緩存。
<mapper namespace="com.example.UserMapper"><!-- 開啟二級緩存,并配置相關屬性 --><cacheeviction="LRU" <!-- 緩存淘汰策略,這里使用最近最少使用策略 -->flushInterval="60000" <!-- 緩存刷新間隔,單位為毫秒 -->size="512" <!-- 緩存的最大對象數 -->readOnly="true" /> <!-- 是否只讀 --><select id="selectUserById" resultType="com.example.User">SELECT * FROM users WHERE id = #{id}</select>
</mapper>
- eviction:緩存淘汰策略,常見的有 LRU(最近最少使用)、FIFO(先進先出)等。
- flushInterval:緩存刷新間隔,指定多長時間清空一次緩存。
- size:緩存的最大對象數,當緩存中的對象數量超過該值時,會根據淘汰策略淘汰部分對象。
- readOnly:是否只讀。如果設置為 true,則緩存中的對象是只讀的,MyBatis 會直接返回緩存中的對象,不會進行序列化和反序列化操作,性能較高;如果設置為 false,則每次返回緩存中的對象時都會進行序列化和反序列化操作,保證返回的對象是一個新的實例,但性能相對較低。
3. 示例代碼及分析
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;public class SecondLevelCacheDetailExample {public static void main(String[] args) throws Exception {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 第一個 SqlSessiontry (SqlSession sqlSession1 = sqlSessionFactory.openSession()) {User user1 = sqlSession1.selectOne("com.example.UserMapper.selectUserById", 1);System.out.println("第一個 SqlSession 查詢結果: " + user1);}// 第二個 SqlSessiontry (SqlSession sqlSession2 = sqlSessionFactory.openSession()) {User user2 = sqlSession2.selectOne("com.example.UserMapper.selectUserById", 1);System.out.println("第二個 SqlSession 查詢結果: " + user2);// 兩次查詢結果相同,說明使用了二級緩存System.out.println("兩次查詢結果是否相同: " + (user1 == user2));}// 執行更新操作try (SqlSession sqlSession3 = sqlSessionFactory.openSession()) {sqlSession3.update("com.example.UserMapper.updateUser", new User(1, "New Name"));sqlSession3.commit(); // 提交事務,清空二級緩存}// 第三個 SqlSessiontry (SqlSession sqlSession4 = sqlSessionFactory.openSession()) {User user3 = sqlSession4.selectOne("com.example.UserMapper.selectUserById", 1);System.out.println("第三個 SqlSession 查詢結果: " + user3);// 由于執行了更新操作,二級緩存已清空,user3 是重新查詢數據庫得到的結果System.out.println("第一個查詢結果和第三個查詢結果是否相同: " + (user1 == user3));}}
}
?
? 在上述代碼中,第一個 SqlSession 執行查詢后,結果會存入二級緩存。第二個 SqlSession 執行相同查詢時,會從二級緩存中獲取結果。執行更新操作并提交事務后,二級緩存會被清空,第三個 SqlSession 執行查詢時會重新訪問數據庫。
3. 緩存失效情況
- 執行 insert、update、delete 操作:當執行這些操作時,會清空該 Mapper 對應的二級緩存,以保證數據的一致性。
- 緩存刷新策略:可以通過配置緩存的刷新策略,如 flushInterval 來定期清空緩存。
4. 優缺點
優點
- 作用范圍大:多個 SqlSession 可以共享二級緩存,減少了數據庫的訪問次數,提高了系統的整體性能。
- 可配置性強:可以通過配置不同的緩存淘汰策略、刷新間隔等參數,滿足不同的業務需求。
缺點
- 配置復雜:需要在全局配置和 Mapper 映射文件中進行配置,相對一級緩存來說配置較為復雜。
- 數據一致性問題:如果在不同的 Mapper 中對同一數據進行了修改,可能會導致二級緩存中的數據不一致,需要手動清空緩存或使用更復雜的緩存刷新策略。
5.一級緩存和二級緩存的比較
作用范圍:一級緩存是基于 SqlSession 的,作用范圍較小;二級緩存是基于 SqlSessionFactory 的,作用范圍較大。
緩存共享:一級緩存不共享,每個 SqlSession 有自己獨立的緩存;二級緩存可以在多個 SqlSession 之間共享。
開啟方式:一級緩存默認開啟;二級緩存需要手動配置開啟。
通過合理使用一級緩存和二級緩存,可以有效提高 MyBatis 應用的性能。但在使用緩存時,需要注意數據的一致性問題,避免出現臟數據。
總結
? MyBatis 的一級緩存和二級緩存各有優缺點,在實際應用中需要根據具體的業務場景合理使用。一級緩存適用于在同一個 SqlSession 中多次執行相同查詢的場景,而二級緩存適用于多個 SqlSession 之間共享緩存的場景。同時,需要注意緩存的使用可能會導致數據一致性問題,需要在業務邏輯中進行相應的處理。