MyBatis——緩存機制

1 前言

緩存可以極大的提升查詢效率。MyBatis 包含一個非常強大的查詢緩存特性,包含一級緩存和二級緩存,如下:

  • 一級緩存:默認開啓,是基於 SqlSession 級別的緩存,也稱爲本地緩存,
  • 二級緩存:需要手動開啓和配置,是基於 namespace 級別的緩存

爲了提高擴展性,MyBatis 定義了緩存接口 Cache,用戶可以通過實現 Cache 接口來自定義二級緩存。

一級緩存失效的情況

  • 不同的 sqlSession 對應不同的一級緩存
  • 同一個 sqlSession 但是查詢條件不同
  • 同一個 sqlSession 兩次查詢期間執行了任何一次增刪改操作(自動清空緩存,包含一級和二級緩存)
  • 同一個 sqlSession 兩次查詢期間手動清空了緩存(sqlSession.clearCache(),只清空一級緩存)

二級緩存使用的步驟

  1. 全局配置文件中開啓二級緩存:<setting name="cacheEnabled" value="true"/>
  2. 需要使用二級緩存的映射文件中配置緩存:<cache/>
  3. POJO 需要實現 Serializable 接口
  4. 關閉或提交 SqlSession

在 select 標籤中可以通過配置 useCache 屬性(默認爲 true),來確定這個 select 語句是否使用二級緩存。

2 實驗環境

(1)導入 JAR 包

其中,前2個 jar 包下載地址見 → log4j-1.2.17.jar、 mybatis-3.4.1.jar,將 jar 包放入 lib 目錄下,並選中所有 jar 包,右鍵,選擇【Add to Build Path】。

(2)工作目錄

注意:src 和 conf 目錄下的 com.mapper 包必須同名。

(3)配置文件 

log4j.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
 
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
 
	<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
		<param name="Encoding" value="UTF-8" />
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L)\n" />
		</layout>
	</appender>
 
	<logger name="java.sql">
		<level value="debug" />
	</logger>
 
	<logger name="org.apache.ibatis">
		<level value="info" />
	</logger>
 
	<root>
		<priority value="debug" />
		<appender-ref ref="STDOUT" />
	</root>
</log4j:configuration>

注意:log4j.xml文件名不能隨意更改。 

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
 
<configuration>
	<!-- 設置或引入資源文件 -->
	<properties resource="jdbc.properties"></properties>
	
	<!-- 設置連接數據庫的環境,default用於設置默認使用的數據庫環境 -->
	<environments default="mysql">
		<!-- 設置某個具體的數據庫環境 -->
		<environment id="mysql">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc.driver}" />
				<property name="url" value="${jdbc.url}" />
				<property name="username" value="${jdbc.username}" />
				<property name="password" value="${jdbc.password}" />
			</dataSource>
		</environment>
	</environments>
	
	<!-- 引入映射文件 -->
	<mappers>
		<package name="com.mapper"/>
	</mappers>
</configuration>

jdbc.properties

# K = V
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/users
jdbc.username=root
jdbc.password=0.

3 案例分析

首先在 MySQL 中創建數據庫:users,再在此數據庫中創建表:students。

students 包含 sid(int)、sname(varchar)和 sex(varchar) 3個字段,其中,sid 設置了自增,students 表中數據如下:

首先介紹下公共的文件,主要包含 Student.java、StudentMapper.java,不同的是 StudentMapper.xml、Test.java,將在各節分別介紹。 

Student.java

package com.bean;
 
public class Student {
	private Integer sid;
	private String sname;
	private String sex; 
	
	public Integer getSid() {
		return sid;
	}
 
	public void setSid(Integer sid) {
		this.sid = sid;
	}
 
	public String getSname() {
		return sname;
	}
 
	public void setSname(String sname) {
		this.sname = sname;
	}
	
	public String getSex() {
		return sex;
	}

	public void setSex(String sex) {
		this.sex = sex;
	}

	@Override
	public String toString() {
		return "Student [sid=" + sid + ", sname=" + sname + ", sex=" + sex + "]";
	}
}

StudentMapper.java

package com.mapper;
 
import com.bean.Student;
 
public interface StudentMapper {	
	public Student getStudentById(Integer sid);
}

3.1 一級緩存

StudentMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<mapper namespace="com.mapper.StudentMapper">
	<!-- public Student getStudentById(Integer sid); -->
	<select id="getStudentById" resultType="com.bean.Student">
		select sid,sname,sex from students where sid=#{sid}
	</select>
</mapper>

測試一 

Test.java

package com.test;
 
import java.io.IOException;
import java.io.InputStream;
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 com.bean.Student;
import com.mapper.StudentMapper;
 
public class Test {
	
	public static void main(String[] args) throws IOException {
		InputStream is=Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
		SqlSession sqlSession=sqlSessionFactory.openSession(true); //自動提交事務
		//getMapper:會通過動態代理動態生成StudentMapper的代理實現類
		StudentMapper mapper=sqlSession.getMapper(StudentMapper.class);
		
		Student student1=mapper.getStudentById(1001);
		System.out.println(student1);
		System.out.println("=========================");
		Student student2=mapper.getStudentById(1001);
		System.out.println(student2);
	}
}

 運行結果:

DEBUG 06-18 16:58:19,831 ==>  Preparing: select sid,sname,sex from students where sid=?  
Student [sid=1001, sname=張三, sex=男]
=========================
Student [sid=1001, sname=張三, sex=男]

可以看到,SQL 語句只執行一次,說明第二次查詢是從緩存中取出的數據。 

測試二

Test.java

package com.test;
 
import java.io.IOException;
import java.io.InputStream;
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 com.bean.Student;
import com.mapper.StudentMapper;
 
public class Test {
	
	public static void main(String[] args) throws IOException {
		InputStream is=Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
		
		SqlSession sqlSession1=sqlSessionFactory.openSession(true); //自動提交事務
		StudentMapper mapper1=sqlSession1.getMapper(StudentMapper.class);		
		Student student1=mapper1.getStudentById(1001);
		System.out.println(student1);
		sqlSession1.commit();
		
		System.out.println("=========================");
		
		SqlSession sqlSession2=sqlSessionFactory.openSession(true);
		StudentMapper mapper2=sqlSession2.getMapper(StudentMapper.class);
		Student student2=mapper2.getStudentById(1001);
		System.out.println(student2);
		sqlSession2.commit();
	}
}

注意:2次查詢在不同會話中。

運行結果:

DEBUG 06-18 22:29:43,299 ==>  Preparing: select sid,sname,sex from students where sid=?  
Student [sid=1001, sname=張三, sex=男]
=========================
DEBUG 06-18 22:29:43,340 ==>  Preparing: select sid,sname,sex from students where sid=?  
Student [sid=1001, sname=張三, sex=男]

可以看到,SQL 語句執行了2次,說明一級緩存是基於會話的,即不同會話的2次相同查詢不會從緩存中取數據。  

3.2 二級緩存

(1)首先在 mybatis-config.xml 文件中配置全局緩存,如下:

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

(2)在 StudentMapper.xml 文件中配置 <cache/>,如下:

StudentMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<mapper namespace="com.mapper.StudentMapper">
	<cache/>
    
	<!-- public Student getStudentById(Integer sid); -->
	<select id="getStudentById" resultType="com.bean.Student">
		select sid,sname,sex from students where sid=#{sid}
	</select>
</mapper>

(3)讓 Student 類繼承 Serializable 接口,如下:

package com.bean;
import java.io.Serializable;
public class Student implements Serializable{
...

(4)Test.java 同3.1中測試二。

運行結果:

DEBUG 06-18 22:42:48,721 Cache Hit Ratio [com.mapper.StudentMapper]: 0.0 (LoggingCache.java:62)
DEBUG 06-18 22:42:48,927 ==>  Preparing: select sid,sname,sex from students where sid=?  
Student [sid=1001, sname=張三, sex=男]
=========================
DEBUG 06-18 22:42:49,011 Cache Hit Ratio [com.mapper.StudentMapper]: 0.5 (LoggingCache.java:62)
Student [sid=1001, sname=張三, sex=男]

可以看到,即使在不同會話中,第二次查詢也沒有執行 SQL 語句,說明第二次查詢使用了緩存。另外,輸出結果中多了【Cache Hit Ratio】,是指緩存命中率。

3.3 第三方緩存

第三方緩存也屬於二級緩存,配置步驟同3.2節,只是在 StudentMapper.xml 文件的 <cache> 標籤的 type 屬性中指向第三方緩存,本節以第三方緩存 ehcache 爲例。

首先需要導入如下4個 JAR 包:

ehcache-core-2.6.8.jarmybatis-ehcache-1.0.3.jarslf4j-api-1.6.1.jarslf4j-log4j12-1.6.2.jar

ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false">

    <!-- diskStore:爲緩存路徑,ehcache分爲內存和磁盤兩級,此屬性定義磁盤的緩存位置 -->
    <diskStore path="G:\spring\Mybatis\cache"/>
    
    <!-- defaultCache:默認緩存策略,當ehcache找不到定義的緩存時,則使用這個緩存策略,只能定義一個 -->
    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="true"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="259200"
            memoryStoreEvictionPolicy="LRU"/>
</ehcache>

注意:ehcache.xml 文件需要放在 conf 目錄下。 

<defaultCache> 中包含如下屬性,說明如下:

eternal:對象是否永久有效,一但設置了,timeout將不起作用。
maxElementsInMemory:緩存最大數目。
maxElementsOnDisk:硬盤最大緩存個數。
overflowToDisk:當系統當機時,是否保存到磁盤。
timeToIdleSeconds:設置對象在失效前的允許閒置時間(單位:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,默認值是0,也就是可閒置時間無窮大。
timeToLiveSeconds:設置對象在失效前允許存活時間(單位:秒)。最大時間介於創建時間和失效時間之間。僅當eternal=false對象不是永久有效時使用,默認是0.,也就是對象存活時間無窮大。
diskPersistent:是否緩存虛擬機重啓期數據 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每個Cache都應該有自己的一個緩衝區。
diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒。
memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理內存。默認策略是LRU(最近最少使用)。你可以設置爲FIFO(先進先出)或是LFU(較少使用)。
clearOnFlush:內存數量最大時是否清除。
memoryStoreEvictionPolicy:可選策略有:LRU(最近最少使用,默認策略)、FIFO(先進先出)、LFU(最少訪問次數)。

StudentMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<mapper namespace="com.mapper.StudentMapper">
	<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
	
	<!-- public Student getStudentById(Integer sid); -->
	<select id="getStudentById" resultType="com.bean.Student">
		select sid,sname,sex from students where sid=#{sid}
	</select>
</mapper>

注意:<cache> 標籤的屬性指向了第三方緩存 EhcachCache 類。

運行後,G:\spring\Mybatis\cache 目錄下可以緩存文件,如下:

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