MyBatis(五)缓存机制

MyBatis(五)缓存机制,第1张

MyBatis(五)缓存机制 缓存机制
  • MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
  • MyBatis系统中默认定义了两级缓存。
  • 一级缓存和二级缓存
    1、默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。
    2、二级缓存需要手动开启和配置,是基于namespace级别的缓存。
    3、为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。
一级缓存
  • 一级缓存(local cache), 即本地缓存, 作用域默认为sqlSession。当 Session flush 或 close 后, 该Session 中的所有 Cache 将被清空。
  • 一级缓存是一直开启的,时不能被关闭的, 但可以调用 clearCache() 来清空本地缓存, 或者改变缓存的作用域
  • 与数据库同一次会话期间查询到的数据会放在本地缓存中。以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库;
  • 在mybatis3.1之后, 可以配置本地缓存的作用域.。
    在 mybatis.xml 中配置
localCacheScopeMyBatis利用本地缓存机制( Local Cache )防止循环引用( circular references )和加速重复嵌套查询。默认值为SESSION ,这种情况下会缓存一 个会话中执行的所有查询若设置值为STATEMENT ,本地会话仅用在语句执行上,对相同SqlSession的不同调用将不会共享数据。SESSION /STATEMENTSESSION

同一次会话期间只要查询过的数据都会保存在当前SqlSession的一个Map中

  • key:hashCode+查询的SqlId+编写的sql查询语句+参数
  • 一级缓存失效的四种情况(发送多次sql查询)
    1、不同的SqlSession对应不同的一级缓存
    2、同一个SqlSession但是查询条件不同(当前一级缓存中还没有这个数据)
    3、同一个SqlSession两次查询期间执行了任何一次增删改 *** 作(这次增删改可能对当前数据有影响)
    4、同一个SqlSession两次查询期间手动清空了缓存

案例一:一级缓存体验

EmployeeMapper接口

Employee getEmpById(Integer id);

    select id, last_name lastName, email, gender
    from tbl_employee
    where id = #{id}

测试代码:

try {
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);

    Employee emp1 = mapper.getEmpById(1);
    System.out.println(emp1);

    Employee emp2 = mapper.getEmpById(1);
    System.out.println(emp2);

    System.out.println(emp1==emp2);

    sqlSession.commit();
} catch (Exception e) {
    e.printStackTrace();
}finally {
    sqlSession.close();
}

结果:

DEBUG 10-17 16:16:19,519 ==>  Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ?   (baseJdbcLogger.java:145) 
DEBUG 10-17 16:16:19,593 ==> Parameters: 1(Integer)  (baseJdbcLogger.java:145) 
DEBUG 10-17 16:16:19,629 <==      Total: 1  (baseJdbcLogger.java:145) 
Employee{id=1, lastname='张三', email='[email protected]', gender='1', dept=null}
Employee{id=1, lastname='张三', email='[email protected]', gender='1', dept=null}
true

Process finished with exit code 0

可以看出,第二次查询,并没有再次发送sql语句查询,而是将第一次查询的结果复制给第二次。这就是mybatis的一级缓存机制。

一级缓存失效情况1:不同的SqlSession

try {
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
	Employee emp1 = mapper.getEmpById(1);
    System.out.println(emp1);

    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class);

    Employee emp2 = mapper2.getEmpById(1);
    System.out.println(emp2);

    System.out.println(emp1==emp2);

    sqlSession.commit();
} catch (Exception e) {
    e.printStackTrace();
}finally {
    sqlSession.close();
}

结果:

DEBUG 10-17 16:29:15,730 ==>  Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ?   (baseJdbcLogger.java:145) 
DEBUG 10-17 16:29:15,821 ==> Parameters: 1(Integer)  (baseJdbcLogger.java:145) 
DEBUG 10-17 16:29:15,880 <==      Total: 1  (baseJdbcLogger.java:145) 
Employee{id=1, lastname='张三', email='[email protected]', gender='1', dept=null}
DEBUG 10-17 16:29:15,965 ==>  Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ?   (baseJdbcLogger.java:145) 
DEBUG 10-17 16:29:15,965 ==> Parameters: 1(Integer)  (baseJdbcLogger.java:145) 
DEBUG 10-17 16:29:15,974 <==      Total: 1  (baseJdbcLogger.java:145) 
Employee{id=1, lastname='张三', email='[email protected]', gender='1', dept=null}
false

Process finished with exit code 

一级缓存失效情况2:同一个SqlSession但是查询条件不同

try {
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
	Employee emp1 = mapper.getEmpById(1);
    System.out.println(emp1);
    Employee emp2 = mapper.getEmpById(3);
  	System.out.println(emp2);
    System.out.println(emp1==emp2);

    sqlSession.commit();
} catch (Exception e) {
    e.printStackTrace();
}finally {
    sqlSession.close();
}

结果:

DEBUG 10-17 16:32:05,436 ==>  Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ?   (baseJdbcLogger.java:145) 
DEBUG 10-17 16:32:05,515 ==> Parameters: 1(Integer)  (baseJdbcLogger.java:145) 
DEBUG 10-17 16:32:05,567 <==      Total: 1  (baseJdbcLogger.java:145) 
Employee{id=1, lastname='张三', email='[email protected]', gender='1', dept=null}
DEBUG 10-17 16:32:05,568 ==>  Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ?   (baseJdbcLogger.java:145) 
DEBUG 10-17 16:32:05,569 ==> Parameters: 3(Integer)  (baseJdbcLogger.java:145) 
DEBUG 10-17 16:32:05,571 <==      Total: 1  (baseJdbcLogger.java:145) 
Employee{id=3, lastname='jerry', email='[email protected]', gender='0', dept=null}
false

一级缓存失效情况3:sqlSession相同,两次查询之间执行了增删改 *** 作

接口中添加方法

void addEmp(Employee emp);

    insert into tbl_employee(last_name,email,gender,d_id)
    values(#{lastname},#{email},#{gender},#{dept.id})

测试代码

try {
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
	Employee emp1 = mapper.getEmpById(1);
    System.out.println(emp1);
    mapper.addEmp(new Employee(null,"维曦","[email protected]","1",new Dept(1004,"客服部")));
    Employee emp2 = mapper.getEmpById(1);
  	System.out.println(emp2);
    System.out.println(emp1==emp2);
    sqlSession.commit();
} catch (Exception e) {
    e.printStackTrace();
}finally {
    sqlSession.close();
}

结果:

DEBUG 10-17 16:44:51,694 ==>  Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ?   (baseJdbcLogger.java:145) 
DEBUG 10-17 16:44:51,744 ==> Parameters: 1(Integer)  (baseJdbcLogger.java:145) 
DEBUG 10-17 16:44:51,779 <==      Total: 1  (baseJdbcLogger.java:145) 
Employee{id=1, lastname='张三', email='[email protected]', gender='1', dept=null}
DEBUG 10-17 16:44:51,781 ==>  Preparing: insert into tbl_employee(last_name,email,gender,d_id) values(?,?,?,?)   (baseJdbcLogger.java:145) 
DEBUG 10-17 16:44:51,783 ==> Parameters: 维曦(String), [email protected](String), 1(String), 1004(Integer)  (baseJdbcLogger.java:145) 
DEBUG 10-17 16:44:51,786 <==    Updates: 1  (baseJdbcLogger.java:145) 
DEBUG 10-17 16:44:51,786 ==>  Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ?   (baseJdbcLogger.java:145) 
DEBUG 10-17 16:44:51,787 ==> Parameters: 1(Integer)  (baseJdbcLogger.java:145) 
DEBUG 10-17 16:44:51,789 <==      Total: 1  (baseJdbcLogger.java:145) 
Employee{id=1, lastname='张三', email='[email protected]', gender='1', dept=null}
false

Process finished with exit code 0

一级缓存失效情况4:sqlSession相同,手动清除了一级缓存

try {
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
	Employee emp1 = mapper.getEmpById(1);
    System.out.println(emp1);
    sqlSession.clearCache();
    Employee emp2 = mapper.getEmpById(1);
  	System.out.println(emp2);
    System.out.println(emp1==emp2);
    sqlSession.commit();
} catch (Exception e) {
    e.printStackTrace();
}finally {
    sqlSession.close();
}

结果:

DEBUG 10-17 16:47:24,448 ==>  Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ?   (baseJdbcLogger.java:145) 
DEBUG 10-17 16:47:24,498 ==> Parameters: 1(Integer)  (baseJdbcLogger.java:145) 
DEBUG 10-17 16:47:24,544 <==      Total: 1  (baseJdbcLogger.java:145) 
Employee{id=1, lastname='张三', email='[email protected]', gender='1', dept=null}
DEBUG 10-17 16:47:24,545 ==>  Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ?   (baseJdbcLogger.java:145) 
DEBUG 10-17 16:47:24,546 ==> Parameters: 1(Integer)  (baseJdbcLogger.java:145) 
DEBUG 10-17 16:47:24,547 <==      Total: 1  (baseJdbcLogger.java:145) 
Employee{id=1, lastname='张三', email='[email protected]', gender='1', dept=null}
false

Process finished with exit code 0
二级缓存
  • 二级缓存(second level cache),全局作用域缓存,基于namespace级别的缓存:一个namespace对应一个二级缓存
  • 二级缓存默认不开启,需要手动配置
  • MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable接口
  • 二级缓存在 SqlSession 关闭或提交之后才会生效
  • 工作机制:
    1、一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    2、如果会话关闭;一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中的内容;
    3、sqlSession===EmployeeMapper==>Employee
    4、不同namespace查出的数据会放在自己对应的缓存中(map)
    5、效果:数据会从二级缓存中获取。一般查出的数据都会被默认先放在一级缓存中。只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中
  • 使用步骤
    1、全局配置文件中开启二级缓存
    2、需要使用二级缓存的映射文件处使用cache配置缓存
    3、注意:POJO需要实现Serializable接口
  • 缓存相关属性
  • eviction缓存回收策略:
    • LRU – 最近最少使用的:移除最长时间不被使用的对象。
    • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
    • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
    • 默认的是 LRU。
  • flushInterval刷新间隔,单位毫秒,缓存多长时间清空一次,默认不清空缓存仅仅调用语句时刷新
  • size引用数目,正整数
    • 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
  • readOnly只读,true/false
    • true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。不安全,速度快
    • false:读写缓存;会返回缓存对象的拷贝(通过序列化)。安全,速度慢,因此默认是 false。
  • type指定自定义缓存的全类名;实现Cache接口即可;
  • 缓存有关设置

• 1、全局setting的cacheEnable:配置二级缓存的开关。一级缓存一直是打开的。
• 2、select标签的useCache属性: 配置这个select是否使用二级缓存。一级缓存一直是使用的
• 3、sql标签的flushCache属性:增删改默认flushCache=true。sql执行以后,会同时清空一级和二级缓存。查询默认flushCache=false。
• 4、sqlSession.clearCache(): 只是用来清除一级缓存。
• 5、localCacheScope:本地缓存作用域:(一级缓存SESSION);当前会话的所有数据保存在会话缓存中;STATEMENT:可以禁用一级缓存;
• 6、当在某一个作用域 (一级缓存Session/二级缓存Namespaces) 进行了 C/U/D *** 作后,默认该作用域下所有 select 中的缓存将被clear。

实 *** 示例

全局变量设置开启缓存


在映射文件中添加设置二级缓存
(这里演示,部分设置属性,其他的采用默认的)



实体类实现可序列化接口

public class Employee implements Serializable {

    private static final long serialVersionUID = 93066158432748273l;

测试代码

try {
   EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
   
   SqlSession sqlSession2 = sqlSessionFactory.openSession();
   EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class);
   System.out.println(mapper.getEmpById(1));
   sqlSession.close();
   System.out.println(mapper2.getEmpById(1));
   sqlSession2.close();

} catch (Exception e) {
    e.printStackTrace();
}finally {
    sqlSession.close();
}

运行结果:

DEBUG 10-17 18:24:45,121 Cache Hit Ratio [com.xiaoqiu.dao.EmployeeMapper]: 0.0  (LoggingCache.java:62) 
DEBUG 10-17 18:24:45,131 ==>  Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ?   (baseJdbcLogger.java:145) 
DEBUG 10-17 18:24:45,176 ==> Parameters: 1(Integer)  (baseJdbcLogger.java:145) 
DEBUG 10-17 18:24:45,215 <==      Total: 1  (baseJdbcLogger.java:145) 
Employee{id=1, lastname='张三', email='[email protected]', gender='1', dept=null}
DEBUG 10-17 18:24:45,232 Cache Hit Ratio [com.xiaoqiu.dao.EmployeeMapper]: 0.5  (LoggingCache.java:62) 
Employee{id=1, lastname='张三', email='[email protected]', gender='1', dept=null}

Process finished with exit code 0
缓存机制图

第三方缓存整合
  • EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
  • MyBatis定义了Cache接口方便我们进行自定义扩展。
  • 步骤:
    1、导入ehcache包,以及整合包,日志包
    ehcache-core-2.6.8.jar
    mybatis-ehcache-1.0.3.jar
    slf4j-api-1.6.1.jar
    slf4j-log4j12-1.6.2.jar
    2、编写ehcache.xml配置文件


    
    

    
    

  • 属性说明:
    diskStore:指定数据在磁盘中的存储位置。
    defaultCache:当借助CacheManager.add(“demoCache”)创建Cache时,EhCache便会采用指定的的管理策略

  • 以下属性是必须的:
    maxElementsInMemory:在内存中缓存的element的最大数目
    maxElementsOnDisk: 在磁盘上缓存的element的最大数目,若是0表示无穷大
    eternal: 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
    overflowToDisk: 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘

  • 以下属性是可选的:
    timeToIdleSeconds : 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
    timeToLiveSeconds: 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
    diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
    diskPersistent : 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
    diskExpiryThreadIntervalSeconds : 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
    memoryStoreEvictionPolicy :当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)

3、配置cache标签


参照缓存:若想在命名空间中共享相同的缓存配置和实例。可以使用 cache-ref 元素来引用另外一个缓存。


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/3978421.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-10-21
下一篇 2022-10-21

发表评论

登录后才能评论

评论列表(0条)

保存