在項目中對數據的訪問往往都是直接訪問數據庫的方式,但如果對數據的訪問量很大或者訪問很頻繁的話,將會對數據庫來很大的壓力,甚至造成數據庫崩潰。爲了解決這類問題大家都會在項目中加入緩存。還有一種情況也是比較適合增加緩存的,數據的更新週期較長,很長時間不會改變。
對於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的原理,這樣以後不管遇到什麼問題都能迎刃而解。