简介

什么是缓存

存在内存中的临时数据。

将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,转从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

为什么使用缓存

减少和数据库的交互次数,减少系统开销,提高系统效率。

什么样的数据适合使用缓存

经常查询并且不经常改变的数据。

Mybatis缓存

MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。

MyBatis 系统中默认定义了两级缓存:一级缓存二级缓存

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

二级缓存需要手动开启和配置,他是基于 namespace 级别的缓存。

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

一级缓存

一级缓存也叫本地缓存。

与数据库同一次会话期间查询到的数据会放在本地缓存中。

以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;

测试

先在mybatis中加入日志,方便测试结果。

@Test
public void oneCache() {
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user1 = userMapper.selectOne(1);
System.out.println(user1);
User user2 = userMapper.selectOne(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession.close();
}

打印结果

Opening JDBC Connection
Created connection 1806431167.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6babf3bf]
==> Preparing: select id, name, pwd from user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 狂神, 123456
<== Total: 1
User(id=1, name=狂神, pwd=123456)
User(id=1, name=狂神, pwd=123456)
true

只进行了一次数据库查询,第二次查询没走数据库,直接取的缓存数据,所以两个对象相等。

缓存失效的四种情况

一级缓存是 SqlSession 级别的缓存,是一直开启的,我们关闭不了它。

要想让一级缓存失效,只能是不使用当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求。

sqlSession 不同

编写代码

@Test
public void oneCache() {
SqlSession sqlSession1 = MybatisUtils.getSession();
SqlSession sqlSession2 = MybatisUtils.getSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user1 = userMapper1.selectOne(1);
System.out.println(user1);
User user2 = userMapper2.selectOne(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession1.close();
sqlSession2.close();
}

打印结果

Opening JDBC Connection
Created connection 1806431167.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6babf3bf]
==> Preparing: select id, name, pwd from user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 狂神, 123456
<== Total: 1
User(id=1, name=狂神, pwd=123456)
Opening JDBC Connection
Created connection 1128948651.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@434a63ab]
==> Preparing: select id, name, pwd from user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 狂神, 123456
<== Total: 1
User(id=1, name=狂神, pwd=123456)
false

进行了两次查询,所以这里没有使用到缓存。

每个sqlSession中的缓存相互独立。

sqlSession 相同,查询条件不同

@Test
public void oneCache() {
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user1 = userMapper.selectOne(1);
System.out.println(user1);
User user2 = userMapper.selectOne(2);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession.close();
}

打印结果

Opening JDBC Connection
Created connection 1806431167.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6babf3bf]
==> Preparing: select id, name, pwd from user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 狂神, 123456
<== Total: 1
User(id=1, name=狂神, pwd=123456)
==> Preparing: select id, name, pwd from user where id = ?
==> Parameters: 2(Integer)
<== Columns: id, name, pwd
<== Row: 2, 张三, abcdef
<== Total: 1
User(id=2, name=张三, pwd=abcdef)
false

发现进行了两次查询。

很容易理解,因为当前缓存中,不存在这个数据。

sqlSession 相同,两次查询之间执行了增删改操作

编写代码

@Test
public void oneCache() {
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user1 = userMapper.selectOne(1);
System.out.println(user1); userMapper.delete(14); User user2 = userMapper.selectOne(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession.close();
}

打印结果:在中间执行了删除操作后,第二次查询也执行了。

结论:因为增删改操作可能会对当前数据产生影响,为严谨考虑,会再执行一次查询操作。

sqlSession 相同,手动清除一级缓存

编写代码

@Test
public void oneCache() {
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user1 = userMapper.selectOne(1);
System.out.println(user1); sqlSession.clearCache(); User user2 = userMapper.selectOne(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession.close();
}

打印结果:执行了两次查询,缓存失效。

结论:一级缓存就是一个 map,清除了之后就没有了,自然会再进行一次查询。

二级缓存

二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存。

基于 namespace 级别的缓存,一个名称空间,对应一个二级缓存。

工作机制

一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中。

如果当前会话关闭了,这个会话对应的一级缓存就没了。

实现效果

我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中。

新的会话查询信息,就可以从二级缓存中获取内容。

不同的 mapper 查出的数据会放在自己对应的缓存( map )中。

使用步骤

开启全局缓存,增加 mybatis-config.xml 中如下代码。

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

去每个 mapper.xml 中配置使用二级缓存,这个配置非常简单。

<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 语句时,缓存会获得更新。

代码测试

所有的实体类先实现序列化接口。

编写代码

@Test
public void TwoCache(){
SqlSession sqlSession1 = MybatisUtils.getSession();
SqlSession sqlSession2 = MybatisUtils.getSession(); UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class); User user1 = userMapper1.selectOne(1);
System.out.println(user1);
sqlSession1.close(); User user2 = userMapper2.selectOne(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession2.close();
}

打印结果

Opening JDBC Connection
Created connection 1048855692.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3e84448c]
==> Preparing: select id, name, pwd from user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 狂神, 123456
<== Total: 1
User(id=1, name=狂神, pwd=123456)
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3e84448c]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3e84448c]
Returned connection 1048855692 to pool.
As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66
Cache Hit Ratio [cn.sail.mapper.UserMapper]: 0.5
User(id=1, name=狂神, pwd=123456)
false

虽然前一个连接被关闭了,但由于开启了二级缓存,在查询同一个 Mapper 的情况下,第二次建立的连接也使用了前一次连接缓存中的数据,但此时对象不一样了。

结论

只要开启了二级缓存,我们在同一个 Mapper 中的查询,可以在二级缓存中拿到数据。

查出的数据都会被默认先放在一级缓存中。

只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中。

缓存原理

EhCache

第三方缓存实现:EhCache。

Ehcache 是一种广泛使用的 java 分布式缓存,用于通用缓存。

要引入依赖的jar包。

<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>

在mapper.xml中使用对应的缓存即可

<mapper namespace="org.acme.FooMapper">
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
</mapper>

编写ehcache.xml文件,如果在加载时未找到/ehcache.xml资源或出现问题,则将使用默认配置。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false"> <diskStore path="./tmpdir/Tmp_EhCache"/> <defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/> <cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/> </ehcache>

diskStore

缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:

  • user.home:用户主目录。
  • user.dir:用户当前工作目录。
  • java.io.tmpdir:默认临时文件路径。

defaultCache

默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。

name

缓存名称。

maxElementsInMemory

缓存最大数目

maxElementsOnDisk

硬盘最大缓存个数。

eternal

对象是否永久有效,一但设置了,timeout 将不起作用。

overflowToDisk

当系统宕机时,是否保存到磁盘。

timeToIdleSeconds

设置对象在失效前的允许闲置时间(单位:秒)。

仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。

timeToLiveSeconds

设置对象在失效前允许存活时间(单位:秒)。

最大时间介于创建时间和失效时间之间。

仅当eternal=false对象不是永久有效时使用,默认是0,也就是对象存活时间无穷大。

diskPersistent

是否缓存虚拟机重启期数据,默认值为 false。

diskSpoolBufferSizeMB

这个参数设置 DiskStore(磁盘缓存)的缓存区大小。默认是30MB。

每个Cache都应该有自己的一个缓冲区。

diskExpiryThreadIntervalSeconds

磁盘失效线程运行时间间隔,默认是120秒。

memoryStoreEvictionPolicy

当达到 maxElementsInMemory 限制时,Ehcache 将会根据指定的策略去清理内存。

默认策略是LRU(最近最少使用),可以设置为FIFO(先进先出)或是LFU(较少使用)。

FIFO,first in first out,这个是大家最熟的,先进先出。

LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。

LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。

clearOnFlush

内存数量最大时是否清除。

最新文章

  1. java compiler level does not match the version of the installed java project facet
  2. 初识CEF
  3. Scrum7.0
  4. iOS开发XCODE5 SVN配置 使用办法 (转) 收藏一下
  5. 百度ECHARTS 饼图使用心得 处理data属性
  6. python中并行遍历:zip和map-转
  7. OC基础 类的三大特性
  8. 588. [NOIP1999] 拦截导弹
  9. HTML一些小细节
  10. hibernate jar包介绍
  11. 关于linux下的文件权限
  12. opencv 学习入门篇
  13. C# -- 使用Ping检查网络是否正常
  14. Quartz 2.2 动态添加、修改和删除定时任务
  15. win10 家庭版 升级 win10企业版
  16. VP9 vs H.265——下一代视频编码标准的王道之争
  17. 46.Linux-创建rc红外遥控平台设备,实现重复功能(2)
  18. 线程安全-002-多个线程多把锁&amp;类锁
  19. Json:前台对象数组传到后台解析
  20. 【译】准备好你求职时候用的 GitHub 账号

热门文章

  1. CoaXPress 是如何只用一条线缆实现双向传输和供电的
  2. 从零开始学YC-Framework之鉴权
  3. Linux Cgroup v1(中文翻译)(1):Control Group
  4. javascript写无缝平移的轮播图
  5. SAP Picture Control(图片加载)
  6. UiPath直播课程
  7. linux函数与数组
  8. Linux文件的删除和软硬链接
  9. rhel6下eth1恢复eth0
  10. jQuery获取市、区县、乡镇、村