早些时候,博主介绍过Redis的使用:Java开发 - Redis初体验,Redie是基于缓存的一项技术,对于Redis,博主此处不再赘述,不了解的可以去看这篇文章,但Redis缓存并不是顶峰,本文要讲的内容就是Redis的辅助工具:SpringCache——的使用。有了SpringCache,Redis便可如虎添翼,使用效果更上一层楼,下面,我们就一起来见识见识SpringCache的厉害吧。
听名字,我们就知道,SpringCache是Spring大家族的一员,所以Spring很好的兼容了SpringCache,它实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能,大大简化我们在业务中操作缓存的代码,比如redisTemplate,这些东西就可以省略了。
Spring Cache提供了一层抽象,底层可以切换不同的cache实现。具体就是通过CacheManager接口来统一不同的缓存技术。CacheManager是Spring提供的各种缓存技术抽象接口,不同的缓存技术需要实现不同的CacheManager,我们大致来看下都有哪些:
CacheManager | 简介 |
---|---|
EhCacheCacheManager | 使用EhCache作为缓存技术 |
GuavaCacheManager | 使用Google的GuavaCache作为缓存技术 |
RedisCacheManager | 使用Redis作为缓存技术 |
从业这么多年,在博主还不做后端的时候,听得最多的就是Redis,所以,今天我就以Redis缓存为基础,来讲解SpringCache的使用。
SpringCache发挥作用,主要用到了以下四个注解,不要小看了这四个注解,他们起到了举足轻重的作用。
在这之前,让我们先一起创建一个项目吧,方便大家进行测试。
4.0.0 org.springframework.boot spring-boot-starter-parent 2.6.0 com.codingfire cache 0.0.1-SNAPSHOT cache Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starter mysql mysql-connector-java 8.0.26 runtime org.springframework.boot spring-boot-starter-test test com.baomidou mybatis-plus-boot-starter 3.4.1 com.alibaba druid 1.1.6 org.projectlombok lombok org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-maven-plugin
entity.User
package com.codingfire.cache.entity;
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
private Long id;
private String username;
private String password;
private Integer age;
private String phone;
}
mapper.UserMapper
package com.codingfire.cache.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.codingfire.cache.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper {
}
service.IUserService
package com.codingfire.cache.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.codingfire.cache.entity.User;
public interface IUserService extends IService {
}
service.impl.UserServiceImpl
package com.codingfire.cache.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.codingfire.cache.entity.User;
import com.codingfire.cache.mapper.UserMapper;
import com.codingfire.cache.service.IUserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl implements IUserService {
}
controller.UserController
package com.codingfire.cache.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
}
create database if not exists s_cache character set utf8;
use s_cache;
CREATE TABLE user (
id bigint(20) primary key auto_increment,
username varchar(32) not null,
password varchar(32) not null,
age int(3) not null ,
phone varchar(32) not null
);
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/s_cache?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&allowPublicKeyRetrieval=true username: root password: xxxx
到这里,准备工作就做完了,上周刚写了MyBatisPlus,我们刚好拿来练练手,没看过的传送门: Java开发 - MybatisPlus框架初体验
@EnableCaching的作用是:开启缓存注解功能,需要添加到启动类上。这个没有太多的使用可以说,干就完了。但接下来这三个可是三王般的存在,学起来可要下点功夫了。虽然简单,案例还是要给的:
package com.codingfire.cache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}
@Cacheable的作用是:在方法执行前,spring先查看缓存中是否有数据,如果有数据,则直接返回前端缓存数据;若没有数据,调用方法获取数据并将方法返回值放到缓存中。
这段话说的比较片面,要想理解其使用,还是有些模棱两可,也并不是直接添加了此注解就可以的,还需要几个关键参数的配合。
@Cacheable一般用在查询方法上,所以才有了上面那句介绍。@Cacheable共有四个属性参数供使用:
为了能查询到,我们先在数据库中添加几条数据:
接着我们以第一条的id去数据库中查找:
@Autowired
private IUserService userService;
@Autowired
CacheManager cacheManager;
@Cacheable(cacheNames = "user",key = "#id")
@GetMapping("/{id}")
public User getById(@PathVariable Long id){
User user = userService.getById(id);
return user;
}
为了方便查看缓存,我们添加CacheManager,在方法中打断点,运行debug模式,使用postman发起请求,然后通过CacheManager跟踪缓存中数据的变化:
在这里,cachemanager看到没?第一次获取,是没有缓存的,请求结束后就有了,所以在第二次请求的时候,里面是有第一次的缓存的,我们发起第二次请求,注意,第二次查询的id需要换一个人,否则因为已经存入缓存,就不会执行我们的这个方法,连断点都不会走了:
第二次可以看到缓存中已经存在了第一次查询的数据。
然后我们看看另外两个注解:
@Cacheable(cacheNames = "user",key = "#id",unless = "#result==null")
@Cacheable(cacheNames = "user",key = "#id",condition = "#result!=null")
为空的时候就不会存缓存了,其实很好理解,博主就不一一测试了,感兴趣的小伙伴用不存在的id查询,一试就知。
@CachePut多用在修改或更新方法上,然后保存其返回值到缓存,@CachePut注解只有两个属性:
对了,忘记说了,key值我们不能给一个固定的值,Spring对于key的命名有固定的规则,可以使用方法调用的对象,方法名,方法参数,方法返回值来进行设置key的名称,具体如下:
@PostMapping
@CachePut(value = "userCache", key = "#user.id")
public User add(User user) {
userService.save(user);
return user;
}
为了方便大家查看,我们来修改下上面的方法:
@PostMapping
@CachePut(value = "user", key = "#user.id")
// @CachePut(value = "user", key = "#backUser.id")
public User add(User user) {
userService.save(user);
User backUser = user;
return backUser;
}
这样是不是可以更方便区分参数和返回值。 和上面的方法一样,打断点,一步步执行,查看缓存中数据。
@CacheEvict一般用在增删改方法上 ,一旦发现用户对数据做了修改(增删改), 就删除缓存中的数据,和上面两个注解不太一样,@CacheEvict不支持unless和condition,但是有一个allEntries属性可用,意思是当前分类下,全部删除,当前分类就是value/cacheNames。
下面,我们来看看它怎么用。
//@CacheEvict(cacheNames = "user",key="#id")
@CacheEvict(cacheNames = "user",allEntries = true)
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id){
userService.removeById(id);
}
可以选择单个删除,也可以选择全部删除,那么有没有多个删除呢?有!看代码:
// @Caching(evict = {
// @CacheEvict(cacheNames = "user",key = "#ids[0]"),
// @CacheEvict(cacheNames = "user",key = "#ids[1]")
// })
@CacheEvict(value = "userCache",key = "#root.args[0]") //#root.args[0] 代表第一个参数
@PostMapping("/deletes")
public void deletes(@RequestBody List ids){
userService.removeByIds(ids);
}
但是,你应该发现了一点问题,多指定删除,如果ids很大,甚至你不知道size,怎么办?有点无解,所以还是不建议这么做,建议删除当前分类下所有缓存。
关于测试,博主就不再贴出来了,自行完成即可。
说完了SpringCache,大家估计都很好奇,Redis呢?现在,它来了,SpringCache和Redis的结合使用堪称bug级别,连redis的代码都省略了,你就说厉不厉害。
那么为什么要和Redis一起使用呢?如果你前面测试了,你就会发现,每次重启后,缓存数据都清空了,这在真实项目中是不是很不友好?我们还指望这些数据来帮我们加快查询的速度呢,你重启一次清空一次,缓存的重新建立可是需要很多时间的,怎么办呢?上Redis!
下面,就让我们一起来使用Spring Boot来整合Redis和SpringCache吧!
org.springframework.boot spring-boot-starter-cache org.springframework.boot spring-boot-starter-data-redis
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/s_cache?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&allowPublicKeyRetrieval=true username: root password: xxxx redis: database: 0 host: localhost port: 6379 password: jedis: pool: #最大连接数 max-active: 8 #最大阻塞等待时间(负数表示没限制) max-wait: -1 #最大空闲 max-idle: 8 #最小空闲 min-idle: 0 #连接超时时间 timeout: 10000 cache: redis: time-to-live: 360000000
做完这些,直接运行项目即可,但是切记要先启动Redis服务。接着通过postman发起请求:
我们到Redis可视化工具看看:
我们前面受过存储的格式,大家还记得吗? cacheNames::key,但是问题又来了,你发现存储的数据是乱码,这这这!这是为什么?
这是因为我们没有设置Redis的序列化方式,下面我们来配置下:
我们把早起学习Redis使用的配置类添加进来:
package com.codingfire.cache.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.io.Serializable;
@Configuration
public class RedisConfig0 {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(RedisSerializer.json());
return redisTemplate;
}
}
清空缓存,执行添加方法后,发现依然是乱码:看来事情并没有我想的那么简单 ,原来的配置类不行了,肯定是因为SpringCache的缘故导致的,不要慌,我们换一种序列化方式,上面的就删掉吧:
package com.codingfire.cache.config;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableConfigurationProperties(CacheProperties.class) // 加载缓存配置类
public class RedisConfig {
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// 设置缓存key的序列化方式
config =
config.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new StringRedisSerializer()));
// 设置缓存value的序列化方式(JSON格式)
config =
config.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
接着,我们再运行项目,执行post添加用户请求:
看看图中的存储格式对了吗?果然是啊!换成get接口获取用户数据试试,如果细心点,你会发现请求瞬间就回来,这是因为你添加用户后,数据就已经存入了Redis,下次查询时,不会再查询数据库,而是直接从Redis中找。
快乐的时光总是短暂的,到这里,SpringCache就给大家介绍完了,如果有了解的地方,欢迎留言讨论,有些的不对的,也欢迎指出。本以为随便写写要不了多大的篇幅,没想到还是写了1w+字,咱们下一篇再见了。