SpringBoot+EHcache实现缓存
撰文背景
公司开发中的一个驱动模块,需要用到本地缓存,来提高驱动模块的访问速度和性能,然后就想到了Ehcache缓存,Ehcache是Hibernate 中默认的CacheProvider,hibernate就是使用Ehcache来实现二级缓存的。本质上来说Ehcache是一个缓存管理器,不仅仅可以和Hibernate配合实现缓存,也可以和其他框架比如spring boot 结合,作为一个缓存管理器,缓存管理器有很多,但是公司的项目说实话只是用到了Ehcache的本地缓存,Ehcache还支持分布式的缓存,虽然公司的项目没有用到,但是本着深究到底的想法,还是想深入的把Ehcache详细的学习下,下面就是我学习中的一些总结,算是做个记录。
常见的缓存管理器:
* Generic
* JCache (JSR-107)
* EhCache 2.x
* Hazelcast
* Infinispan
* Redis
* Guava
* Simple
spring boot本身是提供数据缓存的功能,SpringBoot自带的cache技术我想大家都应该用过,为了解决数据库输入输出的瓶颈所以一般情况下我们都会引入非常多的缓存策略,例如引入redis缓存,引入Hibernate的二级缓存等等。
SpringBoot在annotation的层面给我们实现了cache,当然这也是得益于Spring的AOP。所有的缓存配置只是在annotation层面配置,完全没有侵入到我们的代码当中,就像我们的声明式事务一样。
Spring定义了CacheManager和Cache接口统一不同的缓存技术。其中CacheManager是Spring提供的各种缓存技术的抽象接口。而Cache接口包含缓存的各种操作,当然我们一般情况下不会直接操作Cache接口。
Spring针对不同的缓存技术,需要实现不同的cacheManager,Spring定义了如下的cacheManger实现
CacheManger | 描述 |
SimpleCacheManager | 使用简单的Collection来存储缓存,主要用于测试 |
ConcurrentMapCacheManager | 使用ConcurrentMap作为缓存技术(默认) |
NoOpCacheManager | 测试用 |
EhCacheCacheManager | 使用Ehcache作为缓存技术,以前在HIbernate的时候经常用 |
GuavaCacheManager | 使用google guava的GuavaCache作为缓存技术 |
HazelcastCacheManager | 使用Hazelcast作为缓存技术 |
JCacheCacheManager | 使用JCache标准的实现作为缓存技术,如Apache Commons JCS |
RedisCacheManager | 使用Redis作为缓存技术 |
常规的SpringBoot已经为我们自动配置了EhCache、Collection、Guava、ConcurrentMap等缓存,默认使用SimpleCacheConfiguration,即使用ConcurrentMapCacheManager。
EHcache官网: 点击打开链接
一、EHcache简介
Ehcache是一个开源的基于标准的缓存,是一个纯java的在进程中的缓存,可提高性能,卸载数据库并简化可伸缩性。它是使用最广泛的基于Java的高速缓存,因为它非常强大,经过验证,功能全面,并且与其他流行的库和框架集成在一起。Ehcache从进程内缓存扩展到混合进程内/进程外部署与TB级缓存。
EHCache是一个快速的、轻量级的、易于使用的、进程内的缓存。它支持read-only 和 read/write 缓存,内存和磁盘缓存。是一个非常轻量级的缓存实现,而且从 1.2 之后就支持了集群。
现在的Ehcache已经更新到了3.5版本,版本3加入一些新的功能,包括
- 改进了API,可以利用Java泛型并简化缓存交互,
- 与javax.cache API(JSR-107)完全兼容,
- Offheap存储功能,包括仅堆高速缓存,
- Spring Caching和Hibernate集成得益于javax.cache支持,
- 还有很多 ...
与Ehcache 2.x相比,Ehcache 3具有简化,现代化的类型安全API(和配置),可以大大提高您的编码体验。
特点:
- 快速.
- 简单.
- 多种缓存策略
- 缓存数据有两级:内存和磁盘,因此无需担心容量问题
- 缓存数据会在虚拟机重启的过程中写入磁盘
- 可以通过 RMI、可插入 API 等方式进行分布式缓存
- 具有缓存和缓存管理器的侦听接口
- 支持多缓存管理器实例,以及一个实例的多个缓存区域
- 提供 Hibernate 的缓存实现
存储方式:
- 内存
- 磁盘
对应的jar包下载地址:点击打开链接
又或者可以采用添加pom依赖的方式来添加依赖包:
<dependency><groupId>org.ehcache</groupId><artifactId>ehcache</artifactId><version>3.5.2</version></dependency>
二、EHCache页面缓存的配置
1.EHCache 的类层次模型
主要为三层,最上层的是 CacheManager,他是操作Ehcache 的入口。我们可以通过CacheManager.getInstance()获得一个单子的 CacheManger,或者通过CacheManger 的构造函数创建 一个新的CacheManger。每个 CacheManager 都管理着多个 Cache。而每个Cache 都以一种类 Hash 的方式,关联着多个Element。Element 则是我们用于存放要缓存内容的地方
2.ehcache配置文件中元素说明
ehcach.xml配置文件主要参数的解释,其实文件里有详细的英文注释//DiskStore 配置,
cache 文件的存放目录,主要的值有
* user.home - 用户主目录
* user.dir - 用户当前的工作目录
* java.io.tmpdir - Default temp file path 默认的 temp 文件目录
以下有个范例:
<?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 = "Java.io.tmpdir"/> <defaultCacheeternal="false" <--意味着该缓存会死亡-->maxElementsInMemory="900"<--缓存的最大数目-->overflowToDisk="false" <--内存不足时,是否启用磁盘缓存,如果为true则表示启动磁盘来存储,如果为false则表示不启动磁盘-->diskPersistent="false" timeToIdleSeconds="0" <--当缓存的内容闲置多少时间销毁-->timeToLiveSeconds="60" <--当缓存存活多少时间销毁(单位是秒,如果我们想设置2分钟的缓存存活时间,那么这个值我们需要设置120)-->memoryStoreEvictionPolicy="LRU" /> <--自动销毁策略--><!-- 这里的 users 缓存空间是为了下面的 demo 做准备 --><cachename="data"eternal="false"maxElementsInMemory="200"overflowToDisk="false"diskPersistent="false"timeToIdleSeconds="0"timeToLiveSeconds="60"memoryStoreEvictionPolicy="LRU" />
</ehcache>
<!--<diskStore>==========当内存缓存中对象数量超过maxElementsInMemory时,将缓存对象写到磁盘缓存中(需对象实现序列化接口) -->
<!--<diskStore path="">==用来配置磁盘缓存使用的物理路径,Ehcache磁盘缓存使用的文件后缀名是*.data和*.index -->
<!--name=================缓存名称,cache的唯一标识(ehcache会把这个cache放到HashMap里) -->
<!--maxElementsOnDisk====磁盘缓存中最多可以存放的元素数量,0表示无穷大 -->
<!--maxElementsInMemory==内存缓存中最多可以存放的元素数量,若放入Cache中的元素超过这个数值,则有以下两种情况 --><!--1)若overflowToDisk=true,则会将Cache中多出的元素放入磁盘文件中 --><!--2)若overflowToDisk=false,则根据memoryStoreEvictionPolicy策略替换Cache中原有的元素 -->
<!--eternal==============缓存中对象是否永久有效,即是否永驻内存,true时将忽略timeToIdleSeconds和timeToLiveSeconds -->
<!--timeToIdleSeconds====缓存数据在失效前的允许闲置时间(单位:秒),仅当eternal=false时使用,默认值是0表示可闲置时间无穷大,此为可选属性 --><!--即访问这个cache中元素的最大间隔时间,若超过这个时间没有访问此Cache中的某个元素,那么此元素将被从Cache中清除 -->
<!--timeToLiveSeconds====缓存数据在失效前的允许存活时间(单位:秒),仅当eternal=false时使用,默认值是0表示可存活时间无穷大 --><!--即Cache中的某元素从创建到清楚的生存时间,也就是说从创建开始计时,当超过这个时间时,此元素将从Cache中清除 -->
<!--overflowToDisk=======内存不足时,是否启用磁盘缓存(即内存中对象数量达到maxElementsInMemory时,Ehcache会将对象写到磁盘中) --><!--会根据标签中path值查找对应的属性值,写入磁盘的文件会放在path文件夹下,文件的名称是cache的名称,后缀名是data -->
<!--diskPersistent=======是否持久化磁盘缓存,当这个属性的值为true时,系统在初始化时会在磁盘中查找文件名为cache名称,后缀名为index的文件 --><!--这个文件中存放了已经持久化在磁盘中的cache的index,找到后会把cache加载到内存 --><!--要想把cache真正持久化到磁盘,写程序时注意执行net.sf.ehcache.Cache.put(Element element)后要调用flush()方法 -->
<!--diskExpiryThreadIntervalSeconds==磁盘缓存的清理线程运行间隔,默认是120秒 -->
<!--diskSpoolBufferSizeMB============设置DiskStore(磁盘缓存)的缓存区大小,默认是30MB -->
<!--memoryStoreEvictionPolicy========内存存储与释放策略,即达到maxElementsInMemory限制时,Ehcache会根据指定策略清理内存 --><!--共有三种策略,分别为LRU(Least Recently Used 最近最少使用)、LFU(Less Frequently Used最不常用的)、FIFO(first in first out先进先出) -->
Ehcache 的三种清空策略
1 FIFO,first in first out,这个是大家最熟的,先进先出。
2 LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来
最少被使用的。如上面所讲,缓存的元素有一个 hit 属性,hit 值最小的将会被清出缓存。
3 LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量
满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时
间最远的元素将被清出缓存。
接着我们来看一下SimplePageCachingFilter 的配置,
XML/HTML 代码
<filter>
<filter-name>indexCacheFilterfilter-name>Page9 of 26
<filter-class>
net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter
<filter-class>
<filter>
<filter-mapping>
<filter-name>indexCacheFilterfilter-name>
<url-pattern>*index.actionurl-pattern>
<filter-mapping>
就只需要这么多步骤,我们就可以给某个页面做一个缓存的,把上面这段配置放到你的web.xml 中,那么当你打开首页的时候,你会发现,2 分钟才会有一堆 sql 语句出现在控制台上。当然你也可以调成5 分钟,总之一切都在控制中。好了,缓存整个页面看上去是非常的简单,甚至都不需要写一行代码,只需要几行配置就行了,够简单吧,虽然看上去简单,但是事实上内部实现却不简单哦,有兴趣的话,大家可以看看SimplePageCachingFilter 继承体系的源代码。
上面的配置针对的情况是缓存首页的全部,如果你只想缓存首页的部分内容时,你需要使用SimplePageFragmentCachingFilter 这个 filter。我们看一下如下片断:
XML/HTML 代码
<filter>
<filter-name>indexCacheFilterfilter-name>
<filter-class>
net.sf.ehcache.constructs.web.filter.SimplePageFragmentCachingFilter
<filter-class>
filter>
<filter-mapping>
<filter-name>indexCacheFilterfilter-name>
<url-pattern>*/index_right.jspurl-pattern>
<filter-mapping>
这个 jsp 需要被 jsp:include 到其他页面,这样就做到的局部页面的缓存。这一点貌似没有 oscache 的 tag 好用。事实上在cachefilter 中还有一个特性,就是 gzip,也就是说缓存中的元素是被压缩过的,如果客户浏览器支持压缩的话,filter会直接返回压缩过的流,这样节省了带宽,把解压的工作交给了客户浏览器,如果客户的浏览器不支持 gzip,那么 filter 会把缓存的元素拿出来解压后再返回给客户浏览器(大多数爬虫是不支持 gzip 的,所以 filter 也会解压后再返回流),这样做的优点是节省带宽,缺点就是增加了客户浏览器的负担(但是我觉得对当代的计算机而言,这个负担微乎其微)。好了,如果你的页面正好也需要用到页面缓存,不防可以考虑一下 ehcache,因为它实在是非常简单,而且易用。
三、spring boot + mybatis + Ehcache 实现本地缓存
这个例子其实关于本地缓存我这写了一个例子,这个例子是一个springboot工程项目,集成了mybatis来进行数据库的访问,只是一个简单的数据库表操作,只是在具体的方法上添加了相应的注解,从而实现了,本地缓存。没有用到Ehcache集群和分布式,只是将信息缓存到内存中,从而降低数据库的之间的访问,提高数据的访问速度。
github地址:点击打开链接
核心的代码我来讲解下:
(1)EhcacheProviderApplication启动类
package com.huntkey.rx.ehcache.provider;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer;
/*** 链接Mysql数据库简单的集成Mybatis、ehcache框架采用MapperXml访问数据库。** 简单用户链接Mysql数据库微服务(通过 mybatis 链接 mysql 并用 MapperXml 编写数据访问,并且通过 EhCache 缓存来访问)。*/
@EnableDiscoveryClient
@SpringBootApplication
@EnableCaching
public class EhcacheProviderApplication {public static void main(String[] args) {SpringApplication.run(EhcacheProviderApplication.class,args);System.out.println("【【【【【【 链接MysqlMybatisMapperEhCache数据库微服务 】】】】】】已启动.");}
}
如果想用Ehcache缓存,在启动类上一定要加上 @EnableCaching注解,否则缓存会不起作用。
(2)UserServiceImpl实现类
package com.huntkey.rx.ehcache.provider.service.impl;
import com.github.pagehelper.util.StringUtil;
import com.huntkey.rx.ehcache.common.model.User;
import com.huntkey.rx.ehcache.common.util.Result;
import com.huntkey.rx.ehcache.provider.dao.UserDao;
import com.huntkey.rx.ehcache.provider.service.UserService;
import com.mysql.jdbc.StringUtils;
import com.sun.org.apache.regexp.internal.RE;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {private Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);private static final String CACHE_KEY = "'user'";private static final String CACHE_NAME_B = "cache-b";//* @Cacheable : Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。//* @CacheEvict : 清除缓存。//* @CachePut : @CachePut也可以声明一个方法支持缓存功能。使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
@Autowiredprivate UserDao userDao;@CacheEvict(value = CACHE_NAME_B, key = CACHE_KEY)@Overridepublic int insert(User user) {return userDao.insert(user);}@CacheEvict(value = CACHE_NAME_B, key = "'user_' + #id") //这是清除缓存@Overridepublic int deleteByPrimaryKey(String id) {Result result = new Result();return userDao.deleteByPrimaryKey(id);}@CacheEvict(value = CACHE_NAME_B, key = "'user_'+ #user.id")@Overridepublic User updateByPrimaryKey(User user) {userDao.updateByPrimaryKey(user);return user;}@Cacheable(value = CACHE_NAME_B, key = "'user_'+ #id")@Overridepublic User selectByPrimaryKey(String id) {return userDao.selectByPrimaryKey(id);}@Overridepublic List<User> selectAllUser() {return userDao.selectAllUser();}@Cacheable(value = CACHE_NAME_B, key = "#userId+'_'+#userName")@Overridepublic Result selectUserByAcount(Integer userId, String userName) {Result result = new Result();try {List<User> list = userDao.selectUserByAcount(userId, userName);if (list.size() == 0 || list.isEmpty()) {result.setRetCode(Result.RECODE_ERROR);result.setErrMsg("查找数据不存在");return result;}result.setData(list);} catch (Exception e) {result.setRetCode(Result.RECODE_ERROR);result.setErrMsg("方法执行出错");logger.error("方法执行出错", e);throw new RuntimeException(e);}return result;}
}
然后我们开始讲讲四个注解:
@Cacheable 在方法执行前Spring先是否有缓存数据,如果有直接返回。如果没有数据,调用方法并将方法返回值存放在缓存当中。
这个注解会先查询是否有缓存过的数据,如果有,就直接返回原来缓存好的数据,如果没有,则再执行一次方法,将方法的返回结果放到缓存中。
@CachePut 无论怎样,都将方法的返回结果放到缓存当中。
这个注解不会询问是否有缓存好的数据,而是每次都会执行方法,将方法的返回结果放到缓存中,相当于每次都更新缓存中的数据,每次缓存中的数据都是最新的一次缓存数据。
@CacheEvict 将一条或者多条数据从缓存中删除。
这个是删除一条或者多条的缓存数据。
@Caching 可以通过@Caching注解组合多个注解集合在一个方法上
这个注解可以组合多个注解,从而实现自定义注解
注意:缓存其实存放的是以注解里面的key为key 方法的返回值作为key的value,不是注解里面的value。
总结:
ehcache 是一个非常轻量级的缓存实现,而且从 1.2 之后就支持了集群,目前的最新版本是 1.3,而且是 hibernate 默认的缓存provider。虽然本文是介绍的是 ehcache 对页面缓存的支持,但是ehcache 的功能远不止如此,当然要使用好缓存,对 JEE 中缓存的原理,使用范围,适用场景等等都需要有比较深刻的理解,这样才能用好缓存,用对缓存。
SpringBoot+EHcache实现缓存相关推荐
- SpringBoot集成Cache缓存(Ehcache缓存框架,注解方式)
1.说明 Spring定义了CacheManager和Cache接口, 用来统一不同的缓存技术, 例如JCache,EhCache,Hazelcast,Guava,Redis等. 本文通过Spring ...
- Shiro框架学习笔记、整合Springboot、redis缓存
本笔记基于B站UP主不良人编程 目录 1.权限的管理 1.1什么是权限管理 1.2什么是身份认证 1.3什么是授权 2.什么是Shiro 3.Shiro的核心架构 3.1 S核心内容 4.shiro中 ...
- 七十六、SpringBoot 的数据缓存cache+Redis(三)
@Author:Runsen 来源:尚硅谷 下面建议读者学习尚硅谷的B站的SpringBoot视频,我是学雷丰阳视频入门的. 具体链接如下:B站尚硅谷SpringBoot教程 文章目录 Redis P ...
- 七十四、SpringBoot 的数据缓存cache(一)
@Author:Runsen 来源:尚硅谷 下面建议读者学习尚硅谷的B站的SpringBoot视频,我是学雷丰阳视频入门的. 具体链接如下:B站尚硅谷SpringBoot教程 文章目录 SpringB ...
- EhCache 分布式缓存/缓存集群
开发环境: System:Windows JavaEE Server:tomcat5.0.2.8.tomcat6 JavaSDK: jdk6+ IDE:eclipse.MyEclipse 6.6 开发 ...
- 细谈Ehcache页面缓存的使用
为什么80%的码农都做不了架构师?>>> 关于缓存的话题,在坛子里已经有很多讨论,简单的来说,如果一个应用中80% 的时间内都在访问20% 的数据,那么,这时候就应该使用缓存了 ...
- Ehcache分布式缓存及测试方法
接到配合架构部要求配合测试需求,对EhCache 进行测试,在此之前,未接触过ehcache缓存,之前知道一些缓存,但是还真没了解过内存缓存.于是百度,看书,查资料,先恶补一下ehcache的一些知识 ...
- SpringBoot集成Cache缓存(Redis缓存,RedisTemplate方式)
1.说明 SpringBoot集成Redis缓存, 首先创建一个Spring Boot工程, 使用Maven向导方式创建:SpringBoot集成Maven工程 然后引入redis的spring bo ...
- SpringBoot与Redis缓存
SpringBoot与Redis缓存: 准备 在Docker安装Redis 连接成功 对于Redis不熟悉的同学可以在本站搜索Redis的文章阅读. 整合Redis 在pom文件中加入 12345 & ...
最新文章
- OpenCV——膨胀与腐蚀
- SpringMVC启动分析
- 13 个mod_rewrite 应用举例
- read_copy update
- monty python喜剧-看美剧学托福:最受美国人喜欢的100部美剧
- 【C 语言】字符串模型 ( 字符串翻转模型 | 抽象成业务函数 | 形参返回值 | 函数返回值 | 函数形参处理 | 形参指针判空 )
- kaggle房价预测特征意思_R语言实战:复杂数据处理和分析之Kaggle房价预测
- VC中使用全局变量的2种办法及防错措施
- Watch out for these 10 common pitfalls of experienced Java developers architects--转
- MATLAB之基本语法与基础函数
- LLDP(链路层发现协议)
- SQL Server更改字段名
- wordpress 百度主动推送 PHP,WordPress 百度自动推送插件
- interface Ethernet 0/0/0 和interface GigabitEthernet 1/0/0
- 2010年8月初 泉州将地震 预言还是谣言?
- 大象装进冰箱要几步?Python 来解答
- 《计算机组成原理》期末习题讲解和重点复习内容
- 【基于人脸特征的心率检测研究】非接触式光电容积图和红外人脸视频瞬时心率估计
- 智能型炉温测试仪kic x5软件,KIC X5 炉温测试仪产品说明ppt课件
- 数模学习(模糊数学篇)——模糊识别(python实现)