在项目中对数据的访问往往都是直接访问数据库的方式,但如果对数据的访问量很大或者访问很频繁的话,将会对数据库来很大的压力,甚至造成数据库崩溃。为了解决这类问题大家都会在项目中加入缓存。还有一种情况也是比较适合增加缓存的,数据的更新周期较长,很长时间不会改变。
对于springboot 项目,spring官方提供的默认缓存方案只支持一种缓存策略,既不同的缓存过期时间一致。因项目实际需要不同缓存不同的过期时间,所以对多缓存策略进行了研究。这里采用redis作为缓存容器,至于原因不再累赘。详细过程如下
1. 安装redis数据库
2. 新建spring boot 2.1.7项目,pom文件如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>redis-cache</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redis-cache</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
3.启动类增加@EnableCaching注解。
4.自定义配置文件读取,代码如下:
@ConfigurationProperties(prefix = "caching")
@Component
@Data
public class MyCacheProperties {
private List<CacheSpec> specs;
@Data
static class CacheSpec {
/**
* 缓存过期时间
*/
private Integer timeout;
/**
* 缓存名称
*/
private String cacheName;
}
}
5. 自定义redis配置
@Configuration
@EnableCaching
@EnableConfigurationProperties(CacheProperties.class)
public class RedisConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, MyCacheProperties properties,
CacheProperties cacheProperties) {
RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(defaultCacheConfig(cacheProperties));
//自定义配置
List<MyCacheProperties.CacheSpec> cacheSpecs = properties.getSpecs();
if (!CollectionUtils.isEmpty(cacheSpecs)) {
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
cacheSpecs.forEach(cacheSpec -> {
RedisCacheConfiguration config = defaultCacheConfig(cacheProperties);
if (cacheSpec.getTimeout() != null && StringUtils.isNoneEmpty(cacheSpec.getCacheName())) {
config = config.entryTtl(Duration.ofSeconds(cacheSpec.getTimeout()));
cacheConfigurations.put(cacheSpec.getCacheName(), config);
}
});
builder = builder.withInitialCacheConfigurations(cacheConfigurations).transactionAware();
}
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
}
return builder.build();
}
/**
* 默认缓存配置,使用spring cache 默认配置 也可在配置文件中修改spring cache缓存策略
*/
private RedisCacheConfiguration defaultCacheConfig(CacheProperties cacheProperties) {
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()));
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;
}
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(keySerializer());
redisTemplate.setHashKeySerializer(keySerializer());
redisTemplate.setValueSerializer(valueSerializer());
redisTemplate.setHashValueSerializer(valueSerializer());
return redisTemplate;
}
private RedisSerializer<String> keySerializer() {
return new StringRedisSerializer();
}
/**
* 指定redis value的序列化方式为jackson
*
*/
private RedisSerializer<Object> valueSerializer() {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//设置输入时忽略JSON字符串中存在而Java对象实际没有的属性
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
JavaTimeModule module = new JavaTimeModule();
module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
module.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
module.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
objectMapper.registerModule(module);
objectMapper.registerModule(new Jdk8Module());
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
return jackson2JsonRedisSerializer;
}
/**
* 自定义缓存生成key
*/
@Bean
public KeyGenerator mKeyGenerator() {
return (Object target, Method method, Object... params) -> {
String key = target.getClass().getName().concat("|").concat(method.getName());
StringBuilder stringBuilder = new StringBuilder(key);
for (Object param : params) {
if (!param.getClass().getName().startsWith("org.springframework")) {
stringBuilder.append("|");
stringBuilder.append(param.toString());
}
}
return stringBuilder.toString();
};
}
}
6.新建测试controller
@RestController
public class CacheTestController {
@Cacheable(cacheNames = "test1", key = "method.name")
@GetMapping("/test1")
public Map<String, String> test1(HttpServletRequest request) {
return getCacheOrMap(request);
}
@Cacheable(cacheNames = "test2", keyGenerator = "mKeyGenerator")
@GetMapping("/test2")
public Map<String, String> test2(HttpServletRequest request) {
return getCacheOrMap(request);
}
private Map<String, String> getCacheOrMap(HttpServletRequest request) {
request.getMethod();
Map<String, String> result = new HashMap<>();
result.put("method", request.getMethod());
result.put("time", DateTimeFormatter.ISO_DATE_TIME.format(LocalDateTime.now()));
return result;
}
}
配置文件application.yml如下
server:
port: 8090
spring:
application:
name: redis-cache
redis:
host: 127.0.0.1
port: 6379
cache:
# 指定缓存为redis
type: redis
caching:
specs:
# test1 缓存过期时间60s
- cacheName: test1
timeout: 60
# test2 缓存过期时间10s
- cacheName: test2
至此功能已经实现,访问对应的接口即可进行测试。代码地址:
https://github.com/lbovery/redis-cache
其实相对来说没那么复杂,要实现这种非官方的自定义功能,建议大家多看源码,了解spirng boot的原理,这样以后不管遇到什么问题都能迎刃而解。