package com.dfbz.dao;
import com.dfbz.entity.Emp;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public interface EmpDao {
Emp findById(Integer id);
void save(Emp emp);
}
* EmpDao.xml:
<?xml version="1.0" encoding="UTF-8"?>
<select id="findById" resultType="emp">
select * from emp where id=#{id}
</select>
<insert id="save">
insert into emp(id,name) values(null,#{name});
</insert>
* 测试类:
/**
* 测试一级缓存
*/
@Test
public void test1() {
// 获取session(开启一级缓存)
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
// 发送SQL查询数据,将数据存入一级缓存
Emp emp = mapper.findById(1);
System.out.println(emp);
// 从一级缓存中获取数据(没有发送SQL)
Emp emp2 = mapper.findById(1);
System.out.println(emp2);
System.out.println(emp == emp2); // true
// session关闭,一级缓存清空
session.close();
}
观察日志:

#### 2.2.3 一级缓存清空
一级缓存在如下情况下,会情况:
* 1)将一级缓存的作用域设置为语句级别(`localCacheScope`设置为`STATEMENT`)
* 2)清空缓存(clearCache)
* 3)执行任何增删改操作都会导致**整个**一级缓存
* 4)刷新缓存(flushCache)
* 5)session关闭,一级缓存清空
##### 2.2.3.1 设置localCacheScope
<!--
更改一级缓存的作用域
SESSION: 会话级别缓存,默认值
STATEMENT: 语句级别缓存,缓存只对当前执行的语句生效(类似于关闭一级缓存)
–>
* 再次执行上述的测试代码:
/**
* 测试一级缓存
*/
@Test
public void test1() {
// 获取session(开启一级缓存)
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
// 发送SQL查询数据
Emp emp = mapper.findById(1);
System.out.println(emp);
// 发送SQL查询数据
Emp emp2 = mapper.findById(1);
System.out.println(emp2);
System.out.println(emp == emp2); // false
// session关闭
session.close();
}
执行程序,观察控制台:

##### 2.2.3.2 clearCache
一级缓存清空测试:
@Test
public void test2() throws Exception { // 测试clearCache
// 开启一级缓存
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
// 首先从一级缓存里面查询,没查询到,然后去数据库查询,将查询的结果放入一级缓存
Emp emp = mapper.findById(1);
System.out.println(emp);
session.clearCache(); // 清空一级缓存
// 首先从一级缓存里面查询(没有),去数据库查询,将查询的结果放入一级缓存
Emp emp2 = mapper.findById(1);
System.out.println(emp2);
System.out.println(emp == emp2); // false
// 关闭session(一级缓存清空)
session.close();
}
执行结果,查看控制台:

##### 2.2.3.3 关闭session清空缓存
一级缓存是基于session级别的,如果session关闭,那么一级缓存将会失效!
* 测试代码:
@Test
public void test3() throws Exception { // 测试session关闭清空一级缓存
// 开启一级缓存
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
// 将查询的结果放入一级缓存
Emp emp = mapper.findById(1);
System.out.println(emp);
// session关闭一级缓存清空(session关闭了代表与数据库的会话一级结束了,不可以再发送任何的SQL语句了)
session.close();
// 重新获取一次会话
SqlSession session2 = factory.openSession();
EmpDao mapper2 = session2.getMapper(EmpDao.class);
// 新的session的一级缓存中并没有数据,发送SQL去数据库查询
Emp emp2 = mapper2.findById(1);
System.out.println(emp2);
System.out.println(emp == emp2);
// session关闭,一级缓存清空
session2.close();
}
执行程序,查看控制台:

##### 2.2.3.4 执行任何的增删改清空缓存
在MyBatis中,执行任何的增删改都会导致一级缓存清空!
* 测试代码:
@Test
public void test4() throws Exception { // 测试增删改清空缓存
// 开启一级缓存
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
// 将查询结果放入一级缓存
Emp emp = mapper.findById(1);
System.out.println(emp);
Emp saveEmp = new Emp();
saveEmp.setName("test");
mapper.save(saveEmp); // 任何的增删改都会导致缓存失效
// 首先从一级缓存里面查询(没有),去数据库查询,将查询的结果放入一级缓存
Emp emp2 = mapper.findById(1);
System.out.println(emp2);
System.out.println(emp == emp2); // false
// 关闭session(一级缓存清空)
session.close();
}

##### 2.2.3.5 flushCache清空缓存
flushCache属性用于控制执行完操作后是否要刷新缓存,对于增删改语句,**flushCache的值默认是true**,对于查询语句,flushCache的值默认是false,**但是MyBatis不支持将任何的增删改语句设置为false**;
* EmpDao.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!--
flushCache=“true”:每次执行完这个查询语句都清空一级缓存
–>
select * from emp where id=#{id}
<!--
flushCache: 执行完本次SQL语句是否要刷新缓存
insert/update/delete:默认为true,对于增删改的操作,MyBatis不支持将其设置为false
select:默认为false
–>
insert into emp values(null,#{name},#{age},#{addr},#{salary},null)
>
> Tips:在MyBatis中,不支持将任何的增删改语句的flushCache属性设置为false
>
>
>
* 测试代码:
@Test
public void test5() throws Exception { // 测试flushCache清空缓存(对于增删改操作无法设置为false)
// 开启一级缓存
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
Emp emp = mapper.findById(1);
System.out.println(emp);
Emp emp2 = mapper.findById(1);
System.out.println(emp2);
System.out.println(emp == emp2);
// 关闭session(一级缓存清空)
session.close();
}
执行程序,观察控制台:

**需要注意的是MyBatis并不支持将任何的增删改语句的flushCache设置为false**
* 测试代码(此时save语句的flushCache为false):
/**
* 测试增删改的flushCache属性
*/
@Test
public void test6() {
// 开启一级缓存
SqlSession session = factory.openSession();
EmpDao empDao = session.getMapper(EmpDao.class);
// 将查询到的结果存入一级缓存缓存(发送一次SQL)
Emp emp = empDao.findById(1); // flushCache="true",因此会清空一级缓存
System.out.println(emp);
// 进行增删改操作(一级缓存清空,即使设置了flushCache=false也不管用)
empDao.save(new Emp(null, "test", 20, null, null, null));
// 重新发送SQL语句
Emp emp2 = empDao.findById(1);
System.out.println(emp2);
// session关闭,一级缓存清空
session.close();
}

#### 2.2.4 一级缓存的引用
MyBatis将数据存入一级缓存时,是将对象的引用(内存地址)存入一级缓存;在获取一级缓存中的数据时,MyBatis将返回当初存入一级缓存的那个内存地址值,也就是说,一级缓存中的数据是同一个;这样一来就会出现内存地址值引用问题;
* 测试代码:
// 缓存的引用
@Test
public void test7() throws Exception {
// 开启一级缓存
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
// 去数据库查询,将查询结果存入一级缓存
Emp emp = mapper.findById(1);
System.out.println(emp); // name=张三
// 修改emp对象的name为abc(一级缓存中的emp也会修改)
emp.setName("abc");
// 从一级缓存中查询emp对象
Emp emp2 = mapper.findById(1);
System.out.println(emp2); // name=abc
System.out.println(emp == emp2);
// 关闭session(一级缓存清空)
session.close();
}
#### 2.2.5 PerpetualCache缓存类
MyBatis 跟缓存相关的类都在cache 包里面,其中有一个Cache 接口,只有一个默认的实现类 PerpetualCache,改类是MyBatis的缓存实现类;包括一级缓存和二级缓存都是采用PerpetualCache类来实现的;

* 测试代码:
@Test
public void test1() throws Exception {
// 开启一级缓存
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
/\*
首先从一级缓存里面查询
查询到了: 返回
没查询到: 去数据库查询,之后将查询的结果放入一级缓存
*/
Emp emp = mapper.findById(1);
System.out.println(emp);
/\*
从一级缓存里面查询,直接返回
*/
Emp emp2 = mapper.findById(1);
System.out.println(emp2);
System.out.println(emp == emp2); // true
// 关闭session(一级缓存清空)
session.close();
}
一级缓存执行流程:

### 2.3 二级缓存
我们刚刚学习完了一级缓存,一级缓存是session级别的缓存,不同的session一级缓存是不能共享的;
**二级缓存是mapper级别的缓存**,多个session去操作同一个Mapper映射的SQL语句时,多个session可以共用二级缓存,二级缓存是跨SqlSession 的。
**当二级缓存和一级缓存同时存在时,先查询二级缓存,再查询一级缓存;**

#### 2.3.1 二级缓存相关配置
* 全局配置:
MyBatis默认是开启二级缓存的,我可以通过`cacheEnabled`参数来控制二级缓存的开关;**此配置默认开启**
* 修改SqlMapConfig配置文件:
<!--开启二级缓存,默认是开启状态,false为关闭二级缓存-->
<setting name="cacheEnabled" value="true"/>
* Mapper配置以及SQL语句配置:
**在MyBatis核心配置文件中开启二级缓存后(此配置默认开启状态),还需要在对应的mapper.xml文件中激活缓存;否则二级缓存并不会生效;**
<?xml version="1.0" encoding="UTF-8"?>
<!--
useCache: 当一级缓存关闭时,是否将本次SQL语句的结果集存入二级缓存
true: 存入(默认值)
false: 不存入
–>
select * from emp where id=#{id}
<insert id="save" >
insert into emp(id,name) values(null,#{name});
</insert>
**存入二级缓存中的对象必须实现序列化接口:**

#### 2.3.2 二级缓存测试
##### 2.3.2.1 二级缓存代码测试
**当二级缓存和一级缓存同时存在时,先查询二级缓存,再查询一级缓存;**
当session关闭时,将一级缓存中的数据写入二级缓存;
@Test
public void test1() throws Exception { // 测试二级缓存
// 开启一级缓存
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
/*
先从二级缓存里面查询–>再查询一级缓存–>再查询数据库(查询到了把结果放入一级缓存)
*/
Emp emp = mapper.findById(1);
System.out.println(emp);
// 关闭一级缓存,把数据写入二级缓存(这个时候才会把数据写入二级缓存(序列化))
session.close();
// session.clearCache(); // session关闭才会将一级缓存的内容写入二级缓存,clearCache并不会
// 开启一级缓存
SqlSession session2 = factory.openSession();
EmpDao mapper2 = session2.getMapper(EmpDao.class);
// 先查询二级缓存(有),反序列化
Emp emp2 = mapper2.findById(1);
System.out.println(emp2);
System.out.println(emp == emp2); // false ,两个对象的属性值一样,但是内存地址值不一样(反序列化出来的)
session2.close();
}
观察日志:

##### 2.3.2.2 useCache参数
useCache可以控制当前SQL语句的结果集是否要存入二级缓存;默认情况下为true
select * from emp where id=#{id}
>
> Tips:
>
>
> * 1)useCache只能控制二级缓存,并不会影响一级缓存;
> * 2)useCache需要当前的mapper.xml开启二级缓存功能后才能使用;
>
>
>
#### 2.3.3 二级缓存失效情况
二级缓存在如下情况下,会情况:
* 1)执行任何**增删改**操作
* 2)刷新缓存(flushCache)
##### 2.3.3.1 执行增删改
**执行任何的增删改操作,不仅会导致一级缓存清空,也会导致二级缓存清空!**
* 测试代码:
/**
* 测试任何增删改清空二级缓存
*/
@Test
public void test2() throws Exception {
// 开启一级缓存
SqlSession session = factory.openSession(true);
EmpDao mapper = session.getMapper(EmpDao.class);
// 先从二级缓存里面查询,再从一级缓存里面查询,再从数据库查询(发送SQL),将查询到的结果集存入一级缓存
Emp emp = mapper.findById(1);
System.out.println(emp);
// 关闭一级缓存,把数据写入二级缓存(这个时候才会把数据写入二级缓存(序列化))
session.close();
// 开启一级缓存
SqlSession session2 = factory.openSession(true);
EmpDao mapper2 = session2.getMapper(EmpDao.class);
Emp saveEmp = new Emp();
saveEmp.setName("aaa");
mapper2.save(saveEmp); // 执行任何的增删改清空所有缓存(一级缓存和二级缓存)
// 查询二级缓存(没有),一级缓存(没有),发送SQL
Emp emp2 = mapper2.findById(1);
System.out.println(emp2);
session2.close();
}
执行程序,日志如下:
Created connection 368342628.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@15f47664]
> Preparing: select * from dept where id=? # 第一次发送SQL
> Parameters: 1(Integer)
< Columns: id, name, location
< Row: 1, 研发部, 中国台湾
<== Total: 1
研发部
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@15f47664]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@15f47664] # 关闭session,将数据写入二级缓存
Returned connection 368342628 to pool.
Opening JDBC Connection
Checked out connection 368342628 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@15f47664]
> Preparing: insert into dept values(null,?,?) # 执行任何增删改清空一级/二级缓存
> Parameters: test(String), test(String)
< Updates: 1
Cache Hit Ratio [com.dfbz.dao.DeptDao]: 0.5
> Preparing: select * from dept where id=? # 再次发送SQL查询
> Parameters: 1(Integer)
< Columns: id, name, location
< Row: 1, 研发部, 中国台湾
< Total: 1
false # 返回false
Rolling back JDBC Connection [com.mysql.jdbc.JDBC4Connection@15f47664]
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@15f47664]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@15f47664]
Returned connection 368342628 to pool.
Disconnected from the target VM, address: ‘127.0.0.1:58814’, transport: ‘socket’
Process finished with exit code 0
##### 2.3.3.2 flushCache
flushCache不仅会清空一级缓存,而且还会清空二级缓存

>
> Tips:flushCache控制二级缓存时可以设置任何的**增删改查**是否清空二级缓存
>
>
>
* 测试代码:
/*
测试flushCache
*/
@Test
public void test4() throws Exception {
// 开启一级缓存
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
// 先从二级缓存里面查询,再从一级缓存里面查询,再从数据库查询(发送SQL),之后flushCache,一级缓存数据清空
Emp emp = mapper.findById(1);
System.out.println(emp);
// 一级缓存关闭,将一级缓存的数据写入二级缓存(此时一级缓存并没有数据)
session.close();
// 获取session,开启新的一级缓存
SqlSession session2 = factory.openSession();
EmpDao mapper2 = session2.getMapper(EmpDao.class);
// 此时二级缓存,一级缓存均没有数据,最终去数据库查询
Emp emp2 = mapper2.findById(1);
System.out.println(emp2);
session2.close();
}
执行程序,日志如下:
Created connection 527829831.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
> Preparing: select * from emp where id=? # 发送SQL语句查询,并没有将结果存入一级缓存(flushCache)
> Parameters: 1(Integer)
< Columns: id, name, age, addr, salary, dept_id
< Row: 1, 张三, 20, 广西来宾, 7600.00, 1
<== Total: 1
Emp(id=1, name=张三, age=20, addr=广西来宾, salary=7600.0, deptId=null)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
sesison关闭,将一级缓存的数据写入二级缓存(此时一级缓存中并没有数据)
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
Returned connection 527829831 to pool.
Cache Hit Ratio [com.dfbz.dao.EmpDao]: 0.5
Opening JDBC Connection # 重新开启一个session
Checked out connection 527829831 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
先查询二级缓存(没查询到),再查询一级缓存(没查询到),最终去查询数据库
> Preparing: select * from emp where id=?
> Parameters: 1(Integer)
< Columns: id, name, age, addr, salary, dept_id
< Row: 1, 张三, 20, 广西来宾, 7600.00, 1
<== Total: 1
Emp(id=1, name=张三, age=20, addr=广西来宾, salary=7600.0, deptId=null)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
Returned connection 527829831 to pool.
Disconnected from the target VM, address: ‘127.0.0.1:52082’, transport: ‘socket’
Process finished with exit code 0
**flushCache不仅会清空一级缓存,而且也会清空二级缓存**
* 扩展一个方法:
Emp findByName(String name);
* EmpDao.xml:
select * from emp where name=#{name}

* 测试代码:
@Test
public void test5() throws Exception {
// 开启一级缓存
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
// 先从二级缓存里面查询,再从一级缓存里面查询,再从数据库查询(发送SQL),将查询到的结果集存入一级缓存
Emp emp = mapper.findByName("小明");
System.out.println(emp);
// 一级缓存关闭,将一级缓存的数据写入二级缓存
session.close();
// 获取session,开启新的一级缓存
SqlSession session2 = factory.openSession();
EmpDao mapper2 = session2.getMapper(EmpDao.class);
// flushCache清空一级缓存(会清空二级缓存)
Emp emp2 = mapper2.findById(1);
System.out.println(emp2);
// 去二级缓存查询,查询不到,从一级缓存查询,查询不到,发送SQL语句
Emp emp3 = mapper2.findByName("小明");
System.out.println(emp3);
session2.close();
}
执行程序,日志如下:
Created connection 527829831.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
将数据写入一级缓存
> Preparing: select * from emp where name=?
> Parameters: 小明(String)
< Columns: id, name, age, addr, salary, dept_id
< Row: 3, 小明, 25, 广东云浮, 6600.00, 2
<== Total: 1
Emp(id=3, name=小明, age=25, addr=广东云浮, salary=6600.0, deptId=null)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
连接关闭,将数据写入二级缓存
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
Returned connection 527829831 to pool.
Cache Hit Ratio [com.dfbz.dao.EmpDao]: 0.0
Opening JDBC Connection
Checked out connection 527829831 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
执行findById(flushCache清空一级缓存和二级缓存)
> Preparing: select * from emp where id=?
> Parameters: 1(Integer)
< Columns: id, name, age, addr, salary, dept_id
< Row: 1, 张三, 20, 广西来宾, 7600.00, 1
<== Total: 1
Emp(id=1, name=张三, age=20, addr=广西来宾, salary=7600.0, deptId=null)
Cache Hit Ratio [com.dfbz.dao.EmpDao]: 0.3333333333333333
再次发送SQL去数据库查询
> Preparing: select * from emp where name=?
> Parameters: 小明(String)
< Columns: id, name, age, addr, salary, dept_id
< Row: 3, 小明, 25, 广东云浮, 6600.00, 2
<== Total: 1
Emp(id=3, name=小明, age=25, addr=广东云浮, salary=6600.0, deptId=null)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
Returned connection 527829831 to pool.
Disconnected from the target VM, address: ‘127.0.0.1:52207’, transport: ‘socket’
Process finished with exit code 0
**前面我们学习一级缓存的时候,flushCache对于增删改语句是无法设置为false(设置了不生效),即执行任何增删改的时候一定会清空一级缓存,但flushCache却可以控制二级缓存的增删改;**
将insert语句的flushCache设置为false(不清空缓存):

* 测试代码:
@Test
public void test6() throws Exception {
// 开启一级缓存
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
// 先从二级缓存里面查询,再从一级缓存里面查询,再从数据库查询(发送SQL),将查询到的结果集存入一级缓存
Emp emp = mapper.findByName("小明");
System.out.println(emp);
// 一级缓存关闭,将一级缓存的数据写入二级缓存
session.close();
// 获取session,开启新的一级缓存
SqlSession session2 = factory.openSession();
EmpDao mapper2 = session2.getMapper(EmpDao.class);
// 执行新增(flushCache为false,并不会清空二级缓存)
mapper2.save(new Emp(null,"xxx",null,null,null,null));
// 先查询二级缓存(查询到了)
Emp emp2 = mapper2.findByName("小明");
System.out.println(emp2);
session2.close();
}
日志如下:
Created connection 527829831.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
> Preparing: select * from emp where name=?
> Parameters: 小明(String)
< Columns: id, name, age, addr, salary, dept_id
< Row: 3, 小明, 25, 广东云浮, 6600.00, 2
<== Total: 1
Emp(id=3, name=小明, age=25, addr=广东云浮, salary=6600.0, deptId=null)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
Returned connection 527829831 to pool.
Opening JDBC Connection
Checked out connection 527829831 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
==> Preparing: insert into emp(id,name) values(null,?);
> Parameters: xxx(String)
< Updates: 1
Cache Hit Ratio [com.dfbz.dao.EmpDao]: 0.5
Emp(id=3, name=小明, age=25, addr=广东云浮, salary=6600.0, deptId=null)
Rolling back JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
Returned connection 527829831 to pool.
Disconnected from the target VM, address: ‘127.0.0.1:52373’, transport: ‘socket’
Process finished with exit code 0
#### 2.3.4 PerpetualCache的二级缓存
## 写在最后
**在结束之际,我想重申的是,学习并非如攀登险峻高峰,而是如滴水穿石般的持久累积。尤其当我们步入工作岗位之后,持之以恒的学习变得愈发不易,如同在茫茫大海中独自划舟,稍有松懈便可能被巨浪吞噬。然而,对于我们程序员而言,学习是生存之本,是我们在激烈市场竞争中立于不败之地的关键。一旦停止学习,我们便如同逆水行舟,不进则退,终将被时代的洪流所淘汰。因此,不断汲取新知识,不仅是对自己的提升,更是对自己的一份珍贵投资。让我们不断磨砺自己,与时代共同进步,书写属于我们的辉煌篇章。**
需要完整版PDF学习资源私我