文章目錄
什麼是緩存
緩存就是存儲數據的一個地方(稱作: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呢?
- SqlSession中執行增、刪、改操作,此時sqlsession會自動清理其內部的一級緩存
- 調用SqlSession中的clearCache方法清理其內部的一級緩存
- 設置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:總體上來說使用緩存可以提升查詢效率,這塊知識掌握了,大家可以根據業務自行選擇