文章目录

  • 二、Mybatis缓存机制
    • 2.1 缓存介绍
    • 2.2 一级缓存
      • 2.2.1 一级缓存相关参数
      • 2.2.2 一级缓存测试
      • 2.2.3 一级缓存清空
        • 2.2.3.1 设置localCacheScope
        • 2.2.3.2 clearCache
        • 2.2.3.3 关闭session清空缓存
        • 2.2.3.4 执行任何的增删改清空缓存
        • 2.2.3.5 flushCache清空缓存
      • 2.2.4 一级缓存的引用
      • 2.2.5 PerpetualCache缓存类
    • 2.3 二级缓存
      • 2.3.1 二级缓存相关配置
      • 2.3.2 二级缓存测试
        • 2.3.2.1 二级缓存代码测试
        • 2.3.2.2 useCache参数
      • 2.3.3 二级缓存失效情况
        • 2.3.3.1 执行增删改
        • 2.3.3.2 flushCache
      • 2.3.4 PerpetualCache的二级缓存
    • 2.4 MyBatis二级缓存相关配置
      • 2.4.1 二级缓存配置
        • 2.4.1.1 缓存大小
        • 2.4.1.2 缓存只读
    • 2.5 Redis替换二级缓存
      • 2.5.1 自定义缓存
      • 2.5.2 测试Redis替代二级缓存
    • 2.6 MyBatis缓存总结

二、Mybatis缓存机制

2.1 缓存介绍

像大多数的持久化框架一样, Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。Mybatis 中缓存分为一级缓存,二级缓存。

1、默认情况下,只有一级缓存(session级别的缓存,也称为本地缓存)开启。

2、二级缓存需要手动开启和配置(默认支持状态),他是基于Mapper级别的缓存。

3、为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

从图中我们可以看出:一级缓存是基于SqlSessoion的缓存,一级缓存的内容不能跨sqlsession。由MyBatis自动维护。二级缓存是基于映射文件的缓存,缓存范围比一级缓存更大。不同的sqlsession可以访问二级缓存的内容。哪些数据放入二级缓存需要自己指定。

2.2 一级缓存

一级缓存,也叫本地缓存,作用域默认为当前的SqlSession也就是说不同的SqlSession的缓存是不同的。当 Session flush 或 close 后, 该Session中的一级缓存将被清空。一级缓存是MyBatis维护的,并且默认开启,且不能被关闭,但可以调用clearCache()来清空缓存,或者改变缓存的作用域;

2.2.1 一级缓存相关参数

  • localCacheScope

    • SESSION:会话级别缓存,默认值
    • STATEMENT:语句级别缓存,缓存只对当前执行的语句生效(相当于关闭一级缓存)
<!--一级缓存的作用域-->
<setting name="localCacheScope" value="SESSION"/>

2.2.2 一级缓存测试

  • 实体类:
package com.dfbz.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;/*** @author lscl* @version 1.0* @intro:*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Emp implements Serializable {private Integer id;private String name;private Integer age;private String addr;private Double salary;private Integer deptId;
}
  • 在EmpDao中准备方法:
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"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace 名称空间,指定对哪个接口进行映射-->
<mapper namespace="com.dfbz.dao.EmpDao"><select id="findById" resultType="emp">select * from emp where id=#{id}</select><insert id="save">insert into emp(id,name) values(null,#{name});</insert>
</mapper>
  • 测试类:
/*** 测试一级缓存*/
@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

<!--日志配置-->
<settings><setting name="logImpl" value="STDOUT_LOGGING"/><!--更改一级缓存的作用域SESSION: 会话级别缓存,默认值STATEMENT: 语句级别缓存,缓存只对当前执行的语句生效(类似于关闭一级缓存)--><setting name="localCacheScope" value="STATEMENT"/>
</settings>
  • 再次执行上述的测试代码:
/*** 测试一级缓存*/
@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"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.dfbz.dao.EmpDao"><!--flushCache="true":每次执行完这个查询语句都清空一级缓存--><select id="findById" resultType="emp" flushCache="true">select * from emp where id=#{id}</select><!--flushCache: 执行完本次SQL语句是否要刷新缓存insert/update/delete:默认为true,对于增删改的操作,MyBatis不支持将其设置为falseselect:默认为false--><insert id="save" flushCache="false">insert into emp values(null,#{name},#{age},#{addr},#{salary},null)</insert>
</mapper>

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=abcSystem.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配置文件:
<settings><!--日志配置--><setting name="logImpl" value="STDOUT_LOGGING"/><!--开启二级缓存,默认是开启状态,false为关闭二级缓存--><setting name="cacheEnabled" value="true"/>
</settings>
  • Mapper配置以及SQL语句配置:

在MyBatis核心配置文件中开启二级缓存后(此配置默认开启状态),还需要在对应的mapper.xml文件中激活缓存;否则二级缓存并不会生效;

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dfbz.dao.EmpDao"><!--<cache />: 开启当前Mapper的二级缓存eviction: 缓存的回收策略• LRU  – 最近最少使用的:移除最长时间不被使用的对象。• FIFO – 先进先出:按对象进入缓存的顺序来移除它们。• SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。• WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。flushInterval:缓存刷新间隔,缓存多长时间清空一次,默认不清空,可以通过此参数设置一个毫秒值readOnly:是否只读true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快false:非只读:mybatis觉得获取的数据可能会被修改。mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢size:二级缓存中最大能够缓存存放多少元素type:指定自定义缓存的全类名(实现Cache接口)--><cache /><!--useCache: 当一级缓存关闭时,是否将本次SQL语句的结果集存入二级缓存true: 存入(默认值)false: 不存入--><select id="findById" resultType="emp">select * from emp where id=#{id}</select><insert id="save" >insert into emp(id,name) values(null,#{name});</insert>
</mapper>

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

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

<!--useCache: 当前SQL语句的结果集是否存入二级缓存(需要当前的Mapper.xml开启二级缓存useCache才会生效)true: 存false: 不存
-->
<select id="findById" resultType="emp" useCache="true">select * from emp where id=#{id}
</select>

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);                  // 执行任何的增删改清空所有缓存(一级缓存和二级缓存)// 查询二级缓存(没有),一级缓存(没有),发送SQLEmp 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 id="findByName" resultType="emp" >select * from emp where name=#{name}
</select>

  • 测试代码:
@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的二级缓存

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);        // truesession.close();}
}

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

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 缓存大小

修改配置:

<cache size="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=张三// 因为是反序列化出来的对象,和原有对象不是同一个,返回falseSystem.out.println(emp == emp2);                                session1.close();
}

执行日志:

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

  • 修改二级缓存配置:
<cache 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=abc// trueSystem.out.println(emp == emp2);session1.close();
}

执行日志:

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)引入相关依赖:
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.9.1</version>
</dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.62</version>
</dependency>
  • 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();@Overridepublic String getId() {return id;}/*** 添加缓存** @param key: 缓存的key* @param val: 需要存储到缓存的值(是一个List类型)*/@Overridepublic void putObject(Object key, Object val) {jedis.set(key.toString(), JSON.toJSONString(val));System.out.println("putObject..............");}/*** 获取缓存中的内容** @param key* @return*/@Overridepublic 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*/@Overridepublic Object removeObject(Object key) {System.out.println("removeObject................");return jedis.del(key.toString());}/*** 清空缓存*/@Overridepublic void clear() {System.out.println("clear................");jedis.flushDB();}/*** 获取缓存中的key数量** @return*/@Overridepublic int getSize() {System.out.println("getSize................");return Integer.parseInt(jedis.dbSize() + "");}
}
  • 3)在EmpDao.xml中配置:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.dfbz.dao.EmpDao"><!--替换为我们自定义的缓存--><cache type="com.dfbz.cache.MyCache"></cache><select id="findById" resultType="Emp">select * from emp where id=#{id}</select>
</mapper>

2.5.2 测试Redis替代二级缓存

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;
import org.junit.Before;
import org.junit.Test;/*** @author lscl* @version 1.0* @intro:*/
public class Demo01 {SqlSessionFactory factory;@Beforepublic void before() throws Exception {factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml"));}@Testpublic void test1() throws Exception {// 开启一级缓存SqlSession session = factory.openSession();EmpDao mapper = session.getMapper(EmpDao.class);// 先从二级缓存里面查询,再从一级缓存里面查询,再从数据库查询(发送SQL),将查询到的结果集存入一级缓存Emp emp = mapper.findById(1);System.out.println(emp);// 关闭一级缓存,把数据写入二级缓存(写入到redis)session.close();// 开启一级缓存SqlSession session2 = factory.openSession();EmpDao mapper2 = session2.getMapper(EmpDao.class);// 先从二级缓存里面获取(从redis里面获取)Emp emp2 = mapper2.findById(1);System.out.println(emp2);System.out.println(emp == emp2);            // false ,两个对象的属性值一样,但是内存地址值不一样(反序列化出来的)session2.close();}
}

2.6 MyBatis缓存总结

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

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

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

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

<settings><!--开启二级缓存支持 --><setting name="cacheEnabled" value="true"/>
</settings>

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

<cache></cache>
<select id="findById" parameterType="int" resultType="user" useCache="true">select * from user where id=#{id}
</select>

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

11【MyBatis的缓存机制】相关推荐

  1. MyBatis-学习笔记11【11.Mybatis的缓存】

    Java后端 学习路线 笔记汇总表[黑马程序员] MyBatis-学习笔记01[01.Mybatis课程介绍及环境搭建][day01] MyBatis-学习笔记02[02.Mybatis入门案例] M ...

  2. MyBatis的缓存机制详解

    MyBatis的缓存机制详解 MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制,缓存可以极大的提升查询效率.MyBatis中默认定义了两级缓存,分别是一级缓存和二级缓存. ( ...

  3. MyBatis:缓存机制详解

    本篇内容包括:MyBatis 缓存机制概述.一级缓存与二级缓存的介绍.配置和具体流程. 一.MyBatis 缓存机制概述 在我们常见的 OLTP(on-line transaction process ...

  4. mybatis的缓存机制是怎么样的?

    正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持. 1.缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 ...

  5. MyBatis之缓存机制

    缓存即为存在内存中的临时数据.将用户经常查询的数据存放在缓存(内存)中,用户去查询数据就不用去每次去数据库中查询,而是去缓存中查询,从而提高了查询的效率,解决了高并发系统的性能问题.MyBatis提供 ...

  6. Hibernate和MyBatis的缓存机制和比较

    原文地址:https://my.oschina.NET/u/1445731/blog/416200?p=%7B%7BtotalPage%7D%7D Mybatis缓存 分为1级缓存和2级缓存,2级缓存 ...

  7. (十二)Mybatis的缓存机制

    文章目录 环境 Mybatis的缓存 一级缓存 一级缓存失效 方式一 方式二 二级缓存 MyBatis集成EhCache缓存 Mybatis学习目录 上一篇:(十一)MyBatis的高级映射及延迟加载 ...

  8. mybatis的缓存机制(一级缓存二级缓存和刷新缓存)和mybatis整合ehcache

    1      查询缓存 1.1  什么是查询缓存 mybatis提供查询缓存,用于减轻数据压力,提高数据库性能. mybaits提供一级缓存,和二级缓存. 一级缓存是SqlSession级别的缓存.在 ...

  9. 框架:mybatis的缓存机制

    一级缓存是SqlSession级别的缓存.在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据.不同的sqlSession之间的缓存数 ...

最新文章

  1. 阿里全球数学竞赛第二届
  2. Chrome控制台使用详解
  3. mysql删除了密码怎样恢复_window 下如何恢复被删除的mysql root账户及密码(mysql 8.0.17)...
  4. 最详细的最小堆构建、插入、删除的过程图解
  5. Codeforces Round #590 (Div. 3) F. Yet Another Substring Reverse 子集dp
  6. 在带有组合框的值列表的下拉列表中显示显示属性的子集
  7. 图形用户界面和交互输入方法---图形数据的输入功能
  8. python-演练-通过描述符来控制另一个类的实例化参数
  9. 判断php图片是否存在,php判断远程图片是否存在
  10. 数值分析:插值与拟合
  11. 为什么有些人退休后,一下子衰老了很多?
  12. ZDNS正式成为“.ren”顶级域名注册管理机构
  13. 基于Java后台(Springboot框架)+前端小程序(MINA框架)+Mysql数据库的在线电子书阅读小程序系统设计与实现
  14. linux wps 文件关联,WPS文件扩展名 - 什么是.wps以及如何打开? - ReviverSoft
  15. Mysql delete删除表数据之后,表空间没有释放的问题
  16. 巡逻机器人(Patrol Roboot,UVa1600)
  17. kafka broker 进入 conflicted ephemeral node 死循环
  18. 如何搭建一个机器人控制系统
  19. day001:数据结构和算法-时间频度和时间复杂度
  20. insmod: error inserting 'vivi.ko': -1 Unknown symbol in module 问题解决办法

热门文章

  1. 全国各大高校FTP地址大全(好东东加威望鼓励一下)
  2. C#中如何使用TimeSpan
  3. torch 决策回归树
  4. 隐马尔可夫模型-概率计算算法
  5. 实验二:OpenGL的简单动画
  6. 【JAVA学习笔记】你知道的越多,你不知道的越多系列一
  7. (概率论与数理统计)样本方差
  8. Java基础教程——多线程:创建线程
  9. python作业网站_php网站编程作业代写、代做php网站小作业
  10. 华云积极响应合肥高新区抗疫号召:践行社会责任 贡献科技企业力量