1、什麼是MyBatis緩存?
使用MyBatis緩存可以減少java應用程序與數據庫的交互次數,從這個角度看,它的好處和使用MyBatis延遲加載一樣,都可以在一定程度上提高程序的運行效率。比如,查詢出 id=1的java對象,第一次查詢出來之後會自動把這個java對象保存在MyBatis緩存中,當下一次再查詢id=1的對象時,直接從緩存中取出對象接口,無需再次訪問數據庫。但是,如果第二次查詢的是id=2的對象,那麼從緩存中是獲取不到的,此時仍然需要再次訪問數據庫,訪問完之後再把id=2的java對象再保存到緩存中去。
public static void main(String[] args) {
//加載MyBatis配置文件
InputStream inputStream = UserTest3.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
//獲取實現接口的代理對象
StudentRepository studentRepository = sqlSession.getMapper(StudentRepository.class);
StudentRepository studentRepository2 = sqlSession2.getMapper(StudentRepository.class);
System.out.println(studentRepository);
System.out.println(studentRepository2);
// org.apache.ibatis.binding.MapperProxy@6df97b55
// org.apache.ibatis.binding.MapperProxy@3cbbc1e0
StudentEntity studentEntity = studentRepository.queryStudentLazy(2);
System.out.println(studentEntity.getClassEntity().getClassName());
StudentEntity studentEntity2 = studentRepository2.queryStudentLazy(2);
System.out.println(studentEntity2.getClassEntity().getClassName());
sqlSession.close();
}
2、MyBatis緩存分類
MyBatis的緩存分爲兩類:一級緩存和二級緩存。
(1)一級緩存:SqlSession級別,默認是開啓的,而且不能關閉。
操作數據庫的時候,需要創建SqlSession對象,在SqlSession對象中有一個HashMap用來存儲緩存數據,由於一級緩存是SqlSession級別的,所以不同的SqlSession對象之間緩存的數據是互不影響的。
一級緩存的作用域是在SqlSession範圍內,當在同一個SqlSession中執行了兩次相同的SQL語句時,第一次執行完之後會把結果保存在該SqlSession對象的緩存中,在第二次執行相同查詢SQL的時候會直接從緩存中取出結果並返回。
需要注意的是,如果SqlSession對象執行了DML操作(insert、update、delete),那麼MyBatis必須將SqlSession對象的緩存清空來保證數據查詢的準確性。
(2)二級緩存:Mapper級別,默認是關閉的,想要使用的話需要手動開啓。它的範圍要大於一級緩存。
使用二級緩存時,多個SqlSession對象使用同一個Mapper的SQL語句操作數據庫時,得到的數據會在同一個二級緩存區進行保存,同樣是使用HashMap來做數據存儲。相比較與一級緩存而言,二級緩存的可見範圍更大,多個SqlSession對象可以共用同一個Mapper的二級緩存,所以說,二級緩存是跨SqlSession的。
二級緩存是多個SqlSession共享的,其作用域就是Mapper的同一個namespace。不同的SqlSession兩次執行相同的namespace下的SQL語句,參數也相等時,則第一次執行成功後會把數據保存到二級緩存中,第二次可以直接從二級緩存中獲取。
3、代碼演示:一級緩存
(1)實體類
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class StudentEntity implements Serializable {
private static final long serialVersionUID = -7497520016303190017L;
private int id; //學號
private String name; //姓名
private int classId; //班級
private int status; //是否有效(1:有效,-1:無效)
private String addTime; //添加時間
private String updateTime; //更新時間
private ClassEntity classEntity; //該學生屬於哪個班級
}
(2)實體類接口
public interface StudentRepository {
StudentEntity queryStudent(@Param("id") int id);
}
(3)實體類接口的Mapper.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.wind.repository.StudentRepository">
<resultMap id="studentMap" type="com.wind.entity.StudentEntity">
<result column="Id" property="id"/>
<result column="Name" property="name"/>
<result column="ClassId" property="classId"/>
<result column="Status" property="status"/>
<result column="AddTime" property="addTime"/>
<result column="UpdateTime" property="updateTime"/>
</resultMap>
<sql id="sql_select">
select Id, Name, ClassId, Status, AddTime, UpdateTime from RUN_Student
</sql>
<select id="queryStudent" parameterType="int" resultMap="studentMap">
<include refid="sql_select"/>
where id = #{id} and status = 1
</select>
</mapper>
(4)一級緩存測試:sqlSession對象 是 同一個,然後前後兩次執行相同的SQL
public class UserTest4 {
public static void main(String[] args) {
//加載MyBatis配置文件
InputStream inputStream = UserTest4.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
//獲取實現接口的代理對象
StudentRepository studentRepository = sqlSession.getMapper(StudentRepository.class);
StudentEntity entity = studentRepository.queryStudent(1);
System.out.println(entity);
StudentEntity entity2 = studentRepository.queryStudent(1);
System.out.println(entity2);
sqlSession.close();
}
}
結論:從上述測試結果可以發現,SQL語句只執行了一次,也就是說只和數據庫交互了一次。
(5)一級緩存測試:sqlSession對象 不是 同一個,然後前後兩次執行相同的SQL
public class UserTest4 {
public static void main(String[] args) {
//加載MyBatis配置文件
InputStream inputStream = UserTest4.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
//獲取實現接口的代理對象
StudentRepository studentRepository = sqlSession.getMapper(StudentRepository.class);
StudentEntity entity = studentRepository.queryStudent(1);
System.out.println(entity);
//上面那個sqlSession關閉,也即關閉了那個一級緩存,再重新打開一個
sqlSession.close();
sqlSession = sqlSessionFactory.openSession();
studentRepository = sqlSession.getMapper(StudentRepository.class);
StudentEntity entity2 = studentRepository.queryStudent(1);
System.out.println(entity2);
sqlSession.close();
}
}
結論:從上述測試結果可以發現,先創建一個sqlSession對象,執行一次SQL,此時和數據庫交互一次,並把結果返回。然後把這個sqlSession對象關閉,再重新創建一個新的sqlSession對象,此時再去執行相同的SQL時,會發現第二次查詢時仍然需要再次和數據庫交互一次。所以,前後共交互兩次。
4、代碼演示:二級緩存
4.1 MyBatis自帶的二級緩存
(1)在MyBatis配置文件中開啓二級緩存: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>
<settings>
<!--開啓在控制檯打印SQL日誌-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--開啓延遲加載-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--開啓二級緩存-->
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
<package name="com.wind.entity"/>
</typeAliases>
<!--配置mybatis運行環境-->
<environments default="development">
<environment id="development">
<!--配置JDBC事務管理-->
<transactionManager type="JDBC"></transactionManager>
<!--配置POOLED類型的JDBC數據源連接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/RUNOOB?useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="admin0001112"/>
</dataSource>
</environment>
</environments>
<!--註冊mapper文件-->
<mappers>
<mapper resource="com/entity/mapper/UserMapper.xml"/>
<mapper resource="com/entity/mapper/UserRepository.xml"/>
<mapper resource="com/entity/mapper/StudentRepository.xml"/>
<mapper resource="com/entity/mapper/ClassRepository.xml"/>
</mappers>
</configuration>
(2)在Mapper.xml文件中添加二級緩存的配置:<cache/>
<?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.wind.repository.StudentRepository">
<cache/>
<resultMap id="studentMap" type="com.wind.entity.StudentEntity">
<result column="Id" property="id"/>
<result column="Name" property="name"/>
<result column="ClassId" property="classId"/>
<result column="Status" property="status"/>
<result column="AddTime" property="addTime"/>
<result column="UpdateTime" property="updateTime"/>
</resultMap>
<sql id="sql_select">
select Id, Name, ClassId, Status, AddTime, UpdateTime from RUN_Student
</sql>
<select id="queryStudent" parameterType="int" resultMap="studentMap">
<include refid="sql_select"/>
where id = #{id} and status = 1
</select>
</mapper>
(3)實體類實現序列化接口
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class StudentEntity implements Serializable {
private static final long serialVersionUID = -7497520016303190017L;
private int id; //學號
private String name; //姓名
private int classId; //班級
private int status; //是否有效(1:有效,-1:無效)
private String addTime; //添加時間
private String updateTime; //更新時間
private ClassEntity classEntity; //該學生屬於哪個班級
}
(4)二級緩存測試
public class UserTest4 {
public static void main(String[] args) {
//加載MyBatis配置文件
InputStream inputStream = UserTest4.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
//獲取實現接口的代理對象
StudentRepository studentRepository = sqlSession.getMapper(StudentRepository.class);
StudentEntity entity = studentRepository.queryStudent(1);
System.out.println(entity);
//上面那個sqlSession關閉,也即關閉了那個一級緩存,再重新打開一個
sqlSession.close();
sqlSession = sqlSessionFactory.openSession();
studentRepository = sqlSession.getMapper(StudentRepository.class);
StudentEntity entity2 = studentRepository.queryStudent(1);
System.out.println(entity2);
StudentEntity entity3 = studentRepository.queryStudent(1);
System.out.println(entity3);
sqlSession.close();
}
}
結論:從上述測試結果可以發現,先創建一個sqlSession對象,執行一次SQL,此時和數據庫交互一次,並把結果返回。然後把這個sqlSession對象關閉,再重新創建一個新的sqlSession對象,此時再去執行相同的SQL時,會發現由於開啓了二級緩存設置,第二次查詢與第三次查詢時不再需要再次和數據庫交互了,會命中二級緩存直接從二級緩存中取出數據即可。從日誌中也可以看出,【Cache Hit Ratio [com.wind.repository.StudentRepository]: 0.5 】,緩存命中了,命中率0.5。所以,前後共交互一次。
4.2 第三方插件的二級緩存:ehcache組件
(1)pom.xml文件中引入相關依賴
<!--第三方緩存依賴-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
(2)添加ehcache插件自帶的 ehcache.xml 配置文件
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore/>
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
(3)Mapper.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.wind.repository.StudentRepository">
<cache type="org.mybatis.caches.ehcache.EhcacheCache">
<!-- 緩存創建之後,最後⼀一次訪問緩存的時間⾄至緩存失效的時間間隔 -->
<property name="timeToIdleSeconds" value="3600"/>
<!-- 緩存⾃自創建時間起⾄至失效的時間間隔 -->
<property name="timeToLiveSeconds" value="3600"/>
<!-- 緩存回收策略略,LRU表示移除近期使⽤用最少的對象 -->
<property name="memoryStoreEvictionPolicy" value="LRU"/>
</cache>
<resultMap id="studentMap" type="com.wind.entity.StudentEntity">
<result column="Id" property="id"/>
<result column="Name" property="name"/>
<result column="ClassId" property="classId"/>
<result column="Status" property="status"/>
<result column="AddTime" property="addTime"/>
<result column="UpdateTime" property="updateTime"/>
</resultMap>
<sql id="sql_select">
select Id, Name, ClassId, Status, AddTime, UpdateTime from RUN_Student
</sql>
<select id="queryStudent" parameterType="int" resultMap="studentMap">
<include refid="sql_select"/>
where id = #{id} and status = 1
</select>
</mapper>
(4)實體類不再需要實現序列化接口
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class StudentEntity {
private int id; //學號
private String name; //姓名
private int classId; //班級
private int status; //是否有效(1:有效,-1:無效)
private String addTime; //添加時間
private String updateTime; //更新時間
private ClassEntity classEntity; //該學生屬於哪個班級
}
(5)二級緩存測試
public class UserTest4 {
public static void main(String[] args) {
//加載MyBatis配置文件
InputStream inputStream = UserTest4.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
//獲取實現接口的代理對象
StudentRepository studentRepository = sqlSession.getMapper(StudentRepository.class);
StudentEntity entity = studentRepository.queryStudent(1);
System.out.println(entity);
//上面那個sqlSession關閉,也即關閉了那個一級緩存,再重新打開一個
sqlSession.close();
sqlSession = sqlSessionFactory.openSession();
studentRepository = sqlSession.getMapper(StudentRepository.class);
StudentEntity entity2 = studentRepository.queryStudent(1);
System.out.println(entity2);
StudentEntity entity3 = studentRepository.queryStudent(1);
System.out.println(entity3);
sqlSession.close();
}
}