01 spring cache 搭配 redis 自定义缓存时间 和 ehcache
spring cache
缓存在jvm
搭配redis
pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 对象池,使用redis时必须引入 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 引入 jackson 对象json转换 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
yml
spring:
redis:
host: localhost
# 连接超时时间(记得添加单位,Duration)
timeout: 10000ms
# Redis默认情况下有16个分片,这里配置具体使用的分片
# database: 0
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制) 默认 8
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
max-wait: -1ms
# 连接池中的最大空闲连接 默认 8
max-idle: 8
# 连接池中的最小空闲连接 默认 0
min-idle: 0
cache:
# 一般来说是不用配置的,Spring Cache 会根据依赖的包自行装配
type: redis
logging:
level:
com.xkcoding: debug
config #后面带缓存时间单位秒
@Slf4j
public class RedisConfigCacheManager extends RedisCacheManager {
public RedisConfigCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
private static final RedisSerializationContext.SerializationPair<Object> DEFAULT_PAIR = RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer());
private static final CacheKeyPrefix DEFAULT_CACHE_KEY_PREFIX = cacheName -> cacheName + ":";
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
final int lastIndexOf = StringUtils.lastIndexOf(name, '#');
if (lastIndexOf > -1) {
final String ttl = StringUtils.substring(name, lastIndexOf + 1);
final Duration duration = Duration.ofSeconds(Long.parseLong(ttl));
cacheConfig = cacheConfig.entryTtl(duration);
//修改缓存key和value值的序列化方式
cacheConfig = cacheConfig.computePrefixWith(DEFAULT_CACHE_KEY_PREFIX)
.serializeValuesWith(DEFAULT_PAIR);
final String cacheName = StringUtils.substring(name, 0, lastIndexOf);
return super.createRedisCache(cacheName, cacheConfig);
} else {
//修改缓存key和value值的序列化方式
cacheConfig = cacheConfig.computePrefixWith(DEFAULT_CACHE_KEY_PREFIX)
.serializeValuesWith(DEFAULT_PAIR);
return super.createRedisCache(name, cacheConfig);
}
}
}
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
ObjectMapper om = new ObjectMapper();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
Object.class);
// 解决查询缓存转换异常的问题
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题)
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMillis(-1))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
return new RedisConfigCacheManager(cacheWriter, config);
}
}
service 层
@Service
@Slf4j
@CacheConfig(cacheNames={"demo:userServiceImpl"})
public class UserServiceImpl implements UserService {
/**
* 模拟数据库
*/
private static final Map<Long, User> DATABASES = Maps.newConcurrentMap();
static {
DATABASES.put(1L, new User(1L, "user1"));
DATABASES.put(2L, new User(2L, "user2"));
DATABASES.put(3L, new User(3L, "user3"));
}
/**
* 保存或修改用户
*
* @param user 用户对象
* @return 操作结果
*/
@CachePut(cacheNames = "demo:userServiceImpl:saveOrUpdate#10", key = "#user.id")
@Override
public User saveOrUpdate(User user) {
DATABASES.put(user.getId(), user);
log.info("保存用户【user】= {}", user);
return user;
}
/**
* 获取用户
*
* @param id key值
* @return 返回结果
*/
@Cacheable(key = "#id")
@Override
public User get(Long id) {
// 我们假设从数据库读取
log.info("查询用户【id】= {}", id);
return DATABASES.get(id);
}
/**
* 删除
*
* @param id key值
*/
@CacheEvict(key = "#id")
@Override
public void delete(Long id) {
DATABASES.remove(id);
log.info("删除用户【id】= {}", id);
}
/**
* 调用方法后清除所有缓存
*/
@CacheEvict(allEntries = true)
public void afterDeleteAll() {
DATABASES.clear();
}
/**
* 调用方法前清除所有缓存
*/
@CacheEvict(beforeInvocation = true)
public void beforeDeleteAll() {
DATABASES.clear();
}
}
test
@Slf4j
public class UserServiceTest extends SpringBootDemoCacheRedisApplicationTests {
@Autowired
private UserService userService;
/**
* 获取两次,查看日志验证缓存
*/
@Test
public void getTwice() {
// 模拟查询id为1的用户
User user1 = userService.get(1L);
log.debug("【user1】= {}", user1);
// 再次查询
User user2 = userService.get(1L);
log.debug("【user2】= {}", user2);
// 查看日志,只打印一次日志,证明缓存生效
}
/**
* 先存,再查询,查看日志验证缓存
*/
@Test
public void getAfterSave() {
userService.saveOrUpdate(new User(4L, "测试中文"));
User user = userService.get(4L);
log.debug("【user】= {}", user);
// 查看日志,只打印保存用户的日志,查询是未触发查询日志,因此缓存生效
}
/**
* 测试删除,查看redis是否存在缓存数据
*/
@Test
public void deleteUser() {
// 查询一次,使redis中存在缓存数据
userService.get(1L);
// 删除,查看redis是否存在缓存数据
userService.delete(1L);
}
@Test
public void afterDeleteAll() {
userService.afterDeleteAll();
}
@Test
public void beforeDeleteAll() {
userService.beforeDeleteAll();
}
}
搭配 enhance
pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
yml
spring:
cache:
type: ehcache
ehcache:
config: classpath:ehcache.xml
logging:
level:
com.xkcoding: debug
enhance.xml
<!-- ehcache配置 -->
<ehcache
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--缓存路径,用户目录下的base_ehcache目录-->
<diskStore path="user.home/base_ehcache"/>
<defaultCache
maxElementsInMemory="20000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"/>
<!--
缓存文件名:user,同样的可以配置多个缓存
maxElementsInMemory:内存中最多存储
eternal:外部存储
overflowToDisk:超出缓存到磁盘
diskPersistent:磁盘持久化
timeToLiveSeconds:缓存时间
diskExpiryThreadIntervalSeconds:磁盘过期时间
-->
<cache name="user"
maxElementsInMemory="20000"
eternal="true"
overflowToDisk="true"
diskPersistent="false"
timeToLiveSeconds="0"
diskExpiryThreadIntervalSeconds="120"/>
</ehcache>
service test 同redis
蓝天和白云是标配。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端