在Java的世界里,当我们需要缓存一个对象时,我们会想到用Guava、Ehcache、Memcache、Redis ,这些缓存框架各有优劣,我今天想给大家介绍一个Spring世界里使用的缓存框架。理解SpringCache,我们可以把上述guava\ehcache等当成mysql-jdbc-driver/oracle-jdbc-driver ,而SpringCache就相当于是jdbc标准。
注解
SpringCache里面通过使用如下几个注解来实现各种缓存功能:
- @EnableCaching 开启缓存功能
- @CacheConfig 通用缓存设置,Class维度
- @Cacheable 定义方法有缓存功能
- @CachePut 触发更新缓存
- @CacheEvict 触发删除缓存
- @Caching 组合定义多种缓存功能一次性定义:Cacheable、CachePut、CacheEvict
使用缓存前的准备工作
环境依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<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>ai.grakn</groupId>
<artifactId>redis-mock</artifactId>
<version>0.1.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.61</version>
</dependency>
</dependencies>
开启缓存
SpringBoot开启缓存
@EnableCaching
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
基于Redis实现分布式缓存
@EnableCaching
@Configuration
public class CacheConfig {
@Configuration
public class CacheConfig {
@Primary
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
//缓存配置对象
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
redisCacheConfiguration = redisCacheConfiguration.entryTtl(Duration.ofMinutes(30L)) //设置缓存的默认超时时间:30分钟
.disableCachingNullValues() //如果是空值,不缓存
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer())) //设置key序列化器
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer((valueSerializer()))); //设置value序列化器
return RedisCacheManager
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(redisCacheConfiguration).build();
}
private RedisSerializer<String> keySerializer() {
return new StringRedisSerializer();
}
/**
* 自定义值序列化-json序列化
* @return
*/
private RedisSerializer<Object> valueSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
}
}
一种通用自定义key生成方式
@Slf4j
@Configuration("keyGenerator")
public class CacheKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder key = new StringBuilder();
String simpleName = target.getClass().getSimpleName();
//如果是代理类去掉后缀
simpleName=simpleName.split("\\$\\$")[0];
key.append(simpleName).append(":").append(method.getName()).append(":");
String finalKey = key.toString();
if (params.length == 0) {
return finalKey;
}
for (int i = 0; i < params.length; i++) {
Object param = params[i];
if (param == null) {
del(key);
} else if (ClassUtils.isPrimitiveArray(param.getClass())) {
int length = Array.getLength(param);
for (int j = 0; j < length; j++) {
key.append(Array.get(param, j));
key.append(',');
}
} else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) || param instanceof String) {
key.append(param);
} else {
key.append(param.toString());
}
key.append('-');
}
del(key);
log.debug("final key:{}",key);
return finalKey;
}
private StringBuilder del(StringBuilder stringBuilder) {
if (stringBuilder.toString().endsWith("-")) {
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
}
return stringBuilder;
}
}
查询缓存
/**
* result 不为空时缓存
* @param id
* @return
*/
@Cacheable(value = "user",key = "#id" ,unless="#result == null")
public User findById(Integer id){
System.out.println(String.format("id[%s]未命中缓存", id));
return kvStore.get(id);
}
更新缓存
/**
* 更新成功时更新缓存
* @param id
* @param userDto
* @return
*/
@CachePut(value = "user",key = "#id",unless="#result == null")
public User upateUser(Integer id,UserDto userDto){
if(kvStore.containsKey(id)){
User user = User.builder().build();
BeanUtils.copyProperties(userDto, user);
kvStore.put(id, user);
return user;
}else{
return null;
}
}
删除缓存
/**
* 移除
* @param id
* @return
*/
@CacheEvict(value = "user",key = "#id" )
public User removeById(Integer id){
return kvStore.remove(id);
}
测试运行
package com.example.rediscache;
import ai.grakn.redismock.RedisServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.RedisTemplate;
import java.io.IOException;
@SpringBootApplication
@EnableCaching
public class RedisCacheApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(RedisCacheApplication.class, args);
}
@Autowired
UserService userService;
@Override
public void run(String... args) throws Exception {
mockRedisServerStart();
System.out.println(userService.findById(1));
System.out.println("插入id:1 name=张三");
userService.insertUser(User.builder().id(1).name("张三").build());
System.out.println("查询id:1 "+userService.findById(1));
System.out.println("更新id:1 name=张三(新)");
userService.upateUser(1, UserDto.builder().name("张三(新)").build());
System.out.println("查询id:1 "+userService.findById(1));
userService.removeById(1);
System.out.println("移除id:1");
System.out.println("查询id:1 "+userService.findById(1));
}
private void mockRedisServerStart() {
RedisServer redisServer = null;
try {
redisServer = RedisServer.newRedisServer(6379);
redisServer.start();
int bindPort = redisServer.getBindPort();
String host = redisServer.getHost();
System.out.println(String.format("redis server start on host:%s port %s", host,bindPort));
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果
image.png使用SpringCache缓存Java方法示例
注意事项
- 1、如果出现反序列化异常。可能的原因缓存对象Class发生了变化,我们通常会将缓存删掉,所以我们可以在反序列化失败时捕捉异常,并将产生异常的缓存key删除掉.
- 2、如果KeyGenerator与类的包路径有关,调整包路径时可能会造成key无法命中缓存
网友评论