数据库的数据是存储在硬盘上的,频繁访问性能较低。如果将一些需要频繁查询的热数据放到内存的缓存中,可以大大减轻数据库的访问压力。
SpringCache提供基本的Cache抽象,并没有具体的缓存能力,需要配合具体的缓存实现来完成,目前SpringCache支持redis、ehcache、simple(基于内存)等方式来实现缓存。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
非常好的参考文章
SpringBoot基础系列-SpringCache使用
spring boot2 (28)-cache缓存
SpringBoot2.x—SpringCache(2)使用
通过继承CachingConfigurerSupport类自定义缓存管理器【与本文方式不同,值得一看】
@Cacheable属性名 | 用途 | 备注 |
---|---|---|
cacheNames | 指定缓存空间的名称,不同缓存空间的数据是彼此隔离的 | |
key | 同一个cacheNames中通过key区别不同的缓存。如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合,如:@CachePut(value = “demo”, key = “‘user’+#user.id”),字符串中spring表达式意外的字符串部分需要用单引号 | SpringCache提供了与缓存相关的专用元数据,如target、methodMame、result、方法参数等,如:@CachePut(value = “demo”, key = “#result==null”) |
keyGenrator | key的生成策略,SpringCache默认使用SimpleKeyGenerator,默认情况下将参数值作为键,但是可能会导致key重复出现,因此一般需要自定义key的生成策略 | |
condition | condition是在调用方法之前判断条件,满足条件才缓存 | @Cacheable(cacheNames=“book”, condition="#name.length() < 32") |
unless | unless是在调用方法之后判断条件,如果SpEL条件成立,则不缓存【条件满足不缓存】 | @Cacheable(cacheNames = “hello”,unless="#result.id.contains(‘1’)" ) |
sync | 缓存过期之后,如果多个线程同时请求对某个数据的访问,会同时去到数据库,导致数据库瞬间负荷增高。Spring4.3为@Cacheable注解提供了一个新的参数“sync”(boolean类型,默认为false)。当设置它为true时,只有一个线程的请求会去到数据库,其他线程都会等待直到缓存可用。这个设置可以减少对数据库的瞬间并发访问。 |
//当返回值为null时,通过unless属性
@CachePut(key = “#id”, unless=”#result == null”)
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
<version>2.3.0.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
<version>2.3.0.RELEASEversion>
<exclusions>
<exclusion>
<artifactId>lettuce-coreartifactId>
<groupId>io.lettucegroupId>
exclusion>
exclusions>
dependency>
dependencies>
spring:
redis:
database: 0
host: www.onething.top
port: 6379
password: 123456nw
#配置jedis客户端,这里也可以jedis替换为lettuce客户端,下级配置都一样
jedis:
pool:
# 连接池中的最大空闲连接 默认8
max-idle: 8
# 连接池中的最小空闲连接 默认0
min-idle: 0
# 连接池最大连接数 默认8 ,负数表示没有限制
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认-1
max-wait: -1
timeout: 3000
cache:
type: redis # 指定使用的缓存类型
# redis: 当自定义ChacheManager时,就这里的配置不需要配置,配置了也不起作用
# use-key-prefix: true
# key-prefix: "demo:"
# time-to-live: 60000 #缓存超时时间 单位:ms
# cache-null-values: false #是否缓存空值
cache-names: user
cache:
ttl: '{"user":60,"dept":30}' #自定义某些缓存空间的过期时间
@Configuration
public class RedisConfiguration {
// ${cache} 获取配置文件的配置信息 #{}是spring表达式,获取Bean对象的属性
@Value("#{${cache}}")
private Map<String, Long> ttlParams;
/**
* @param redisConnectionFactory
* @功能描述 redis作为缓存时配置缓存管理器CacheManager,主要配置序列化方式、自定义
*
* 注意:配置缓存管理器CacheManager有两种方式:
* 方式1:通过RedisCacheConfiguration.defaultCacheConfig()获取到默认的RedisCacheConfiguration对象,
* 修改RedisCacheConfiguration对象的序列化方式等参数【这里就采用的这种方式】
* 方式2:通过继承CachingConfigurerSupport类自定义缓存管理器,覆写各方法,参考:
* https://blog.csdn.net/echizao1839/article/details/102660649
*
* 切记:在缓存配置类中配置以后,yaml配置文件中关于缓存的redis配置就不会生效,如果需要相关配置需要通过@value去读取
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
redisCacheConfiguration = redisCacheConfiguration
// 设置key采用String的序列化方式
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer.UTF_8))
//设置value序列化方式采用jackson方式序列化
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer()))
//当value为null时不进行缓存
.disableCachingNullValues()
// 配置缓存空间名称的前缀
.prefixCacheNameWith("demo:")
//全局配置缓存过期时间【可以不配置】
.entryTtl(Duration.ofMinutes(30L));
//专门指定某些缓存空间的配置,如果过期时间【主要这里的key为缓存空间名称】
Map<String, RedisCacheConfiguration> map = new HashMap<>();
Set<Map.Entry<String, Long>> entries = ttlParams.entrySet();
for (Map.Entry<String, Long> entry : entries) {
//指定特定缓存空间对应的过期时间
map.put("user", redisCacheConfiguration.entryTtl(Duration.ofSeconds(40)));
map.put(entry.getKey(), redisCacheConfiguration.entryTtl(Duration.ofSeconds(entry.getValue())));
}
return RedisCacheManager
.builder(redisConnectionFactory)
.cacheDefaults(redisCacheConfiguration) //默认配置
.withInitialCacheConfigurations(map) //某些缓存空间的特定配置
.build();
}
/**
* 自定义缓存的redis的KeyGenerator【key生成策略】
* 注意: 该方法只是声明了key的生成策略,需在@Cacheable注解中通过keyGenerator属性指定具体的key生成策略
* 可以根据业务情况,配置多个生成策略
* 如: @Cacheable(value = "key", keyGenerator = "cacheKeyGenerator")
*/
@Bean
public KeyGenerator keyGenerator() {
/**
* target: 类
* method: 方法
* params: 方法参数
*/
return (target, method, params) -> {
//获取代理对象的最终目标对象
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getSimpleName()).append(":");
sb.append(method.getName()).append(":");
//调用SimpleKey的key生成器
Object key = SimpleKeyGenerator.generateKey(params);
return sb.append(key);
};
}
/**
* @param redisConnectionFactory:配置不同的客户端,这里注入的redis连接工厂不同: JedisConnectionFactory、LettuceConnectionFactory
* @功能描述 :配置Redis序列化,原因如下:
* (1) StringRedisTemplate的序列化方式为字符串序列化,
* RedisTemplate的序列化方式默为jdk序列化(实现Serializable接口)
* (2) RedisTemplate的jdk序列化方式在Redis的客户端中为乱码,不方便查看,
* 因此一般修改RedisTemplate的序列化为方式为JSON方式【建议使用GenericJackson2JsonRedisSerializer】
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = serializer();
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// key采用String的序列化方式
redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);
// value序列化方式采用jackson
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
// hash的key也采用String的序列化方式
redisTemplate.setHashKeySerializer(StringRedisSerializer.UTF_8);
//hash的value序列化方式采用jackson
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
/**
* 此方法不能用@Ben注解,避免替换Spring容器中的同类型对象
*/
public GenericJackson2JsonRedisSerializer serializer() {
return new GenericJackson2JsonRedisSerializer();
}
}
(1)开启缓存功能
@EnableCaching //开启缓存的主键
@SpringBootApplication
public class RedisDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RedisDemoApplication.class, args);
}
}
(2)使用缓存
@Service
@CacheConfig(cacheNames = "user",keyGenerator = "keyGenerator")
public class RedisServiceImpl implements RedisService {
@Cacheable(value = "user", key = "'list'")
@Override
public List<User> list() {
System.out.println("=========list");
User user1 = new User();
user1.setId(1);
user1.setName("老大");
User user2 = new User();
user2.setId(2);
user2.setName("老二");
List<User> users = new ArrayList<>();
users.add(user1);
users.add(user2);
return users;
}
@CacheEvict(value = "user", key = "'list'")
@Override
public void del(Integer id) {
System.out.println("************************************+id");
List<User> users = new ArrayList<>();
Iterator<User> iterator = users.iterator();
while (iterator.hasNext()) {
User user = iterator.next();
if (user.getId().equals(id)) {
iterator.remove();
break;
}
}
}
@CachePut(value = "demo", key = "#result==null")
@Override
public User select(Integer id) {
System.out.println("===============dddd================");
if (id == 0) {
return null;
}
User user = new User();
user.setId(100);
user.setName("测试");
return user;
}
}