Spring 3.1 中开始对缓存提供支持,核心思路是对方法的缓存,当我们调用一个方法时,将方法的参数和返回值作为 key/value 缓存起来,当再次调用该方法时,如果缓存中有数据,就直接从缓存中获取,否则再去执行该方法。不过并未提供缓存的实现,可以自由选择缓存的实现,目前 Spring Boot 支持的缓存有 JCache、EhCache 2.x、Redis等。不过无论使用那种缓存实现,不同的只是缓存配置,开发者使用的缓存注解都是一样的。
Ehchche 依赖及配置
- 首先编辑项目的 pom.xml 文件,添加 spring-boot-starter-cache 依赖以及 Ehcache 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
- 添加缓存配置文件
在 resources 目录下创建 ehcache.xml 文件作为 Ehcache 缓存的配置文件,内容如下:
(1)如果 Ehcache 的依赖存在,并且在 classpath 下又一个名为 encache.xml 的 Ehcache 配置文件,那么 EhCacheManager 将会自动作为缓存的实现。
(2)这是一个常规的 Ehcache 配置文件,提供了两个缓存策略,一个是默认的,另一个名为 book_cache。具体参数作用如下:
- name:缓存名称
- maxElementsInMemory:缓存最大个数
- eternal:缓存对象是否永久有效,一但设置了永久有效,timeout 将不起作用。
- timeToIdleSeconds:缓存对象在失效前的允许闲置时间(单位:秒)。仅当 eternal=false 对象不是永久有效时,该属性才生效。默认值是 0,也就是可闲置时间无穷大。
- timeToLiveSeconds:缓存对象在失效前允许存活时间(单位:秒)。仅当 eternal=false 对象不是永久有效时,该属性才生效。默认值是 0,也就是存活时间无穷大。
- overflowToDisk:表示内存中的数量达到 maxElementsInMemory 时,Ehcache 是否将对象写到磁盘中。
- diskPersistent:是否缓存虚拟机重启期数据。
- diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是 120 秒。
(3)如果我们想要自定 Ehcache 配置文件的名称和位置,可以在 application.properties 中添加如下配置:
- spring.cache.ehcache.config=classpath:config/another-config.xml
<ehcache>
<diskStore path="java.io.tmpdir/cache"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
<cache
name="test-cache"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="30"
overflowToDisk="false"
diskPersistent="false"
memoryStoreEvictionPolicy="LRU">
</cache>
</ehcache>
启用缓存
- 开启缓存
在项目的入口类上添加 @EnableCaching 注解开启缓存,代码如下:
@SpringBootApplication
@EnableCaching
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context
= SpringApplication.run(DemoApplication.class, args);
}
}
- 开始测试
创建一个 Service 并添加相关的缓存注解:
(1) 添加 @CacheConfig 注解指定使用的缓存的名字,这个配置可选。若不使用 @CacheConfig 注解,则直接在 @Cacheable 注解中指明缓存名字。
(2) 在方法上添加 @Cacheable 注解表示对该方法进行缓存:
默认情况下,缓存的 key 是方法的参数,缓存的 value 是方法的返回值。
当开发者在其他类中调用该方法时,首先会根据调用参数查看缓存中是否有相关数据,若有则直接使用缓存数据,该方法不会执行。
否则执行该方法,执行成功后将返回值缓存起来。
但若是在当前类中调用该方法,则缓存不会生效。
(3)@Cacheable 注解中还有一个属性 condition 用来描述缓存的执行时机,例如:
@Cacheable(condition="#id%2-0") 表示当 id 对 2 取模为 0 时才进行缓存,否则不缓存。
(4)除了上面这种使用参数定义 key 的方式之外,Spring 还提供了一个 root 对象用来生成 key。下面是一些用法实例:
#root.methodName
:当前方法名
#root.method.name
:当前方法对象
#root.caches[0].name
:当前方法使用的缓存
#root.target
:当前被调用的对象
#root.targetClass
:当前被调用的对象的 class
#root.args[0]
:当前方法参数数组
(5)@Cacheput 注解一般用于数据更新方法上:
与 @Cacheable 注解不同,添加了 @Cacheput 注解的方法每次在执行时都不去检查缓存中是否有数据,而是直接执行方法,然后将方法的执行结果缓存起来。
如果该 key 对应的数据已经被缓存起来了,就会覆盖之前的数据,这样可以避免再次加载数据时获取到脏数据。
同时,@Cacheput 具有和 @Cacheable 类似的属性,这里不再赘述。
(6)@CacheEvict 注解一般用于删除方法上,表示移除一个 key 对应的缓存。@CacheEvict 注解有两个特殊的属性 allEntries 和 beforelnvocation:
allEntries 表示是否将所有的缓存数据都移除,默认为 false。
beforelnvocation 表示是否在方法执行之前移除缓存中的数据,默认为 false,即在方法执行之后移除缓存中的数据。
@Service
public class UserService {
...
//删除用户数据
@CacheEvict(value = DEMO_CACHE_NAME,key = "'user_'+#id")//这是清除缓存
public void delete(String id){
userDao.delete(id);
}
//更新用户数据
@CachePut(value = DEMO_CACHE_NAME,key = "'user_'+#user.getId()")
public User update(User user) throws CacheException {
User user1 = userDao.findById(user.getId());
if (null == user1){
throw new CacheException("Not Find");
}
userDao.update(user);
return user;
}
//查找用户数据
@Cacheable(value=DEMO_CACHE_NAME,key="'user_'+#id")
public User findById(String id){
//若找不到缓存将打印出提示语句
System.err.println("直接查询数据库!"+id);
User user = userDao.findById(id);
return user;
}
//保存用户数据
@CacheEvict(value=DEMO_CACHE_NAME,key=CACHE_KEY)
public void save(User user){
userDao.save(user);
}
}
测试缓存
- 使用controller测试
@RequestMapping("/test")
public String EhcacheTest(){
System.out.println("====生成第一个用户====");
User user1 = new User(UUID.randomUUID().toString(),"熊大",18);
userService.save(user1);
//第一次查询
System.out.println(userService.findById(user1.getId()));
//通过缓存查询
System.out.println(userService.findById(user1.getId()));
System.out.println("====修改数据====");
User user2 = new User(user1.getId(),"熊二",20);
try {
userService.update(user2);
} catch (CacheException e){
e.printStackTrace();
}
System.out.println(userService.findById(user1.getId()));
return user1.getId();
}
执行结果下如下:
image.png
可以看到第一次从DB查询了数据,第二次则从缓存读取,当更新DB数据时同时也更新了缓存数据,再次取数据仍然走缓存。
灵活控制缓存
对于高频数据,一般希望长时间缓存,对于临时数据,如验证码,token等,一般需要一个指定的时间,到期则取消。
可以通过一个类去控制缓存的有效时间,一般指定timeToIdleSeconds=0表示数据一直有效,timeToLiveSeconds=X秒指定过期时间,如下:
public class EhCacheUtil {
private static final String path = "./src/main/resources/ehcache/ehcache.xml";
private CacheManager cacheManager;
private static EhCacheUtil ehCache;
private EhCacheUtil(String path) {
cacheManager = CacheManager.create(path);
}
public static EhCacheUtil getInstance() {
if (ehCache== null) {
ehCache= new EhCacheUtil(path);
}
return ehCache;
}
// 默认的缓存存在时间(秒)
private static final int DEFAULT_LIVE_SECOND = 1 * 10;
/**
* @Author: dian
* @Date: 2020/5/25 18:29
* @Description: 添加缓存
*/
public void set(String key,String value){
Cache cache = cacheManager.getCache("test-cache");
Element element = new Element(key,value,0,DEFAULT_LIVE_SECOND);
cache.put(element);
}
/**
* @Author: dian
* @Date: 2020/5/25 18:29
* @Description: 添加缓存
* @param timeToLiveSeconds 缓存生存时间(秒)
*/
public void set(String key,String value,int timeToLiveSeconds){
Cache cache = cacheManager.getCache("test-cache");
Element element = new Element(key,value,0,timeToLiveSeconds);
cache.put(element);
}
/**
* @Author: dian
* @Date: 2020/5/25 18:29
* @Description: 添加缓存
* @param timeToIdleSeconds 对象空闲时间,指对象在多长时间没有被访问就会失效。 * 只对eternal为false的有效。传入0,表示一直可以访问。以秒为单位。
* @param timeToLiveSeconds 缓存生存时间(秒) 只对eternal为false的有效
*/
public void set(String key,String value,int timeToIdleSeconds,int timeToLiveSeconds){
Cache cache = cacheManager.getCache("test-cache");
Element element = new Element(key,value,timeToIdleSeconds,timeToLiveSeconds);
cache.put(element);
}
/**
* @Author: dian
* @Date: 2020/5/25 18:30
* @Description: 获取缓存
*/
public String get(String key){
Cache cache = cacheManager.getCache("test-cache");
Element element = cache.get(key);
if(element == null){
return null;
}
return (String) element.getObjectValue();
}
/**
* @Author: dian
* @Date: 2020/5/26 8:49
* @Description: 获取缓存个数
*/
public int geSize(){
Cache cache = cacheManager.getCache("test-cache");
return cache.getSize();
}
/**
* @Author: dian
* @Date: 2020/5/26 9:32
* @Description: 删除缓存
*/
public void delete(String key){
Cache cache = cacheManager.getCache("test-cache");
cache.remove(key);
}
}
然后在controller灵活的调用
@RequestMapping("/get")
public String get(){
EhCacheUtil ehCacheUtil = EhCacheUtil.getInstance();
String value = ehCacheUtil.get("dian");
return value;
}
@RequestMapping("/set")
public String set(){
EhCacheUtil ehCacheUtil = EhCacheUtil.getInstance();
ehCacheUtil.set("dian","你好",0, 600);
return "成功添加";
}
对于无状态的系统,可以方便的缓存token,验证码等短时有效的数据,方便存取。
网友评论