一、缓存机制
介绍:
- MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
- MyBatis系统中默认定义了两级缓存。
一级缓存和二级缓存:
- 默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启
- 二级缓存需要手动开启和配置,它是基于namespace级别的缓存
- 为了提高扩展性,MyBatis定义了缓存接口Cache,用户可以通过实现Cache接口来定义二级缓存。
两级缓存:
一级缓存(本地缓存):将数据库同一次会话期间查询到的数据会放在本地缓存中。以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库。
二级缓存(全局缓存):
二、一级缓存
说明:
- 一级缓存(local cache),即本地缓存,作用域默认为sqlSession。当Session flush或close后,该Session中的所有Cache将被清空(每个sqlSession对应一个一级缓存)
- 本地缓存不能被关闭,是一直打开的,但可以调用clearCache()来清空本地缓存,或者改变缓存的作用域
- 同一次会话期间只要查询过得数据都会保存在当前sqlSession的一个Map中
一级缓存失效的情况:(没有使用当前一级缓存的情况,效果就是还需要再向数据库发出查询)
- sqlSession不同。(即不同的sqlSession对应各自的一级缓存)
- sqlSession相同,但查询条件不同。(即当前一级缓存中没有新的查询对应的结果)
- sqlSession相同,但两次查询之间执行了增删改操作。(即当前增删改可能对当前的数据有影响)
- sqlSession相同,但手动清除了一级缓存。(即当前一级缓存已清空)
示例:
package com.csu.marden;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Test {
public static void main(String[] args) throws IOException {
//1.根据xml配置文件(全局配置文件,即mybatis-config.xml)创建一个SqlSessionFactory对象
String resources="mybatis-config.xml";
InputStream inputStream=Resources.getResourceAsStream(resources);
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
//2.根据SqlSessionFactory创建SqlSession,每个SqlSession代表和数据库的一次会话
SqlSession openSession=sqlSessionFactory.openSession();
try{
//3.获取接口的实现类对象(mybatis会为接口自动创建一个代理对象,代理对象会执行增删改查)
EmployeeMapper mapper0=openSession.getMapper(EmployeeMapper.class);
EmployeeMapperPlus mapper1=openSession.getMapper(EmployeeMapperPlus.class);
DepartmentMapper mapper2=openSession.getMapper(DepartmentMapper.class);
EmployeeMapperDynamicSQL mapper3=openSession.getMapper(EmployeeMapperDynamicSQL.class);
//4.通过接口的实现类对象调用接口的查询方法
Employee result1 = mapper0.getEmpById(1);
System.out.println(result1.hashCode());
//手动清除一级缓存
openSession.clearCache();
Employee result2 = mapper0.getEmpById(1);
System.out.println(result2.hashCode());
openSession.commit();
}finally{
openSession.close();
}
}
}
三、二级缓存
说明:
- 二级缓存(second level cache),即全局作用域缓存。是基于namespace级别的缓存(一个sql映射文件对应一个namespace,即对应一个二级缓存)
- 二级缓存默认不开启,需要手动配置。
- MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable接口。
- 查处的数据默认先放在一级缓存中,二级缓存在SqlSession关闭或提交之后才会生效!!!
工作机制:
- 一个会话查询一条数据,这个数据就会被放在当前的一级缓存中。
- 如果这个会话关闭,一级缓存中的数据会被保存到二级缓存中。新的会话查询同样的信息,就可以参照二级缓存中的内容。
- 例如一个sqlSession,通过EmployeeMapper查询了Employee对象,也通过DepartmentMapper查询了Department对象。但是由于两个Mapper映射文件对应两个namespace,不同的namespace查出的数据会放在自己对应的二级缓存中。
使用步骤:
- 开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>
- 在每个sql映射文件(即mapper.xml)中配置使用二级缓存: <cache></cache>
- 注意:POJO需要实现Serializable接口
示例:
第一步:在全局配置文件中,开启二级缓存配置
<?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="dbconfig.properties"></properties>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
<typeAlias type="com.csu.marden.Employee" />
<typeAlias type="com.csu.marden.Department"/>
</typeAliases>
<environments default="development">
<environment id="development">
<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>
<!-- 将sql映射文件(EmployeeMapper.xml)注册到全局配置文件(mybatis-config.xml)中 -->
<mappers>
<mapper resource="EmployeeMapper.xml"/>
<mapper resource="EmployeeMapperPlus.xml" />
<mapper resource="DepartmentMapper.xml"/>
<mapper resource="EmployeeMapperDynamicSQL.xml"/>
</mappers>
</configuration>
第二步:在相应的sql映射文件(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.csu.marden.EmployeeMapper">
<!-- 映射文件中配置使用二级缓存 -->
<cache eviction="LRU" flushInterval="6000" readOnly="false" size="1024"></cache>
<!-- 返回多条记录的Map集合 ,其中id作为map的主键-->
<select id="getEmpByIdLastNameLikeReturnIdMap" resultType="employee">
select * from tbl_employee where last_name like #{lastName}
</select>
<!-- 返回多条记录的Map集合 ,其中lastName作为map的主键-->
<select id="getEmpByIdLastNameLikeReturnLastNameMap" resultType="employee">
select * from tbl_employee where last_name like #{lastName}
</select>
<!-- 返回一条记录的Map集合,resultType应该写Map类型 -->
<select id="getEmpByIdReturnMap" resultType="map">
select * from tbl_employee where id=#{id}
</select>
<!-- 返回List集合 -->
<!-- 如果返回值是一个集合,resultType应该写集合中元素的类型 -->
<select id="getEmpByLastNameLike" resultType="employee" >
select * from tbl_employee where last_name like #{lastName}
</select>
<!-- 多个参数使用Map传入的查询方法 -->
<select id="getEmpByMap" resultType="employee">
select * from tbl_employee where id=#{id} and last_name=#{lastName}
</select>
<!-- 多个参数查询方法 -->
<select id="getEmpByIdAndLastName" resultType="employee">
select * from tbl_employee where id = #{emp.id} and last_name=#{emp.lastName}
</select>
<!-- 单个参数查询方法 -->
<select id="getEmpById" resultType="employee" >
select * from tbl_employee where id = #{id}
</select>
<!--插入方法 -->
<insert id="addEmp" parameterType="employee" useGeneratedKeys="true" keyProperty="id" >
insert into tbl_employee (last_name,email,gender) values (#{lastName},#{email},#{gender})
</insert>
<!-- 更新方法 -->
<update id="updateEmp" parameterType="employee" >
update tbl_employee set last_name=#{lastName},email=#{email},gender=#{gender} where id=#{id}
</update>
<!-- 删除方法 -->
<delete id="deleteEmpById" parameterType="integer">
delete from tbl_employee where id=#{id}
</delete>
</mapper>
第三步:JavaBean实现Serializable接口
package com.csu.marden;
import java.io.Serializable;
public class Employee implements Serializable{
private Integer id;
private String lastName;
private String email;
private String gender;
private Department dept;
public Employee() {
}
public Employee(Integer id, String lastName, String email, String gender) {
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
}
public Employee(Integer id, String lastName, String email, String gender, Department dept) {
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
this.dept = dept;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Department getDept() {
return dept;
}
public void setDept(Department dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Employee [id=" + id + ", lastName=" + lastName + ", email=" + email + ", gender=" + gender + "]";
}
}
第四步:编写测试类
创建两个sqlSession对象,每个sqlSession对象查询一次(若没有开启二级缓存,则需查询两次)
package com.csu.marden;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Test {
public static void main(String[] args) throws IOException {
//1.根据xml配置文件(全局配置文件,即mybatis-config.xml)创建一个SqlSessionFactory对象
String resources="mybatis-config.xml";
InputStream inputStream=Resources.getResourceAsStream(resources);
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
//2.根据SqlSessionFactory创建SqlSession,每个SqlSession代表和数据库的一次会话
SqlSession openSession1=sqlSessionFactory.openSession();
SqlSession openSession2=sqlSessionFactory.openSession();
try{
//3.获取接口的实现类对象(mybatis会为接口自动创建一个代理对象,代理对象会执行增删改查)
EmployeeMapper mapper1=openSession1.getMapper(EmployeeMapper.class);
EmployeeMapper mapper2=openSession2.getMapper(EmployeeMapper.class);
//4.通过接口的实现类对象调用接口的查询方法
Employee result1=mapper1.getEmpById(1);
System.out.println(result1);
openSession1.close();
Employee result2=mapper2.getEmpById(1);
System.out.println(result2);
openSession2.close();
}finally{
}
}
}
1. 缓存相关属性:
eviction属性:缓存回收策略
- LRU –最近最少使用的:移除最长时间不被使用的对象。
- FIFO –先进先出:按对象进入缓存的顺序来移除它们。
- SOFT –软引用:移除基于垃圾回收器状态和软引用规则的对象。
- WEAK –弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
- 默认的是LRU。
flushInterval属性:缓存刷新间隔
- 缓存多久清空一次,默认不清空,可以设置一个毫秒值
readOnly属性:是否只读
- ture:只读缓存,mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,但速度快。
- false:读写缓存,mybatis认为获取的数据可能会被修改,mybatis会利用序列化与反序列化的技术,克隆一份新的数据。安全,但速度慢。(默认)
size属性:缓存中存放多少个元素
- 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
2. 缓存相关设置
- 全局配置文件中,setting标签的cacheEnable属性,功能:配置二级缓存的开关,若cacheEnable置为false,则二级缓存关闭,一级缓存仍然可用。
- sql映射文件中,select标签中的useCache属性,功能:配置这个select标签是否使用二级缓存,若useCache置为false,则二级缓存关闭,一级缓存仍然可用。
- sql映射文件中,insert,update,delete标签中的flushCache属性,功能:配置当前标签执行完增删改操作后,是否清空一级缓存和二级缓存。若flushCache置为true,一级缓存和二级缓存都会被清空。
- sqlSession调用clearCache()方法,功能:用于清除一级缓存。
- 全局配置文件中,setting标签的localCacheScope属性,功能:设置本地缓存作用域。默认为SESSION,即当前会话的所有数据保存在会话缓存汇总。若localCacheScope置为STATEMENT可以禁用一级缓存。
三、缓存原理示意图
缓存原理机制:
- 首先查找二级缓存
- 接下来查找一级缓存
- 最后查询数据库