SpringBoot缓存-@Cacheable的用法

SpringBoot缓存-@Cacheable的用法

1简介实例配置及依赖Redis配置业务代码测试

简介

说明

本文用示例介绍SpringBoot的缓存注解@Cacheable的用法。

本文重点展示@Cacheable的配置及其基础用法,详细用法见:SpringBoot缓存-注解的用法 – 自学精灵

示例介绍

需求:给分页接口加缓存,且设置其过期时间。

第1次访问时,真实请求,执行成功后@Cacheable注解会将结果缓存到Redis。

之后访问时,先从缓存中取,若缓存中有则直接从缓存中取,不再执行方法内的逻辑。

过期时间统一在配置类中设置,里边设置部分key的过期时间,其余的用默认的过期时间。

实例

配置及依赖

application.yml

spring:

redis:

host: 127.0.0.1

port: 6379

# password:

# database: 0 #指定数据库,默认为0

# timeout: 3000 #连接超时时间,单位毫秒,默认为0。也可以这么写:3s

# ssl: false # 是否启用SSL连接,默认false

# pool: #连接池配置

# max-active: 8 #最大活跃连接数,默认8个。

# max-idle: 8 #最大空闲连接数,默认8个。

# max-wait: -1 #获取连接的最大等待时间,默认-1,表示无限制,单位毫秒。

# #默认值可能会因为获取不到连接,导致事务无法提交,数据库被锁,大量线程处于等待状态的情况。

# min-idle: 0 #最小空闲连接数,默认0。

# sentinel:

# master: myMaster #哨兵master

# nodes: host1:port,host2:port #哨兵节点

# cluster:

# max-redirects: # 集群模式下,集群最大转发的数量

# nodes: host1:port,host2:port # 集群节点

pom.xml

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

org.springframework.boot

spring-boot-starter-parent

2.3.12.RELEASE

com.example

demo_Cacheable_SpringBoot

0.0.1-SNAPSHOT

demo_Cacheable_SpringBoot

demo_Cacheable_SpringBoot

1.8

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-data-redis

org.projectlombok

lombok

true

org.springframework.boot

spring-boot-starter-test

test

org.springframework.boot

spring-boot-maven-plugin

org.projectlombok

lombok

Redis配置

常量

package com.example.demo.constant;

public class CachingConstant {

/**

* @Cacheable的cacheNames属性使用的值

*/

public interface CacheNames {

String CACHE_30_SECOND = "redis_cache_30_second";

String CACHE_1_MINUTE = "redis_cache_1_minute";

String CACHE_5_MINUTE = "redis_cache_5_minute";

String CACHE_10_MINUTE = "redis_cache_10_minute";

String CACHE_30_MINUTE = "redis_cache_30_minute";

String CACHE_1_HOUR = "redis_cache_1_hour";

String CACHE_2_HOUR = "redis_cache_2_hour";

String CACHE_6_HOUR = "redis_cache_6_hour";

String CACHE_12_HOUR = "redis_cache_12_hour";

String CACHE_1_DAY = "redis_cache_1_day";

String CACHE_15_DAY = "redis_cache_15_day";

String CACHE_30_DAY = "redis_cache_30_day";

String CACHE_60_DAY = "redis_cache_60_day";

String CACHE_180_DAY = "redis_cache_180_day";

String CACHE_365_DAY = "redis_cache_365_day";

}

}

配置

package com.example.demo.config;

import com.example.demo.constant.CachingConstant;

import com.fasterxml.jackson.annotation.JsonAutoDetect;

import com.fasterxml.jackson.annotation.JsonTypeInfo;

import com.fasterxml.jackson.annotation.PropertyAccessor;

import com.fasterxml.jackson.databind.ObjectMapper;

import org.springframework.cache.CacheManager;

import org.springframework.cache.annotation.CachingConfigurerSupport;

import org.springframework.cache.annotation.EnableCaching;

import org.springframework.cache.interceptor.KeyGenerator;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.data.redis.cache.RedisCacheConfiguration;

import org.springframework.data.redis.cache.RedisCacheManager;

import org.springframework.data.redis.connection.RedisConnectionFactory;

import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.data.redis.serializer.*;

import org.springframework.util.StringUtils;

import java.time.Duration;

import java.util.HashMap;

import java.util.Map;

@Configuration

@EnableCaching

public class RedisCachingConfig extends CachingConfigurerSupport {

/**

* 重写缓存Key生成策略。

* 包名+方法名+参数列表。防止缓存Key冲突

*/

@Bean

@Override

public KeyGenerator keyGenerator() {

return (target, method, params) -> {

// 存放最终结果

StringBuilder resultStringBuilder = new StringBuilder("cache:key:");

// 执行方法所在的类

resultStringBuilder.append(target.getClass().getName()).append(".");

// 执行的方法名称

resultStringBuilder.append(method.getName()).append("(");

// 存放参数

StringBuilder paramStringBuilder = new StringBuilder();

for (Object param : params) {

if (param == null) {

paramStringBuilder.append("java.lang.Object[null],");

} else {

paramStringBuilder

.append(param.getClass().getName())

.append("[")

.append(String.valueOf(param))

.append("],");

}

}

if (StringUtils.hasText(paramStringBuilder.toString())) {

// 去掉最后的逗号

String trimLastComma = paramStringBuilder.substring(0, paramStringBuilder.length() - 1);

resultStringBuilder.append(trimLastComma);

}

return resultStringBuilder.append(")").toString();

};

}

@Bean

public CacheManager cacheManager(RedisConnectionFactory factory) {

Map configurationMap = new HashMap<>();

configurationMap.put(RedisConstant.CacheNames.CACHE_30_SECOND, createCacheConfig(Duration.ofSeconds(30)));

configurationMap.put(RedisConstant.CacheNames.CACHE_1_MINUTE, createCacheConfig(Duration.ofMinutes(1)));

configurationMap.put(RedisConstant.CacheNames.CACHE_5_MINUTE, createCacheConfig(Duration.ofMinutes(5)));

configurationMap.put(RedisConstant.CacheNames.CACHE_10_MINUTE, createCacheConfig(Duration.ofMinutes(10)));

configurationMap.put(RedisConstant.CacheNames.CACHE_30_MINUTE, createCacheConfig(Duration.ofMinutes(30)));

configurationMap.put(RedisConstant.CacheNames.CACHE_1_HOUR, createCacheConfig(Duration.ofHours(1)));

configurationMap.put(RedisConstant.CacheNames.CACHE_2_HOUR, createCacheConfig(Duration.ofHours(2)));

configurationMap.put(RedisConstant.CacheNames.CACHE_6_HOUR, createCacheConfig(Duration.ofHours(6)));

configurationMap.put(RedisConstant.CacheNames.CACHE_12_HOUR, createCacheConfig(Duration.ofHours(12)));

configurationMap.put(RedisConstant.CacheNames.CACHE_1_DAY, createCacheConfig(Duration.ofDays(1)));

configurationMap.put(RedisConstant.CacheNames.CACHE_15_DAY, createCacheConfig(Duration.ofDays(15)));

configurationMap.put(RedisConstant.CacheNames.CACHE_30_DAY, createCacheConfig(Duration.ofDays(30)));

configurationMap.put(RedisConstant.CacheNames.CACHE_60_DAY, createCacheConfig(Duration.ofDays(60)));

configurationMap.put(RedisConstant.CacheNames.CACHE_180_DAY, createCacheConfig(Duration.ofDays(180)));

configurationMap.put(RedisConstant.CacheNames.CACHE_365_DAY, createCacheConfig(Duration.ofDays(365)));

RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()

.entryTtl(Duration.ofDays(7))

.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))

.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))

.disableCachingNullValues();

return RedisCacheManager.builder(factory)

.initialCacheNames(configurationMap.keySet())

.withInitialCacheConfigurations(configurationMap)

// 如果key不在configurationMap中,则使用此配置

.cacheDefaults(config)

.build();

}

@Bean

public RedisTemplate redisTemplate(RedisConnectionFactory factory) {

RedisTemplate template = new RedisTemplate<>();

template.setConnectionFactory(factory);

template.setKeySerializer(keySerializer());

template.setValueSerializer(valueSerializer());

template.setHashKeySerializer(keySerializer());

template.setHashValueSerializer(valueSerializer());

template.afterPropertiesSet();

return template;

}

private RedisSerializer keySerializer() {

return new StringRedisSerializer();

}

private RedisSerializer valueSerializer() {

Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =

new Jackson2JsonRedisSerializer<>(Object.class);

ObjectMapper objectMapper = new ObjectMapper();

objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

// 此项必须配置,否则如果序列化的对象里边还有对象,会报如下错误:

// java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX

objectMapper.activateDefaultTyping(

objectMapper.getPolymorphicTypeValidator(),

ObjectMapper.DefaultTyping.NON_FINAL,

JsonTypeInfo.As.PROPERTY);

// 旧版写法:

// objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

return jackson2JsonRedisSerializer;

}

private RedisCacheConfiguration createCacheConfig(Duration ttl) {

return RedisCacheConfiguration.defaultCacheConfig()

.entryTtl(ttl)

.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))

.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()));

}

}

业务代码

Controller

package com.example.demo.controller;

import com.example.demo.entity.Result;

import com.example.demo.service.UserService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

@RestController

@RequestMapping("user")

public class UserController {

@Autowired

private UserService userService;

@GetMapping("page")

public Result page(int pageNo, int pageSize) {

return userService.page(pageNo, pageSize);

}

}

Service

接口

package com.example.demo.service;

import com.example.demo.entity.Result;

public interface UserService {

Result page(int pageNo, int pageSize);

}

实现

package com.example.demo.service.impl;

import com.example.demo.constant.RedisConstant;

import com.example.demo.entity.Result;

import com.example.demo.entity.User;

import com.example.demo.service.UserService;

import org.springframework.cache.annotation.Cacheable;

import org.springframework.stereotype.Service;

import java.util.ArrayList;

import java.util.Arrays;

import java.util.List;

@Service

public class UserServiceImpl implements UserService {

private final List allUsers = Arrays.asList(

new User(1L, "Tony1", 20),

new User(2L, "Tony2", 18),

new User(3L, "Tony3", 30),

new User(4L, "Tony4", 25),

new User(5L, "Tony5", 28)

);

@Override

@Cacheable(cacheNames = CachingConstant.CacheNames.CACHE_5_MINUTE)

public Result> page(int pageNo, int pageSize) {

String format = String.format("pageNo: %s, pageSize: %s", pageNo, pageSize);

System.out.println("从数据库中读数据。" + format);

int from = (pageNo - 1) * pageSize;

int to = Math.min(allUsers.size(), (pageNo) * pageSize);

List users = new ArrayList<>(allUsers.subList(from, to));

return new Result>().data(users);

}

}

Entity

User

package com.example.demo.entity;

import lombok.AllArgsConstructor;

import lombok.Data;

import lombok.NoArgsConstructor;

@Data

@AllArgsConstructor

// 必须要有无参构造函数。因为Redis反序列化为对象时要用到

@NoArgsConstructor

public class User {

private Long id;

private String userName;

private Integer age;

}

Result

package com.example.demo.entity;

import lombok.Data;

@Data

public class Result {

private boolean success = true;

private int code = 1000;

private String message;

private T data;

public Result() {

}

public Result(boolean success) {

this.success = success;

}

public Result success(boolean success) {

Result result = new Result<>(success);

if (success) {

result.code = 1000;

} else {

result.code = 1001;

}

return result;

}

public Result success() {

return success(true);

}

public Result failure() {

return success(false);

}

/**

* @param code {@link ResultCode#getCode()}

*/

public Result code(int code) {

this.code = code;

return this;

}

public Result message(String message) {

this.message = message;

return this;

}

public Result data(T data) {

this.data = data;

return this;

}

}

测试

第1次请求

http://localhost:8080/user/page?pageNo=1&pageSize=2

postman结果:

后端结果:

从数据库中读数据。pageNo: 1, pageSize: 2

Redis结果:

第2次请求(重复第1次)

访问:http://localhost:8080/user/page?pageNo=1&pageSize=2

postman结果:

后端结果:无输出

Redis结果:(不会更新TTL)

第3次请求(使用新参数)

访问:http://localhost:8080/user/page?pageNo=2&pageSize=2

postman结果:

后端结果:

从数据库中读数据。pageNo: 2, pageSize: 2

Redis结果:(方法返回值存入Redis,与其他的key的超时时间是分开的)

本次请求的结果:

之前请求的结果:

相关推荐

为什么网页没有声音 探究网页声音的原因及解决方案
网彩365平台下载

为什么网页没有声音 探究网页声音的原因及解决方案

📅 07-26 👁️ 660
喜马拉雅
365 体育投注

喜马拉雅

📅 07-08 👁️ 8188