摘要:本文介绍在SpringBoot项目中,如何使用EhCache做缓存。
EhCache 简介:EhCache 是一个纯Java的进程内缓存框架,是Hibernate中默认的CacheProvider;其缓存的数据可以存放在内存里面,也可以存放在硬盘上;其核心是CacheManager,一切Ehcache的应用都是从CacheManager开始的;它是用来管理Cache(缓存)的,一个应用可以有多个CacheManager,而一个CacheManager下又可以有多个Cache;Cache内部保存的是一个个的Element,而一个Element中保存的是一个key和value的配对,相当于Map里面的一个Entry。
它具有如下特点:
注意:本文案例使用的接口层,持久层等,都依赖于(5)SpringBoot使用JPA访问数据库,并在其基础上添加了部分方法,完整源码请点击阅读原文,github同步更新代码。
在SpringBoot项目中整合EhCache做缓存,具体步骤如下:
我们在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>
启动类加此注解后,会自动开启SpringBoot对缓存的支持
@EnableCaching
在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>
各个参数的涵义如下:
由于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
由于此案例持久层使用的是之前写过的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----------------------*/
}
我们在需要使用缓存的持久层,添加@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);
}
然后,我们逐个调用接口,进行分析。
在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找到了,就直接返回了。
此注解通常用在删除方法上,用于从缓存中清除数据。
我们这里访问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的缓存数据删除了,所以缓存中再查询时查不到了。
此注解通常用在新增和修改方法上,根据条件,进行缓存。
我们执行一下http://localhost:8088/user/addUser,比如被加到数据库的User.userName=momo456,在addUser方法上,@CachePut(key = "#p0.userName"),我们会在数据库添加完User后,在缓存中也添加一份数据,key为momo456;此时,我们去执行http://localhost:8088/user/findByUserName?userName=momo456,会发现,尽管我们是第一次查询,但是控制台并没有显示sql语句,说明此请求并没有向数据库发送请求,而是直接走的缓存。