target
掌握 mybatis的一级缓存是什么
掌握 mybatis一级缓存失效的几种情况
掌握 mybatis 二级缓存的机制和开启步骤
掌握 mybatis 缓存机制的执行流程
在计算机操作系统中,有一个叫缓存机制的东西。大概的意思就是操作系统把CPU频繁需要用到的指令和数据暂时存在cache中,使的CPU交互直接是和cache进行,因为cache的速度极快。而不需要和速度相对较慢的内存之间进行,大大提高了效率和资源消耗。
mybatis中也存在缓存机制,分为一级缓存和二级缓存。
新建动态Web项目MyBatis-08
,来演示本章内容。
① 导入jar
将以下jar包复制到lib目录下
log4j-1.2.17.jar
log4j-api-2.3.jar
log4j-core-2.3.jar
mybatis-3.4.5.jar
mysql-connector-java-8.0.15.jar
② 创建实体类
在com.mybatis.po包下新建一个Employee类:
public class Employee {
private Integer id;
private String name;
private Integer gender;
private String email;
}
③ 建表
在MySQL数据库中新建employee表:
CREATE TABLE employee(
id int PRIMARY key,
name VARCHAR(30),
gender int(2),
email VARCHAR(30)
);
④ 创建映射接口
在com.mybatis.mapper包下创建EmployeeMapper.java接口:
public interface EmployeeMapper {
Employee selectEmployeeById(Integer id);
int insertEmp(Employee emp);
int updateEmpById(Employee emp);
}
⑤ 创建映射文件
在com.mybatis.mapper包下创建EmployeeMapper.xml映射文件:
namespace是映射接口的全类名。
<?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.mybatis.mapper.EmployeeMapper">
<!--根据id查询用户信息 -->
<select id="selectEmployeeById" resultType="com.mybatis.po.Employee"
parameterType="com.mybatis.po.Employee">
select * from employee where id = #{id}
</select>
<!-- 插入一条用户信息 -->
<insert id="insertEmp" parameterType="com.mybatis.po.Employee">
insert into employee(name,gender,email)
values (#{name},#{gender},#{email})
</insert>
<!-- 根据id修改一条用户信息 -->
<update id="updateEmpById" parameterType="com.mybatis.po.Employee">
update employee set name = #{name} , gender = #{gender} ,email = #{email} where id = #{id}
</update>
</mapper>
⑥ 创建mybatis全局配置文件
在src目录下创建mybat-config.xml文件,并配置数据库信息和别名,mapper等。
<?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="dbconf.properties"></properties>
<settings>
<setting name="logImpl" value="LOG4J" />
</settings>
<typeAliases>
<package name="com.mybatis.po" />
</typeAliases>
<!-- 配置mybatis运行环境 -->
<environments default="development">
<environment id="development">
<!-- 使用JDBC的事务管理 -->
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<!-- MySQL数据库驱动 -->
<property name="driver" value="${mysql.driver}" />
<!-- 连接数据库的URL -->
<property name="url" value="${mysql.url}" />
<property name="username" value="${mysql.username}" />
<property name="password" value="${mysql.password}" />
</dataSource>
</environment>
</environments>
<!-- 将mapper文件加入到配置文件中 -->
<mappers>
<mapper resource="com/mybatis/mapper/EmployeeMapper.xml" />
</mappers>
</configuration>
在src目录下创建dbconf.properties文件,用来加载数据库连接信息。
# MySQL environment
mysql.driver=com.mysql.cj.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mybatis
mysql.username=root
mysql.password=lixintao
# Oracle Environment
oracle.driver=com.jdbc.OracleDriver
oracle.url=jdbc:oracle:thin:@localhost:1521:orcl
oracle.username=root
oracle.password=lixintao
创建log4j.properties,配置日志相关:
# Global logging configuration
log4j.rootLogger=ERROR,stdout
# MyBatis logging configuration...
log4j.logger.com.mybatis=DEBUG
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
⑦ 创建测试类
在com.mybatis.test包下,新建测试类MybatisTest.java:
public class MybatisTest {
SqlSession session = null;
@Before
public void init() {
InputStream in = null;
try {
in = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in);
session = sessionFactory.openSession();
}
@After
public void destory() {
if (session!=null) {
session.close();
}
}
}
1. 一级缓存
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。同一个sqlSession之间的缓存数据区域(HashMap)是相互共享的。
1)什么是一级缓存
我们在一个 sqlSession 中,对 employee 表根据id进行两次查询,查看他们发出sql语句的情况。
@Test
public void testCache() {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
System.out.println("第一次查询id为1的雇员:");
Employee emp = employeeMapper.selectEmployeeById(1);
System.out.println(emp);
System.out.println("第二次查询id为1的雇员:");
Employee emp2 = employeeMapper.selectEmployeeById(1);
System.out.println(emp2);
}
执行流程:
只发起一次SQL查询。
第一次查询,发出sql语句,并将查询的结果放入缓存中。第二次查询,由于是同一个sqlSession,会在缓存中查找查询结果,如果有,则直接从缓存中取出来,不和数据库进行交互。
2)一级缓存失效的几种情况
① sqlSession不同
@Test
public void testCache2() {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
Employee emp = employeeMapper.selectEmployeeById(1);
System.out.println(emp);
System.out.println("新建一个sqlSession:");
SqlSession session2 = sessionFactory.openSession();
EmployeeMapper employeeMapper2 = session2.getMapper(EmployeeMapper.class);
Employee emp2 = employeeMapper2.selectEmployeeById(1);
System.out.println(emp2);
}
执行流程:
由于是不同的sqlSession,一级缓存失效,所以执行两次SQL查询。
② sqlSession相同,查询条件不同
@Test
public void testCache3() {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
System.out.println("第一次查询id为1的雇员:");
Employee emp = employeeMapper.selectEmployeeById(1);
System.out.println(emp);
System.out.println("第二次查询id为3的雇员:");
Employee emp2 = employeeMapper.selectEmployeeById(3);
System.out.println(emp2);
}
执行流程:
发现,执行了两次SQL查询。
③ sqlSession相同,两次查询之间有增删改操作
两次查询之间有增删改操作(即使没有commit),一级缓存同样失效。
或者直接执行一个commit操作(没有增删改操作),一级缓存也会失效。
@Test
public void testCache4() {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
System.out.println("第一次查询id为1的雇员:");
Employee emp = employeeMapper.selectEmployeeById(1);
System.out.println(emp);
// Employee newemp = new Employee(null, "wangwu", 0, "ww@qq.com");
// employeeMapper.insertEmp(newemp);
session.commit();
System.out.println("第二次查询id为1的雇员:");
Employee emp2 = employeeMapper.selectEmployeeById(1);
System.out.println(emp2);
}
执行流程:
④ sqlSession相同,手动清除一级缓存
@Test
public void testCache5() {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
System.out.println("第一次查询id为1的雇员:");
Employee emp = employeeMapper.selectEmployeeById(1);
System.out.println(emp);
session.clearCache();//清空一级缓存
System.out.println("第二次查询id为1的雇员:");
Employee emp2 = employeeMapper.selectEmployeeById(1);
System.out.println(emp2);
}
执行流程:
2. 二级缓存
二级缓存也叫全局缓存。二级缓存是基于namespace的缓存,一个namespace对应一个二级缓存。
1)工作机制
一个会话查询一条语句,这个会话会放在当前会话的一级缓存中。
如果会话关闭,一级缓存中的数据会保存到二级缓存中。
新的会话查询信息,会参照二级缓存。
2)开启步骤
① 实体类序列化
将Employee类进行序列化:
public class Employee implements Serializable{
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
private Integer gender;
private String email;
}
② mybatis中开启二级缓存
在mybatis-config.xml中开启二级缓存:
<settings>
<!--这个配置使全局的映射器(二级缓存)启用或禁用缓存-->
<setting name="cacheEnabled" value="true" />
</settings>
③ 在映射文件中开启二级缓存
实际项目中会存在多个xml映射文件,在哪个中开启二级缓存,哪个就有效。
EmployeeMapper.xml:
<cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024" />
参数介绍:
-
eviction:代表的是缓存回收策略,目前MyBatis提供以下策略。
- LRU,最近最少使用的,一处最长时间不用的对象
- FIFO,先进先出,按对象进入缓存的顺序来移除他们
- SOFT,软引用,移除基于垃圾回收器状态和软引用规则的对象
- WEAK,弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象。
默认采用的是LRU,移除最长时间不用的对形象
flushInterval:刷新间隔时间,单位为毫秒,这里配置的是100秒刷新,如果你不配置它,那么当SQL被执行的时候才会去刷新缓存。
size:引用数目,一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大。设置过大会导致内存溢出。 这里配置的是1024个对象
readOnly:只读,意味着缓存数据只能读取而不能修改,这样设置的好处是我们可以快速读取缓存,缺点是我们没有办法修改缓存,他的默认值是false,不允许我们修改。
3)测试
@Test
public void testCache6() {
SqlSession sqlSession = sessionFactory.openSession();
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
System.out.println("第一次查询id为1的雇员:");
Employee emp = employeeMapper.selectEmployeeById(1);
System.out.println(emp);
sqlSession.close();//关闭sqlSession会话
SqlSession sqlSession2 = sessionFactory.openSession();
EmployeeMapper employeeMapper2 = sqlSession2.getMapper(EmployeeMapper.class);
System.out.println("第二次查询id为1的雇员:");
Employee emp2 = employeeMapper2.selectEmployeeById(1);
System.out.println(emp2);
}
执行流程:
3. 细节问题
-
cacheEnable=true/false:
只对二级缓存起作用,一级缓存一直开启的。
-
每个select标签里都有useCache=true
只对二级缓存起作用,一级缓存一直开启的。
-
每个增删改标签都有flushCache
当设置为true的时候,一级缓存和二级缓存都被清除。
-
sqlSession.clearCache():
只清除一级缓存,不会影响到二级缓存的使用。即:执行完clearCache后,在执行close,依旧会把一级缓存未清除之前的内容放到二级缓存。
4.执行流程
当一个新的会话(sqlSession)到来时,首先会去查询二级缓存,若二级缓存未命中,直接发出SQL语句。
@Test
public void testCache6() {
SqlSession sqlSession = sessionFactory.openSession();
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
System.out.println("第一次查询id为1的雇员:");
Employee emp = employeeMapper.selectEmployeeById(1);
System.out.println(emp);
SqlSession sqlSession2 = sessionFactory.openSession();
EmployeeMapper employeeMapper2 = sqlSession2.getMapper(EmployeeMapper.class);
System.out.println("第二次查询id为1的雇员:");
Employee emp2 = employeeMapper2.selectEmployeeById(1);
System.out.println(emp2);
}
执行流程:
当一个已经存在的会话里发出一个新的查询时,首先会去二级缓存查询,如果未命中,则会去一级缓存再次查询,若仍然未命中,直接发出SQL语句。
@Test
public void testCache() {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
System.out.println("第一次查询id为1的雇员:");
Employee emp = employeeMapper.selectEmployeeById(1);
System.out.println(emp);
System.out.println("第二次查询id为1的雇员:");
Employee emp2 = employeeMapper.selectEmployeeById(1);
System.out.println(emp2);
}
执行流程:
5. mybatis整合Ehcache
三方缓存要和mybatis进行整合,必须实现org.apache.ibatis.cache.Cache接口。
1)导入jar:
Ehcache-core-2.6.8.jar
mybatis-Ehcache-1.0.3.jar
slf4j-api-1.7.5.jar
2)创建Ehcache配置文件
在src下创建Ehcache配置文件Ehcache.xml:
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="F:\Ehcache"/>
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="1000000"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="100"
timeToLiveSeconds="100"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
3)开启Ehcache
在mapper.xml文件中,
<mapper namespace="org.lanqiao.mapper.IStudentMapper">
<cache type="org.mybatis.caches.ehcache.EhcacheCache">
<property name="timeToIdleSeconds" value="3600"/>
<property name="timeToLiveSeconds" value="3600"/>
<!--同ehcache参数maxElementsInMemory -->
<property name="maxEntriesLocalHeap" value="1000"/>
<!--同ehcache参数maxElementsOnDisk -->
<property name="maxEntriesLocalDisk" value="10000000"/>
<property name="memoryStoreEvictionPolicy" value="LRU"/>
</cache>
</mapper>