整合Spring Cache
Spring Cache是Spring框架为我们提供的一个缓存抽象层,无论我们使用一个map作为本地缓存,还是EnCache和Redis这种专业的缓存,都可以使用Spring Cache方便地集成,使用起来也很简单,使用注解在业务逻辑方法上标注,即可完成缓存操作,而不用将缓存操作和业务代码耦合在一起
基本使用
使用Spring Cache,首先需要导入对应的starter依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
这里我使用最常用的缓存方案,使用Redis作为缓存,在使用Spring Cache整合Redis之前,就需要先配置Redis
spring:
redis:
host: 127.0.0.1
port: 6379
database: 1
password: 12345
然后配置缓存的相关配置
spring:
cache:
# 缓存类型
type: redis
redis:
# 缓存失效时间
time-to-live: 60000
# 是否缓存空值
cache-null-values: true
# 是否启用前缀
use-key-prefix: true
# 前缀
key-prefix: cache_
然后在主启动类上或者配置类上添加@EnableCaching
注解开启缓存功能
@EnableCaching
@SpringBootApplication
public class SpringDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringDemoApplication.class, args);
}
}
Spring Cache默认使用JDK序列化写入Redis服务器,需要缓存的实体类实现Serializable
接口
但是这样做缓存服务器中的数据是不可读的,而且不兼容其他非Java构建的服务,我们可以使用以下配置,将序列化机制改为JSON序列化方式
@Configuration
@EnableConfigurationProperties(CacheProperties.class)
public class RedisConfig {
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
CacheProperties.Redis redis = cacheProperties.getRedis();
if (redis.getTimeToLive() != null) {
configuration = configuration.entryTtl(redis.getTimeToLive());
}
if (redis.getKeyPrefix() != null) {
configuration = configuration.prefixCacheNameWith(redis.getKeyPrefix());
}
if (!redis.isCacheNullValues()) {
configuration = configuration.disableCachingNullValues();
}
if (!redis.isUseKeyPrefix()) {
configuration = configuration.disableKeyPrefix();
}
return configuration;
}
}
这里的配置会将配置文件中的配置覆盖掉,所以需要注入CacheProperties
类,然后再次判断配置文件中的值,重新设置
最后只需要在需要缓存的方法上添加@Cacheable
注解即可,Spring Cache会缓存方法的返回值,调用方法时先查询缓存,如果缓存未命中才执行方法
@Cacheable
注解需要配置两个属性,一个是cacheNames,这个属性指定缓存的分区,如果需要缓存在多个分区,可以使用数组的形式
还有一个属性是key,这个属性指定了缓存在Redis中的键的值,使用SpEL表达式,这里使用方法名作为键的值
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Cacheable(cacheNames = "user", key = "#root.methodName")
@Override
public List<User> queryUsers() {
System.out.println("查询数据库");
return userMapper.selectList(null);
}
}
编写一个测试接口来测试一下
@RestController
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/getUsers")
public List<User> getUsers() {
return userService.queryUsers();
}
}
第一次调用正常执行queryUsers方法,因为此时缓存中还没有数据
不过这时候查看Redis中的数据,发现已经有缓存了
可以看到缓存的键为配置文件中指定的前缀+分区名+注解中指定的键名
后续在缓存失效时间内多次调用接口,就不会再执行queryUsers方法,而是直接从缓存中返回数据
读模式加锁
缓存读模式中的三个问题在Spring Cache中都可以得到解决,缓存穿透问题可以通过设置缓存空对象来解决,缓存雪崩问题可以通过设置缓存失效时间来解决,而缓存击穿问题可以通过设置加锁来解决
设置加锁在Spring Cache中也可以轻松实现,在@Cacheable
注解中设置sync属性为true即可,这样在缓存失效时,如果有多个请求同时调用该方法,会以加锁排队的方式同步调用,底层是使用synchronized锁机制同步执行方法
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Cacheable(cacheNames = "user", key = "#root.methodName", sync = true)
@Override
public List<User> queryUsers() {
System.out.println("查询数据库");
return userMapper.selectList(null);
}
}
失效模式
在缓存写模式中,需要考虑数据一致性问题,有两种方法,失效模式和双写模式
失效模式既如果数据发生改变,直接删除缓存,等到下一次查询数据时,会因为缓存未命中再次查询数据库,将最新的数据缓存到缓存服务器中
在Spring Cache中使用失效模式,只需要在修改数据的方法上添加@CacheEvict
注解即可,然后使用cacheNames和key属性分别指定分区名和缓存的键名就可以在调用该方法的时候删除对应的缓存
以下示例在调用modifyUser方法进行用户修改的时候删除queryUsers方法在缓存服务器中的缓存,注意key属性使用SpEL表达式进行赋值,需要在""
中再加上''
才是指定字符串的含义
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Cacheable(cacheNames = "user", key = "#root.methodName", sync = true)
@Override
public List<User> queryUsers() {
System.out.println("查询数据库");
return userMapper.selectList(null);
}
@CacheEvict(cacheNames = "user", key = "'queryUsers'")
@Override
public void modifyUser(User user) {
userMapper.updateById(user);
}
}
如果需要删除分区中所有的缓存,只需要将allEntries属性设置为true既可
双写模式
双写模式既在数据发生改变时,将最新的数据即时地写入缓存中
在Spring Cache中使用双写模式,只需要在修改数据的方法上添加@CachePut
注解即可,使用方式和@CacheEvict
注解相同
注意@CachePut
注解标注的方法也需要有对应返回值,缓存的最新数据为该方法的返回值,如以下示例中调用addUser方法时,插入数据后会调用queryUsers方法再次查询数据库得到最新的数据,然后用新数据将旧的缓存覆盖
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Cacheable(cacheNames = "user", key = "#root.methodName", sync = true)
@Override
public List<User> queryUsers() {
System.out.println("查询数据库");
return userMapper.selectList(null);
}
@CachePut(cacheNames = "user", key = "'queryUsers'")
@Override
public List<User> addUser(User user) {
userMapper.insert(user);
return queryUsers();
}
@CachePut(cacheNames = "user", key = "'queryUsers'")
@Override
public List<User> addUser(User user) {
userMapper.insert(user);
return queryUsers();
}
}