MyBatis 入门到精通(六)

上一篇博客的传送门:MyBatis 入门到精通(五)

Mybatis 是一个对数据库进行操作的 ORM 框架,主要是封装提供灵活的增删改查sql,开发中,service层能够通过mybatis组件查询和修改数据库中表的数据;作为查询工具,mybatis有使用缓存,我们今天来讲讲 MyBatis 的缓存操作。

缓存

在计算机里面,任何信息都有源头,缓存一般指源头信息读取后,放在内存或者其他读取较快的地方,下次读取相同信息不去源头查询而是直接从内存(或者能快速存取的硬件)读取。这样可以减少硬件使用,提高读取速度。

CPU 的缓存

在计算机系统中,CPU高速缓存(英语:CPU Cache,在本文中简称缓存)是用于减少处理器访问内存所需平均时间的部件。在金字塔式存储体系中它位于自顶向下的第二层,仅次于CPU寄存器。其容量远小于内存,但速度却可以接近处理器的频率。

当处理器发出内存访问请求时,会先查看缓存内是否有请求数据。如果存在(命中),则不经访问内存直接返回该数据;如果不存在(失效),则要先把内存中的相应数据载入缓存,再将其返回处理器。

缓存之所以有效,主要是因为程序运行时对内存的访问呈现局部性(Locality)特征。这种局部性既包括空间局部性(Spatial Locality),也包括时间局部性(Temporal Locality)。有效利用这种局部性,缓存可以达到极高的命中率。

在处理器看来,缓存是一个透明部件。因此,程序员通常无法直接干预对缓存的操作。但是,确实可以根据缓存的特点对程序代码实施特定优化,从而更好地利用缓存。

对比一下 CPU 的缓存,这样有助于我们学习 MyBatis 的缓存。

MyBatis 的缓存

mybatis 也是这样,查询数据库的数据之后,mybatis 可以把查询结果缓存到内存,下次查询如果查询语句相同,并且查询相关的表的数据没被修改过,就可以直接返回缓存中的结果,而不用去查询数据库的语句,有效节省了时间。

MyBatis 的缓存分类

  • 一级缓存
  • 二级缓存

一级缓存

基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空。一级缓存默认是长开启的。

效果演示

一级缓存是系统自带的缓存,不需要任何额外的操作。同一个 session ,多次对 7788 数据进行查询,第二次就不需要连接数据库。

代码部分:

SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(in);
SqlSession session = ssf.openSession();
EmpMapper mapper = session.getMapper(EmpMapper.class);
Emp emp1 = mapper.findEmpByNo(7788);
Emp emp2 = mapper.findEmpByNo(7788);
System.out.println(emp2);
System.out.println(emp1);

输出的日志: 

Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1040be71]
==>  Preparing: select * from emp where empState=1 and empNo=?
==> Parameters: 7788(Integer)
<==    Columns: empno, ename, job, mgr, hiredate, sal, comm, deptno, empstate
<==        Row: 7788, SCOTT, ANALYST, 7566, 1987-07-13, 3000.00, null, 20, 1
<==      Total: 1
Emp(empNo=7788, ename=关为, job=程序猿, mgr=7566, hireDate=1987-07-13, sal=3000.0, comm=null, deptNo=20, empState=1, dept=null, mgrEmp=null)
Emp(empNo=7788, ename=关为, job=程序猿, mgr=7566, hireDate=1987-07-13, sal=3000.0, comm=null, deptNo=20, empState=1, dept=null, mgrEmp=null)

生命周期

MyBatis 在开启一个数据库会话时,会创建一个新的 SqlSession 对象,SqlSession 对象中会有一个新的 Executor 对象。Executor 对象中持有一个新的 PerpetualCache 对象。当会话结束时,SqlSession 对象及其内部的 Executor 对象还有 PerpetualCache 对象也一并释放掉。当发生下列情况时一级缓存的内容会失效:

  1. 如果 SqlSession 调用了 close()方法,会释放掉一级缓存 PerpetualCache 对象,一级缓存将不可用。
  2. 如果 SqlSession 调用了 clearCache(),会清空 PerpetualCache 对象中的数据,但是该对象仍可使用。
  3. SqlSession 中执行了任何一个 update 操作(update()、delete()、insert()) ,都会清空 PerpetualCache 对象的数据,但是该对象可以继续使用。

怎么判断某两次查询是完全相同的查询 

mybatis 认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询。

  1. 传入的 statementId
  2. 查询时要求的结果集中的结果范围
  3. 这次查询所产生的最终要传递给 JDBC java.sql.Preparedstatement 的 Sql语句字符串(boundSql.getSql() )
  4. 传递给 java.sql.Statement 要设置的参数值

二级缓存 

二 级 缓 存 与 一 级 缓 存 其 机 制 相 同 , 默 认 也 是 采 用 PerpetualCache , HashMap 存 储 , 不 同 在 于 其 存 储 作 用 域 为Mapper,也被称之为 namespace 级别缓存,并且可自定义存储源,如 Ehcache。

为什么需要二级缓存

之所以称之为“二级缓存”,是相对于“一级缓存”而言的。既然有了一级缓存,那么为什么要提供二级缓存呢?

我们知道,在一级缓存中,不同 session 进行相同 SQL 查询的时候,是查询两次数据库的。显然这是一种浪费,既然 SQL 查询相同,就没有必要再次查库了,直接利用缓存数据即可,这种思想就是 MyBatis 二级缓存的初衷。

另外,Spring 和 MyBatis 整合时,每次查询之后都要进行关闭 sqlsession ,关闭之后数据被清空。所以 MyBatis 和 Spring 整合之后,一级缓存是没有意义的。如果开启二级缓存,关闭 sqlsession 后,会把该 sqlsession 一级缓存中的数据添加到 mapper namespace 的二级缓存中。

这样,缓存在sqlsession 关闭之后依然存在。

开启二级缓存

Namespace 层面上的二级缓存默认是不开启的,二级缓存的开启需要进行配置。开启的步骤很简单,按照以下三部完成即可:

1. bean 包中的类需要序列化。

实现二级缓存的时候,MyBatis 要求返回的 POJO 必须是可序列化的。

package com.dailyblue.java.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp implements Serializable {
    ......
}

2. 在 config.xml 中设置二级缓存开启。

<settings>
    <setting name="cacheEnabled" value="true" />
</settings>

3. 对具体的 Mapper 单独允许二级缓存的使用。

要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

<cache/>

这个简单语句的效果如下:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。 

注意:多个 session 使用一个缓存数据时,必须等待第一个 session 释放资源,其后 session 才能使用缓存数据。 

cache 标签的相关属性

我们在刚才的案例中只是引入了缓存,我们还可以对缓存进行更加详细的配置:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。 

可用的 eviction 清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

其他相关属性:

  • flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
  • size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
  • readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

注意:二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert|delete|update 语句时,缓存会获得更新。

在下一篇博客中,我们讲解 MyBatis 注解的使用。---- MyBatis 入门到精通(七)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值