(11)SpringBoot整合EhCache做缓存
发布日期:2021-06-30 11:08:05 浏览次数:2 分类:技术文章

本文共 10703 字,大约阅读时间需要 35 分钟。

640?wx_fmt=png

   摘要:本文介绍在SpringBoot项目中,如何使用EhCache做缓存。

EhCache 简介:EhCache 是一个纯Java的进程内缓存框架,是Hibernate中默认的CacheProvider;其缓存的数据可以存放在内存里面,也可以存放在硬盘上;其核心是CacheManager,一切Ehcache的应用都是从CacheManager开始的;它是用来管理Cache(缓存)的,一个应用可以有多个CacheManager,而一个CacheManager下又可以有多个Cache;Cache内部保存的是一个个的Element,而一个Element中保存的是一个key和value的配对,相当于Map里面的一个Entry。

它具有如下特点:

  1. 快速

  2. 简单

  3. 多种缓存策略

  4. 缓存数据有两级:内存和磁盘,因此无需担心容量问题

  5. 缓存数据会在虚拟机重启的过程中写入磁盘

  6. 可以通过RMI、可插入API等方式进行分布式缓存

  7. 具有缓存和缓存管理器的侦听接口

  8. 支持多缓存管理器实例,以及一个实例的多个缓存区域

  9. 提供Hibernate的缓存实现

注意:本文案例使用的接口层,持久层等,都依赖于,并在其基础上添加了部分方法,完整源码请点击阅读原文,github同步更新代码。

在SpringBoot项目中整合EhCache做缓存,具体步骤如下:

1.pom.xml

我们在pom.xml中引入相关依赖:

 
     
org.springframework.boot
     
spring-boot-starter-cache
 
 
 
     
net.sf.ehcache
     
ehcache
 
 

2.启动类添加注解:@EnableCaching

启动类加此注解后,会自动开启SpringBoot对缓存的支持

@EnableCaching

3.配置文件ehcache.xml

在resources包下新建一个ehcache.xml配置文件,用于配置ehcache缓存的参数,我这里习惯在resources下再建一个resource文件夹,专门存放配置文件,所以我这里的结构是:main\resources\resource\ehcache.xml

 
     
 
 

各个参数的涵义如下:

  • name:缓存名称。

  • maxElementsInMemory:缓存最大个数。

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

  • timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。

  • timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。

  • overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。

  • diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。

  • maxElementsOnDisk:硬盘最大缓存个数。

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

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

  • memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用),有如下3种清空策略:

  • - FIFO ,first in first out (先进先出)。

  • - LFU , Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存。

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

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

4.application.properties

由于SpringBoot已经为我们内置了redis,jcache等多种缓存,所以我们需要在application.properties文件中配置选择一下;且本示例持久层为jpa,所以我们顺便控制台输出一下sql,方便后面观察。

#展示sql spring.jpa.show-sql=true #ehcache spring.cache.type=ehcache #配置文件位置 spring.cache.ehcache.config=resource/ehcache.xml

5.web层 UserController.java

由于此案例持久层使用的是之前写过的JpaRepository,web层是直接调用的持久层的,这里为了直观一些,我把web层做了简单的修改,这里再贴一下代码,后面会直接贴访问路径,访问的就是这个类的接口,方法均见名知意。

package com.java4all.controller; import com.java4all.dao.UserRepository; import com.java4all.entity.User; import com.java4all.service.UserService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.List; import java.util.Map; /**  * Author: momo  * Date: 2018/3/26  * Description:  */ @RestController @RequestMapping("user") public class UserController {
   private final Logger logger = LoggerFactory.getLogger("");    @Autowired    private UserRepository userRepository;    @Autowired    private UserService userService;    /**     * 根据id查询     * @param id     * @return     */    @RequestMapping(value = "getById",method = RequestMethod.GET)    public User getList(Integer id){
       User user = userService.getById(id);        return user;    }    /**-------------jpa----------------------*/    /**     * 查询操作     * @return     */    @RequestMapping(value = "findById",method = RequestMethod.GET)    public User findById(Integer id){
       User user = userRepository.findById(id);        return user;    }    /**     * 删除操作     * @param id     */    @RequestMapping(value = "deleteById",method = RequestMethod.GET)    public void deleteById(Integer id){
       userRepository.deleteById(id);    }    /**     * 添加User     * @return     */    @RequestMapping(value = "addUser",method = RequestMethod.GET)    public String addUser(){
       User user = new User();        user.setUserName("momo345");        user.setCountry("中国啊");        userRepository.saveAndFlush(user);        return "操作成功";    }    /**     * 根据userName查询user     * @param userName     * @return     */    @RequestMapping(value = "findByUserName",method = RequestMethod.GET)    public User findByUserName(String userName){
       User user = userRepository.findUserByUserName(userName);        return user;    }    /**     * 分页查询     * @param pageSize 每页个数     * @param pageNum 页码     * @return     */    @RequestMapping(value = "findByPage",method = RequestMethod.GET)    public Map
findByPage(Integer pageSize,Integer pageNum){
       Map
map = new HashMap();        Page
userPage = userRepository.findAll(new Pageable() {
           @Override            public int getPageNumber() {
               return pageNum;            }            @Override            public int getPageSize() {
               return pageSize;            }            @Override            public long getOffset() {
               //logger.info("=======>:" + (pageNum - 1) * pageSize);                return (pageNum - 1) * pageSize;            }            @Override            public Sort getSort() {
               return new Sort("id");            }            @Override            public Pageable next() {
               return null;            }            @Override            public Pageable previousOrFirst() {
               return null;            }            @Override            public Pageable first() {
               return null;            }            @Override            public boolean hasPrevious() {
               return false;            }        });        List
list = userPage.getContent();        long count = userPage.getTotalElements();        int totalPages = userPage.getTotalPages();        map.put("list",list);        map.put("count",count);        map.put("totalPages",totalPages);        return map;    }    /**-------------jpa----------------------*/ }

6.持久层 UserRepository.java

我们在需要使用缓存的持久层,添加@CacheConfig(),然后在需要缓存的方法上添加对应的缓存注解。此次案例我们在以下方法做简单测试:

package com.java4all.dao; import com.java4all.entity.User; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.transaction.annotation.Transactional; /**  * Author: momo  * Date: 2018/3/27  * Description:  */ @Transactional @CacheConfig(cacheNames = "user") public interface UserRepository extends JpaRepository
{
   /**根据userName查询对象*/    @Cacheable(key = "#p0")    User findUserByUserName(String userName);    /**根据id查询对象*/    @Cacheable(key = "#p0")    User findById(Integer id);    /**根据id删除*/    @CacheEvict(key = "#p0")    void deleteById(Integer id);    /**保存或者更新  当有id时就是更新,否则是添加*/    @Override    @CachePut(key = "#p0.userName")    User saveAndFlush(User user);    /**分页查询*/    @Override    @Cacheable(key = "#p0.pageNumber")    Page
findAll(Pageable pageable); }

然后,我们逐个调用接口,进行分析。

6.1 @Cacheable()

在findUserByUserName,findById两个方法上,我们都添加了@Cacheable(key = "#p0");这里用了@Cacheable()的一个参数key,缓存对象存储时的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = "#p0"):意思是使用此函数第一个参数作为该缓存的key值,如果有第二个参数,我们也可以用key = "#p1";

我们这里调用这两个方法时,会根据这个key先去缓存找是否有数据,如果有,就直接返回了,如果没有,那就会去数据库查询后返回,并以此key为key,存入缓存中;当再次调用此方法时,若该缓存没有过期,那么当此方法的参数和这个key相同时,就会直接去找缓存;我们可以在控制台查看打印的sql来观察是否访问数据库。

分页方法上,我们添加了@Cacheable(key = "#p0.pageNumber"),此时执行http://localhost:8088/user/findByPage?pageSize=10&pageNum=3,会发现有sql执行,第二次时,没有sql执行;我们把pageNumber换为13,发现就有sql执行了,因为我们这里缓存时key只和pageNumber有关;如果pageNumber=3,把pageSize=50呢?会发现尽管参数变了,但是这次依旧走的缓存,没有查数据库,因为,我们这里设置的key和pageSize是无关的,pageNum找到了,就直接返回了。

6.2 @CacheEvict()

此注解通常用在删除方法上,用于从缓存中清除数据。

我们这里访问http://localhost:8088/user/findById?id=1599991,第一次访问时发现会有sql显示,第二次就没有sql了,因为缓存中已经有了这个id为key的数据了,直接走的缓存;

如果deleteById方法上没有@CacheEvict注解时,那我们执行http://localhost:8088/user/deleteById?id=1599991,会发现数据库此数据删除了,但是http://localhost:8088/user/findById?id=1599991时,发现还是查出来了,为什么呢?因为我们之前查询过,此数据被缓存了,而且没有过期,所以我们查询时走缓存还是可以拿出来的。

如果我们在deleteById方法上加上@CacheEvict(key = "#p0"),那http://localhost:8088/user/deleteById?id=1599991执行后,数据库数据会被删除,再去http://localhost:8088/user/findById?id=1599991,会发现查询不出来了,因为我们删除执行后,把缓存中key为1599991的缓存数据删除了,所以缓存中再查询时查不到了。

6.3 @CachePut()

此注解通常用在新增和修改方法上,根据条件,进行缓存。

我们执行一下http://localhost:8088/user/addUser,比如被加到数据库的User.userName=momo456,在addUser方法上,@CachePut(key = "#p0.userName"),我们会在数据库添加完User后,在缓存中也添加一份数据,key为momo456;此时,我们去执行http://localhost:8088/user/findByUserName?userName=momo456,会发现,尽管我们是第一次查询,但是控制台并没有显示sql语句,说明此请求并没有向数据库发送请求,而是直接走的缓存。

7.Cache注解详解

  • @CacheConfig:主要用于配置该类中会用到的一些共用的缓存配置。在这里@CacheConfig(cacheNames = "user"):配置了该数据访问对象中返回的内容将存储于名为user的缓存对象中,我们也可以不使用该注解,直接通过@Cacheable自己配置缓存集的名字来定义。

  • @Cacheable:在查询时,会先从缓存中获取,若不存在才再发起对数据库的访问。阅读源码,发现该注解主要有下面几个参数:

  • - value、cacheNames:两个等同的参数(cacheNames为Spring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了。

  • - key:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = "#p0"):使用函数第一个参数作为缓存的key值,更多关于SpEL表达式的详细内容可参考官方文档。

  • - condition:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = "#p0", condition = "#p0.length() < 3"),表示只有当第一个参数的长度小于3的时候才会被缓存,读者可自行实验尝试。

  • - unless:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。

  • - keyGenerator:用于指定key生成器,非必需。若需要指定一个自定义的key生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator接口,并使用该参数来指定。需要注意的是:该参数与key是互斥的。

  • - cacheManager:用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用。

  • - cacheResolver:用于指定使用哪个缓存解析器,非必需。需通过org.springframework.cache.interceptor.CacheResolver接口来实现自己的缓存解析器,并用该参数指定。

  • @CachePut:配置于函数上,能够根据参数定义条件来进行缓存,它与@Cacheable不同的是,它每次都会真是调用函数,所以主要用于数据新增和修改操作上。它的参数与@Cacheable类似,具体功能可参考上面对@Cacheable参数的解析。

  • @CacheEvict:配置于函数上,通常用在删除方法上,用来从缓存中移除相应数据。除了同@Cacheable一样的参数之外,它还有下面两个参数:

  • - allEntries:非必需,默认为false。当为true时,会移除所有数据。

  • - beforeInvocation:非必需,默认为false,会在调用方法之后移除数据。当为true时,会在调用方法之前移除数据。

↓↓点击
阅读原文
,查看完整源码。
640?wx_fmt=gif

与其相忘江湖,不如点赞关注

本文为  java4all (公众号:java4all)原创

欢迎转载,请注明出处或文末给出二维码

谢谢!

转载地址:https://it4all.blog.csdn.net/article/details/89037673 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:(15)SpringBoot使用Junit单元测试
下一篇:聊聊分布式

发表评论

最新留言

做的很好,不错不错
[***.243.131.199]2024年04月25日 15时28分48秒