(11)SpringBoot整合EhCache做缓存

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6




(11)SpringBoot整合EhCache做缓存_缓存

   摘要:本文介绍在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的缓存实现

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


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

1.pom.xml


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

<!--缓存-->  
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--ehcache-->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>

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


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

@EnableCaching

3.配置文件ehcache.xml


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

<?xml version="1.0" encoding="UTF-8"?>  
<ehcache>
<cache name="user"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
diskPersistent="false"
diskSpoolBufferSizeMB="30"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
clearOnFlush="false"
/>
</ehcache>

各个参数的涵义如下:

  • 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<String,Object> findByPage(Integer pageSize,Integer pageNum){
Map<String,Object> map = new HashMap();
Page<User> 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<User> 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<User,Long> {
/**根据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<User> 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时,会在调用方法之前移除数据。





阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Spring