MyBatis一级缓存和二级缓存全面详解

在结束之际,我想重申的是,学习并非如攀登险峻高峰,而是如滴水穿石般的持久累积。尤其当我们步入工作岗位之后,持之以恒的学习变得愈发不易,如同在茫茫大海中独自划舟,稍有松懈便可能被巨浪吞噬。然而,对于我们程序员而言,学习是生存之本,是我们在激烈市场竞争中立于不败之地的关键。一旦停止学习,我们便如同逆水行舟,不进则退,终将被时代的洪流所淘汰。因此,不断汲取新知识,不仅是对自己的提升,更是对自己的一份珍贵投资。让我们不断磨砺自己,与时代共同进步,书写属于我们的辉煌篇章。

需要完整版PDF学习资源

需要体系化学习资料的朋友,可以加我V获取:vip204888 (备注网络安全)

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以点击这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

// 开启一级缓存
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();

}


![在这里插入图片描述](https://img-blog.csdnimg.cn/d858d8eb0a1741e8bbd585849136b00f.png#pic_center)


#### 2.2.4 一级缓存的引用


MyBatis将数据存入一级缓存时,是将对象的引用(内存地址)存入一级缓存;在获取一级缓存中的数据时,MyBatis将返回当初存入一级缓存的那个内存地址值,也就是说,一级缓存中的数据是同一个;这样一来就会出现内存地址值引用问题;


* 测试代码:



// 缓存的引用
@Test
public void test6() 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类来实现的;


![在这里插入图片描述](https://img-blog.csdnimg.cn/5e0b0da05a344884bf4926a94baebd17.png#pic_center)


* 测试代码:



@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();

}


一级缓存执行流程:


![在这里插入图片描述](https://img-blog.csdnimg.cn/b54cfe16b4d74bd98e04cdb6b5634ddb.png#pic_center)


### 2.3 二级缓存


我们刚刚学习完了一级缓存,一级缓存是session级别的缓存,不同的session一级缓存是不能共享的;


**二级缓存是mapper级别的缓存**,多个session去操作同一个Mapper映射的SQL语句时,多个session可以共用二级缓存,二级缓存是跨SqlSession 的。


**当二级缓存和一级缓存同时存在时,先查询二级缓存,再查询一级缓存;**


![在这里插入图片描述](https://img-blog.csdnimg.cn/29bc086cd5d948629c7e81e399dade80.png#pic_center)


#### 2.3.1 二级缓存相关配置


* 全局配置:


MyBatis默认是开启二级缓存的,我可以通过`cacheEnabled`参数来控制二级缓存的开关;**此配置默认开启**


* 修改SqlMapConfig配置文件:



<!--开启二级缓存,默认是开启状态,false为关闭二级缓存-->
<setting name="cacheEnabled" value="true"/>

* Mapper配置以及SQL语句配置:


**在SqlMapConfig.xml开启二级缓存后(此配置默认开启状态),还需要在对应的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>

**存入二级缓存中的对象必须实现序列化接口:**


![在这里插入图片描述](https://img-blog.csdnimg.cn/8fcbe5934b7746289c889bccb737e787.png#pic_center)


#### 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();

}


观察日志:


![在这里插入图片描述](https://img-blog.csdnimg.cn/b95cddea5d854bf7acde6b6bcc5a9f37.png#pic_center)


##### 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不仅会清空一级缓存,而且还会清空二级缓存


![在这里插入图片描述](https://img-blog.csdnimg.cn/7c5c6db49a284a9499d03bc3cee142da.png#pic_center)



> 
> 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}

![在这里插入图片描述](https://img-blog.csdnimg.cn/adac7e18b9d44bc2af94935838194164.png#pic_center)


* 测试代码:



@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);

// 先查询二级缓存(查询到了)
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(不清空缓存):


![在这里插入图片描述](https://img-blog.csdnimg.cn/5dffa52856914940835cfcd520dd6bf1.png#pic_center)


* 测试代码:



@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的二级缓存


PerpetualCache不仅是MyBaits的一级缓存类,同时MyBatis也将二级缓存的数据存储在PerpetualCache中;在MyBatis加载时(创建session工厂时)就已经创建好了二级缓存,只不过需要等到session关闭时,本次session的一级缓存的数据才会写入二级缓存;


* 测试代码:



package com.dfbz.mybatis;

import com.dfbz.dao.EmpDao;
import com.dfbz.entity.Emp;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_一级缓存和二级缓存 {

public static void main(String[] args) throws Exception {

    // 创建Session工厂构建对象
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

    // 通过构建对象来构建一个session工厂(创建二级缓存)
    SqlSessionFactory factory = builder.build(Resources.getResourceAsStream("SqlMapConfig.xml"));

    // 获取session(创建一级缓存)
    SqlSession session = factory.openSession();

    // 获取mapper类
    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.close();
}

}


一级缓存和二级缓存查询流程:


![在这里插入图片描述](https://img-blog.csdnimg.cn/a8f3d42e06b44dcaace3475bbc5f7cda.png#pic_center)


### 2.4 MyBatis二级缓存相关配置


#### 2.4.1 二级缓存配置


* `<cache />`:开启当前Mapper的二级缓存
	+ `eviction`:当内存使用紧张时,缓存的回收策略缓存的回收策略
		- `LRU`:最近最少使用的:移除最长时间不被使用的对象。
		- `FIFO`:先进先出:按对象进入缓存的顺序来移除它们。
		- `SOFT`:软引用:移除基于垃圾回收器状态和软引用规则的对象。
		- `WEAK`:弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
	+ `flushInterval`:缓存刷新间隔,缓存多长时间清空一次,默认不清空,可以通过此参数设置一个毫秒值
	+ `readOnly`:是否只读
		- `true`:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
		- `false`:非只读(默认值);mybatis觉得获取的数据可能会被修改。
	+ `size`:二级缓存中最大能够缓存存放多少元素
	+ `type`:指定自定义缓存的全类名(实现Cache接口)


##### 2.4.1.1 缓存大小


修改配置:




* 测试代码:



@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(2);
System.out.println(emp2);

System.out.println(emp == emp2);        // true

// 关闭session,将数据写入二级缓存(此时一级缓存的大小为1)
session.close();

SqlSession session1 = factory.openSession();

EmpDao mapper1 = session1.getMapper(EmpDao.class);
Emp emp3 = mapper1.findById(2);         // 二级缓存不存在,一级缓存也不存在,发送SQL去查询

session1.close();

}


执行日志如下:



Created connection 527829831.
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)
Cache Hit Ratio [com.dfbz.dao.EmpDao]: 0.0
> Preparing: select * from emp where id=?
> Parameters: 2(Integer)
<
Columns: id, name, age, addr, salary, dept_id
<
Row: 2, 李四, 22, 浙江绍兴, 6800.00, 4
<== Total: 1
Emp(id=2, name=李四, age=22, addr=浙江绍兴, salary=6800.0, deptId=null)
false
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]

关闭session将数据写入二级缓存,只能存储一条

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]

再次查询二级缓存发现没有,然后查询一级缓存也没有,最终发送SQL去查询数据库

> Preparing: select * from emp where id=?
> Parameters: 2(Integer)
<
Columns: id, name, age, addr, salary, dept_id
<
Row: 2, 李四, 22, 浙江绍兴, 6800.00, 4
<== Total: 1
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:51525’, transport: ‘socket’


##### 2.4.1.2 缓存只读


之前我们学一级缓存的时候提到过,一级缓存里面存储的是对象的引用(内存地址),当对象内容修改了,一级缓存里面存储的那个对象内容也会随之修改,而二级缓存里面存储的是对象序列化之后的值,换句话来说就是类似与拷贝了一份新的数据到二级缓存;因此修改原有对象不会影响二级缓存里面的对象内容;


* 测试代码:



@Test
public void test2() throws Exception {
// 开启一级缓存
SqlSession session = factory.openSession();

EmpDao mapper = session.getMapper(EmpDao.class);

// 数据存入一级缓存
Emp emp = mapper.findById(1);
System.out.println("------------emp1: " + emp);                 // name=张三

// 关闭session,将一级缓存的数据序列化(拷贝)到二级缓存
session.close();

// 修改emp对象的属性值
emp.setName("abc");

SqlSession session1 = factory.openSession();

EmpDao mapper1 = session1.getMapper(EmpDao.class);

// 从二级缓存中反序列化出来
Emp emp2 = mapper1.findById(1);
System.out.println("------------emp2: " + emp2);                // name=张三

// 因为是反序列化出来的对象,和原有对象不是同一个,返回false
System.out.println(emp == emp2);                                
session1.close();

}


执行日志:


![在这里插入图片描述](https://img-blog.csdnimg.cn/4040ee7899ac48549f4c5f7e61426d00.png#pic_center)


而这样带来的弊端就是每次session关闭的时候都需要将数据序列化一份,这不仅从时间角度上效率低了,从空间角度上效率也会变低,因为每次都在序列化(拷贝)新的对象;如果我们对数据的修改并不敏感,或者不会对数据进行修改,那么我们可以将二级缓存的readOnly设置为true;此时二级缓存里面存储的是对象的引用,并不会采用序列化技术,此时的对象也不需要实现序列化接口了;


* 修改二级缓存配置:




* 测试代码:



@Test
public void test2() throws Exception {
// 开启一级缓存
SqlSession session = factory.openSession();

EmpDao mapper = session.getMapper(EmpDao.class);

// 数据存入一级缓存
Emp emp = mapper.findById(1);
System.out.println("------------emp1: " + emp);                 // name=张三

// 关闭session,将一级缓存的数据序列化(拷贝)到二级缓存
session.close();

// 修改emp对象的属性值
emp.setName("abc");

SqlSession session1 = factory.openSession();

EmpDao mapper1 = session1.getMapper(EmpDao.class);

// 从二级缓存中反序列化出来
Emp emp2 = mapper1.findById(1);
System.out.println("------------emp2: " + emp2);                // name=张三

// 因为是反序列化出来的对象,和原有对象不是同一个,返回false
System.out.println(emp == emp2);
session1.close();

}


执行日志:


![在这里插入图片描述](https://img-blog.csdnimg.cn/5879bde440f544f5ab917749a6c8c8a7.png#pic_center)


### 2.5 Redis替换二级缓存


查看Cache接口:



package org.apache.ibatis.cache;

import java.util.concurrent.locks.ReadWriteLock;

public interface Cache {
String getId();

void putObject(Object var1, Object var2);

Object getObject(Object var1);

Object removeObject(Object var1);

void clear();

int getSize();

default ReadWriteLock getReadWriteLock() {
    return null;
}

}


#### 2.5.1 自定义缓存


我们可以自定义一个类,重写Cache接口,让MyBatis存入二级缓存的数据交给我们来处理!


* 1)引入相关依赖:



redis.clients jedis 2.9.1 com.alibaba fastjson 1.2.62

* 2)重写Cache接口:



package com.dfbz.cache;

import com.alibaba.fastjson.JSON;
import com.dfbz.entity.Emp;
import org.apache.ibatis.cache.Cache;
import redis.clients.jedis.Jedis;

import java.util.List;

/**
* @author lscl
* @version 1.0
* @intro:
*/
public class MyCache implements Cache {
private Jedis jedis = new Jedis();

@Override
public String getId() {
    return id;
}

/\*\*

* 添加缓存
*
* @param key: 缓存的key
* @param val: 需要存储到缓存的值(是一个List类型)
*/
@Override
public void putObject(Object key, Object val) {

    jedis.set(key.toString(), JSON.toJSONString(val));

    System.out.println("putObject..............");
}

/\*\*

* 获取缓存中的内容
*
* @param key
* @return
*/
@Override
public Object getObject(Object key) {

    System.out.println("getObject................");

    String jsonStr = jedis.get(key.toString());

    List<Emp> list = JSON.parseArray(jsonStr, Emp.class);

    return list;
}

/\*\*

* 删除缓存
*
* @param key
* @return
*/
@Override
public Object removeObject(Object key) {
System.out.println(“removeObject…”);
return jedis.del(key.toString());
}

/\*\*

* 清空缓存
*/
@Override
public void clear() {
System.out.println(“clear…”);
jedis.flushDB();
}

/\*\*

* 获取缓存中的key数量
*
* @return
*/
@Override
public int getSize() {
System.out.println(“getSize…”);
return Integer.parseInt(jedis.dbSize() + “”);
}
}


* 3)在EmpDao.xml中配置:



<?xml version="1.0" encoding="UTF-8"?>
<!--替换为我们自定义的缓存-->
<cache type="com.dfbz.cache.MyCache"></cache>

<select id="findById" resultType="Emp">
    select * from emp where id=#{id}
</select>

![在这里插入图片描述](https://img-blog.csdnimg.cn/acd7111fdff14f15a41537ddb4782445.png#pic_center)


#### 2.5.2 测试Redis替代二级缓存



package com.dfbz.test;

import com.dfbz.dao.EmpDao;
import com.dfbz.entity.Emp;
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 org.junit.Before;
import org.junit.Test;

import java.io.IOException;

public class Demo03_自定义缓存 {

private SqlSessionFactoryBuilder builder;

private SqlSessionFactory factory;

@Before
public void before() throws IOException {
    builder = new SqlSessionFactoryBuilder();
    factory = builder.build(Resources.getResourceAsStream("SqlMapConfig.xml"));
}

@Test
public void test1() {

    // 开启一级缓存
    SqlSession session = factory.openSession();

    EmpDao mapper = session.getMapper(EmpDao.class);

    // 首先从二级缓存去查询,没查询到则去一级缓存查询,然后将结果存入一级缓存
    Emp emp = mapper.findById(1);
    System.out.println(emp);

    // 关闭session(将数据存入二级缓存)
    session.close();

    SqlSession session2 = factory.openSession();

    EmpDao mapper2 = session2.getMapper(EmpDao.class);

    Emp emp2 = mapper2.findById(1);
    System.out.println(emp2);

    session.close();
}

}


![在这里插入图片描述](https://img-blog.csdnimg.cn/0269d754a9f2415ca179b14d53832813.png#pic_center)


### 2.6 MyBatis缓存总结


一级缓存和二级缓存的执行顺序为:**二级缓存---->一级缓存---->数据库**


查询语句始终是由一级缓存发送的,一级缓存默认由MyBatis维护,我们只需了解使用即可


二级缓存需要我们可以修改配置:


1、在MyBatis中,二级缓存是默认开启状态,在SqlMapConfig.xml中设置二级缓存的支持情况




2、在mapper文件中加入`<cache>`标签代表激活本mapper文件的二级缓存,并保证useCache不为false(默认为true)





select * from user where id=#{id}


3、**MyBatis的二级缓存默认采用序列化/反序列化来保证对象的存取**,所以所有的entity对象都应该实现`serializable`接口



**先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7**

**深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年最新网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。**
![img](https://img-blog.csdnimg.cn/img_convert/2245b9c78d89f9239bfb8168cb36c59f.png)
![img](https://img-blog.csdnimg.cn/img_convert/a53c0048ca8b858a48c6c64285cf64ea.png)
![img](https://img-blog.csdnimg.cn/img_convert/505043bb680ea71083d7238700444808.png)
![img](https://img-blog.csdnimg.cn/img_convert/0c266f6c594a8ce581dbffc945535db1.png)
![img](https://img-blog.csdnimg.cn/img_convert/ffc379134d3c1976309108e588ff2205.png)
![img](https://img-blog.csdnimg.cn/img_convert/dd3b14edceeb12be0dcd5c87f956e960.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上网络安全知识点,真正体系化!**

**需要体系化学习资料的朋友,可以加我V获取:vip204888 (备注网络安全)**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.csdn.net/topics/618540462)**

*


查询语句始终是由一级缓存发送的,一级缓存默认由MyBatis维护,我们只需了解使用即可


二级缓存需要我们可以修改配置:


1、在MyBatis中,二级缓存是默认开启状态,在SqlMapConfig.xml中设置二级缓存的支持情况




2、在mapper文件中加入`<cache>`标签代表激活本mapper文件的二级缓存,并保证useCache不为false(默认为true)





select * from user where id=#{id}


3、**MyBatis的二级缓存默认采用序列化/反序列化来保证对象的存取**,所以所有的entity对象都应该实现`serializable`接口



**先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7**

**深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年最新网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。**
[外链图片转存中...(img-9o62eOWs-1715882345119)]
[外链图片转存中...(img-SGdGUdwO-1715882345121)]
[外链图片转存中...(img-UfYT8Jpa-1715882345122)]
[外链图片转存中...(img-9kvTjsNK-1715882345123)]
[外链图片转存中...(img-lAQ0Ygz6-1715882345124)]
[外链图片转存中...(img-XYAGdSKd-1715882345125)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上网络安全知识点,真正体系化!**

**需要体系化学习资料的朋友,可以加我V获取:vip204888 (备注网络安全)**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.csdn.net/topics/618540462)**

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值