MyBatis緩存原來是這麼一回事兒


什麼是緩存

緩存就是存儲數據的一個地方(稱作:Cache),當程序要讀取數據時,會首先從緩存中獲取,有則直接返回,否則從其他存儲設備中獲取。

緩存的特點:緩存最重要的一點就是從其內部獲取數據的速度是非常快的,通過緩存可以加快數據的訪問速度。

mybatis中的緩存

通常情況下mybatis會訪問數據庫獲取數據,中間涉及到網絡通信,數據庫從磁盤中讀取數據,然後將數據返回給mybatis,總的來說耗時還是挺長的,mybatis爲了加快數據查詢的速度,在其內部引入了緩存來加快數據的查詢速度。

mybatis中分爲一級緩存和二級緩存:

一級緩存是SqlSession級別的緩存,在操作數據庫時需要構造 sqlSession對象,在對象中有一個數據結構(HashMap)用於存儲緩存數據,不同的sqlSession之間的緩存數據區域是互相不影響的。

二級緩存是mapper級別的緩存,多個SqlSession去操作同一個Mapper的sql語句,多個SqlSession可以共用二級緩存,二級緩存是跨SqlSession的。

mybatis一級緩存

一級緩存是SqlSession級別的緩存,每個SqlSession都有自己單獨的一級緩存,多個SqlSession之間的一級緩存是相互隔離的,互不影響,mybatis中一級緩存是默認自動開啓的。

一級緩存工作原理:在同一個SqlSession中去多次去執行同樣的查詢,每次執行的時候會先到一級緩存中查找,如果緩存中有就直接返回,如果一級緩存中沒有相關數據,mybatis就會去db中進行查找,然後將查找到的數據放入一級緩存中,第二次執行同樣的查詢的時候,會發現緩存中已經存在了,會直接返回。一級緩存的存儲介質是內存,是用一個HashMap來存儲數據的,所以訪問速度是非常快的。

一級緩存案例

sql源文件如下:

DROP TABLE IF EXISTS `t_student`;
CREATE TABLE `t_student`  (
  `id` int(32) NOT NULL AUTO_INCREMENT,
  `student_id` int(32) NOT NULL,
  `student_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `age` int(32) NOT NULL,
  `gender` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `class_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `t_student` VALUES (1, 2018013001, '張三', 18, '男', '301');
INSERT INTO `t_student` VALUES (2, 2018013002, '李四', 19, '男', '301');
INSERT INTO `t_student` VALUES (3, 2018013003, '王芳', 18, '女', '302');
INSERT INTO `t_student` VALUES (4, 2018013004, '李娟', 19, '女', '301');

下面是查詢用戶信息mapper文件內容

 <!--查詢單個-->
 <select id="queryById" resultMap="StudentMap">
     select
       id, student_id, student_name, age, gender, class_name
     from mybatis.t_student
     where id = #{id}
 </select>

測試用例

@Test
    public void testStudentDao() {
        // 通過工具類生成SqlSession對象
        SqlSession session = MybatisUtils.getSession();
        // 測試查詢方法
        Student stu1 = session.selectOne("com.lwz.mapper.StudentDao.queryById", 1);
        System.out.println(stu1.getStudentName());
        Student stu2 = session.selectOne("com.lwz.mapper.StudentDao.queryById", 1);
        System.out.println(stu2.getStudentName());

		System.out.println(stu1 == stu2);

        session.commit();
        session.close();
    }

上面的代碼在同一個SqlSession中去執行了2次獲取用戶列表信息,2次查詢結果分別放在stu1和stu2,最終代碼中也會判斷這兩個結果是否相等,下面我們運行一下看看會訪問幾次db?

運行輸出:
在這裏插入圖片描述
從輸出中可以看出看到,sql只輸出了一次,說明第一次會訪問數據庫,第二次直接從緩存中獲取的,最後輸出了一個true,也說明兩次返回結果是同一個對象,第二次直接從緩存中獲取數據的,加快了查詢的速度。

清空一級緩存的3種方式

同一個SqlSession中查詢同樣的數據,mybatis默認會從一級緩存中獲取,如果緩存中沒有,纔會訪問db,那麼我們如何去清除一級緩存呢,強制讓查詢去訪問db呢?

  1. SqlSession中執行增、刪、改操作,此時sqlsession會自動清理其內部的一級緩存
  2. 調用SqlSession中的clearCache方法清理其內部的一級緩存
  3. 設置Mapper xml中select元素的flushCache屬性值爲true,那麼執行查詢的時候會先清空一級緩存中的所有數據,然後去db中獲取數據

下面我們來分別演示這3種情況

方式1:增刪操作讓一級緩存失效

當執行增刪改操時,mybatis會將當前SqlSession一級緩存中的所有數據都清除。

案例代碼:

@Test
    public void testStudentDao() {
        // 通過工具類生成SqlSession對象
        SqlSession session = MybatisUtils.getSession();

        //第一次查詢
        Student stu1 = session.selectOne("com.lwz.mapper.StudentDao.queryById", 1);
        System.out.println(stu1.getStudentName());

        int i = session.delete("com.lwz.mapper.StudentDao.deleteById", 4);
        System.out.println(i);

        //第二次查詢
        Student stu2 = session.selectOne("com.lwz.mapper.StudentDao.queryById", 1);
        System.out.println(stu2.getStudentName());

        System.out.println(stu1 == stu2);

        session.commit();
        session.close();

    }

運行輸出:
在這裏插入圖片描述
從輸出中可以看出2次查詢都訪問了db,並且兩次查詢的結果集(false)是不一樣的。刪除數據讓緩存失效是可以理解的,刪除操作可能會改變數據庫中的數據,所以如果再從緩存中去獲取,可能獲取到的數據和db中的數據不一致的情況,mybatis爲了避免這種情況,在執行刪除操作的時候,會將SqlSession中的一級緩存清空。插入和修改也是一樣的。

方式2:SqlSession.clearCache清理一級緩存

SqlSession.clearCache()方法會將當前SqlSession一級緩存中的所有數據清除。

案例代碼:

@Test
    public void testStudentDao() {
        // 通過工具類生成SqlSession對象
        SqlSession session = MybatisUtils.getSession();

        //第一次查詢
        Student stu1 = session.selectOne("com.lwz.mapper.StudentDao.queryById", 1);
        System.out.println(stu1.getStudentName());

        //調用clearCache方法清理當前SqlSession中的緩存
        session.clearCache();

        //第二次查詢
        Student stu2 = session.selectOne("com.lwz.mapper.StudentDao.queryById", 1);
        System.out.println(stu2.getStudentName());

        System.out.println(stu1 == stu2);

        session.commit();
        session.close();

    }

運行輸出:
在這裏插入圖片描述
從輸出中可以看出,2次同樣的查詢都訪問了db。

方式3:Select元素的flushCache置爲true

將Mapper xml中select元素的flushCache屬性置爲true的時候,每次執行這個select元素對應的查詢之前,mybatis會將當前SqlSession中一級緩存中的所有數據都清除。

案例代碼

<!--查詢單個-->
<!--新增一個select元素的查詢,將flushCache元素置爲true,注意:select元素這個屬性的默認值是false-->
<select id="queryById" flushCache="true" resultMap="StudentMap">
    select
      id, student_id, student_name, age, gender, class_name
    from mybatis.t_student
    where id = #{id}
</select>

對應測試用例

@Test
    public void testStudentDao() {
        // 通過工具類生成SqlSession對象
        SqlSession session = MybatisUtils.getSession();

        //第一次查詢
        Student stu1 = session.selectOne("com.lwz.mapper.StudentDao.queryById", 1);
        System.out.println(stu1.getStudentName());

        //第二次查詢
        Student stu2 = session.selectOne("com.lwz.mapper.StudentDao.queryById", 1);
        System.out.println(stu2.getStudentName());

        System.out.println(stu1 == stu2);

        session.commit();
        session.close();

    }

運行輸出:
在這裏插入圖片描述
從輸出中可以看出,2次同樣的查詢都訪問了db。

一級緩存使用總結

1:一級緩存是SqlSession級別的,每個人SqlSession有自己的一級緩存,不同的SqlSession之間一級緩存是相互隔離的

2:mybatis中一級緩存默認是自動開啓的

4:當在同一個SqlSession中執行同樣的查詢的時候,會先從一級緩存中查找,如果找到了直接返回,如果沒有找到會去訪問db,然後將db返回的數據丟到一級緩存中,下次查詢的時候直接從緩存中獲取

5:一級緩存清空的3種方式(1:SqlSession中執行增刪改會使一級緩存失效;2:調用SqlSession.clearCache方法會使一級緩存失效;3:Mapper xml中的select元素的flushCache屬性置爲true,那麼執行這個查詢會使一級緩存失效)


mybatis二級緩存

二級緩存的使用

一級緩存使用上存在侷限性,必須要在同一個SqlSession中執行同樣的查詢,一級緩存才能提升查詢速度,如果想在不同的SqlSession之間使用緩存來加快查詢速度,此時我們需要用到二級緩存了

二級緩存是mapper級別的緩存,每個mapper xml有個namespace,二級緩存和namespace綁定的,每個namespace關聯一個二級緩存,多個SqlSession可以共用二級緩存,二級緩存是跨SqlSession的。
在這裏插入圖片描述
二級緩存默認是沒有開啓的,需要我們在mybatis全局配置文件中進行開啓:

<settings>
    <!-- 開啓二級緩存 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

上面配置好了以後,還需要在對應的mapper xml加上下面配置,表示這個mapper中的查詢開啓二級緩存:

<cache/>

經過上面兩個步驟,就完成了二級緩存的開啓操作。

一二級緩存共存時查詢原理

一二級緩存如果都開啓的情況下,數據查詢過程如下:

1:當發起一個查詢的時候,mybatis會先訪問這個namespace對應的二級緩存,如果二級緩存中有數據則直接返回,否則繼續向下
2:查詢一級緩存中是否有對應的數據,如果有則直接返回,否則繼續向下
3:訪問db獲取需要的數據,然後放在當前SqlSession對應的二級緩存中,並且在本地內存中的另外一個地方存儲一份(這個地方我們就叫TransactionalCache)
4:當SqlSession關閉的時候,也就是調用SqlSession的close方法的時候,此時會將TransactionalCache中的數據放到二級緩存中,並且會清空當前SqlSession一級緩存中的數據。

二級緩存案例

mybatis全局配置文件開啓二級緩存配置

<settings>
    <!-- 開啓二級緩存 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

映射文件中使用cache元素開啓二級緩存

<!-- 啓用二級緩存 -->
<cache></cache>

<!--查詢單個-->
<!--新增一個select元素的查詢,將flushCache元素置爲true,注意:select元素這個屬性的默認值是false-->
<select id="queryById" resultMap="StudentMap">
    select
      id, student_id, student_name, age, gender, class_name
    from mybatis.t_student
    where id = #{id}
</select>

測試用例

@Test
public void testStudentDao() {

    // 通過工具類生成SqlSession對象
    SqlSession session = MybatisUtils.getSession();
    //第一次查詢
    StudentDao studentDao = session.getMapper(StudentDao.class);
    Student student = studentDao.queryById(1);
    System.out.println(student.getStudentName());
    //關閉session
    session.close();

    SqlSession session1 = MybatisUtils.getSession();
    //第二次查詢
    StudentDao studentDao1 = session1.getMapper(StudentDao.class);
    Student student1 = studentDao1.queryById(1);
    System.out.println(student1.getStudentName());

}

上面執行了2次查詢,每次查詢都是新的SqlSession,運行一下看看效果。

執行輸出

DEBUG [main] - Cache Hit Ratio [com.lwz.dao.StudentDao]: 0.0
DEBUG [main] - ==>  Preparing: select id, student_id, student_name, age, gender, class_name from mybatis.t_student where id = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
張三
DEBUG [main] - Cache Hit Ratio [com.lwz.dao.StudentDao]: 0.5
張三

注意上面第一行日誌輸出:

DEBUG [main] - Cache Hit Ratio [com.lwz.dao.StudentDao]: 0.0

對這行做一個解釋:com.lwz.dao.StudentDao是上面查詢訪問的mapper xml的namesapce的值,去這個namespace對應的二級緩存中去查詢數據,沒有查詢到,輸出中的0.0表示命中率,這次沒有命中,所以命中率爲0

然後就去db中訪問數據了,會將db中返回的數據放在一級緩存中,第一次運行完畢之後會調用SqlSession的close方法,然後db中返回的數據會被丟到二級緩存中,第二次查詢的時候就直接從二級緩存中獲取到數據返回了,所以第二次查詢輸出如下:
在這裏插入圖片描述
2次查詢都去訪問了二級緩存,第二次命中了,命中率爲1/2=0.5

清空或者跳過二級緩存的3種方式

當二級緩存開啓的時候,在某個mapper xml中添加cache元素之後,這個mapper xml中所有的查詢都默認開啓了二級緩存,那麼我們如何清空或者跳過二級緩存呢?3種方式如下:

1:對應的mapper中執行增刪改查會清空二級緩存中數據

2:select元素的flushCache屬性置爲true,會先清空二級緩存中的數據,然後再去db中查詢數據,然後將數據再放到二級緩存中

3:select元素的useCache屬性置爲false,可以使這個查詢跳過二級緩存,然後去查詢數據

方式1:增刪改會清除二級緩存中的數據

案例代碼

@Test
    public void testStudentDao() {

        // 通過工具類生成SqlSession對象
        SqlSession session = MybatisUtils.getSession();
        //第一次查詢
        StudentDao studentDao = session.getMapper(StudentDao.class);
        Student student = studentDao.queryById(1);
        System.out.println(student.getStudentName());

        SqlSession session2 = MybatisUtils.getSession();
        StudentDao studentDao2 = session2.getMapper(StudentDao.class);
        Student student3 = new Student();
        student3.setAge(10);
        student3.setStudentName("速度放緩");
        student3.setGender("女");
        student3.setClassName("303");
        student3.setStudentId(2018013456);
        int i = studentDao2.insert(student3);
        System.out.println(i);
        session2.commit();

        SqlSession session1 = MybatisUtils.getSession();
        //第二次查詢
        StudentDao studentDao1 = session1.getMapper(StudentDao.class);
        Student student1 = studentDao1.queryById(1);
        System.out.println(student1.getStudentName());

    }

上面使用了3個不同的SqlSession,第一次和第三次都調用了queryById()執行查詢,中間執行了一個插入操作,mybatis執行插入的時候,會先清除當前namespace對應的二級緩存中的數據,所以上面2次查詢最終都會訪問db,來運行一下看看效果。

運行輸出
在這裏插入圖片描述

方式2:select元素的flushCache屬性置爲true

當將mapper xml中select元素的flushCache屬性置爲true,會先清空二級緩存中的數據,然後再去db中查詢數據,然後將數據再放到二級緩存中。

<!--查詢單個-->
    <select id="queryById" flushCache="true" resultMap="StudentMap">
        select
          id, student_id, student_name, age, gender, class_name
        from mybatis.t_student
        where id = #{id}
    </select>

方式3:select元素的useCache置爲false跳過二級緩存,但是不會清空二級緩存數據

<!--查詢單個-->
    <select id="queryById" useCache="false" resultMap="StudentMap">
        select
          id, student_id, student_name, age, gender, class_name
        from mybatis.t_student
        where id = #{id}
    </select>

二級緩存使用總結

1:一二級緩存訪問順序:一二級緩存都存在的情況下,會先訪問二級緩存,然後再訪問一級緩存,最後纔會訪問db,這個順序大家理解一下

2:將mapper xml中select元素的flushCache屬性置爲false,最終會清除一級緩存所有數據,同時會清除這個select所在的namespace對應的二級緩存中所有的數據

3:將mapper xml中select元素的useCache置爲false,會使這個查詢跳過二級緩存

4:總體上來說使用緩存可以提升查詢效率,這塊知識掌握了,大家可以根據業務自行選擇

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章