likes
comments
collection
share

常规CRUD快速接入Spring Cache

作者站长头像
站长
· 阅读数 18

Spring Cache注解

注解解释
Cacheable查询缓存
CachePut更新缓存
CacheEvict删除缓存
Caching聚合缓存策略
CacheConfig缓存配置

CRUD改造

实体的增删改查进行缓存注解的标记

package com.wuhanpe.task.service;

import com.wuhanpe.task.entity.TaskBase;
import com.wuhanpe.task.repository.TaskRepository;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Slf4j
@CacheConfig(cacheNames = {"task"})
public class TaskCacheService {

    @NonNull
    private TaskRepository taskRepository;

    @CachePut(key = "#taskBase.id")
    public int insertTask(TaskBase taskBase) {

        return taskRepository.insert(taskBase);
    }

    @CachePut(key = "#taskBase.id")
    public int updateTask(TaskBase taskBase) {

        return taskRepository.updateById(taskBase);
    }

    @CacheEvict(key = "#id")
    public int deleteTask(long id) {

        return taskRepository.deleteById(id);
    }

    @Cacheable(key = "#taskBase.id")
    public TaskBase selectTask(TaskBase taskBase) {

        return taskRepository.selectById(taskBase.getId());
    }

}

发起请求

第一笔

走数据库的请求 常规CRUD快速接入Spring Cache 耗时是625ms

日志

常规CRUD快速接入Spring Cache

第二笔

第二笔及后续的几笔走了缓存 常规CRUD快速接入Spring Cache 响应时间大幅降低,同时没有新的日志产生

缓存容器

上述采用的是系统内存做为默认缓存。我们可以将缓存改为redis,可以方便其他服务器进行内存共享。

Redis缓存

添加依赖

implementation('org.springframework.boot:spring-boot-starter-data-redis')
implementation('org.apache.commons:commons-pool2:2.11.1')

配置cacheManager

@Bean("redisCacheManager")
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {

    return RedisCacheManager.create(redisConnectionFactory);
}

配置文件

spring
  redis:
      database: 4
      host: redis.whpe
      port: 6379
      password: Whpe!@#123
      lettuce: 
        pool:
          #连接池最大连接数(使用负值没有限制)默认8 
          max-active: 64
          #连接池最大阻塞等待时间(使用负值没有限制)默认-1
          max-wait: -1
          #连接池最大空闲连接(使用负值没有限制)默认8
          max-idle: 8
          #连接池最小空闲连接(使用负值没有限制)默认0
          min-idle: 0

缓存类上的使用

@CacheConfig(cacheNames = {"task"}, cacheManager = "redisCacheManager")

引用上述定义的bean

redis显示

常规CRUD快速接入Spring Cache 可以看到缓存已经写入,并生效。实际体验下来会比内存的慢个1~2ms左右,毕竟发生了一次网络io查询。然而发现写入到redis中的是对象格式,无法直观的看数据内容,不方便调试,因此需要进行redis序列化器的配置。 😀配置 :

@Bean("redisCacheManager")
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {


    RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();

    redisCacheConfiguration = redisCacheConfiguration.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

    RedisCacheManager redisCacheManager = new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), redisCacheConfiguration);
    return redisCacheManager;
}

配置了value序列化采用jackson进行序列化。redis中显示: 常规CRUD快速接入Spring Cache

CaffineCache缓存

添加依赖

implementation('com.github.ben-manes.caffeine:caffeine:2.8.8')

配置cacheManager

@Primary
@Bean("caffeineCacheManager")
public CacheManager cacheManager() {

    CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
    caffeineCacheManager.setCacheSpecification("");

    return caffeineCacheManager;
}

配置文件

咖啡因缓存框架是基于内存的处理,提供了读过期和写过期的配置,当一定时间过后,缓存失效,读取数据库中的数据写入到缓存中,并进行返回。

缓存类上的使用

@CacheConfig(cacheNames = {"task"}, cacheManager = "caffeineCacheManager")

只需要修改cacheManager为上面配置的cacheManager即可

联合使用

调用更新缓存,然后调用查询缓存,看是否发生了变化 😅 注意点:更新缓存的返回值需要重新从数据库中获取最新的值。

@CachePut(key = "#taskBase.id")
public TaskBase updateTask(TaskBase taskBase) {

    taskRepository.updateById(taskBase);
    return taskRepository.selectById(taskBase.getId());
}
  1. 插入,查询联合使用
@CachePut(key = "#taskBase.id")
public TaskBase insertTask(TaskBase taskBase) {

    taskRepository.insert(taskBase);
    return taskBase;
}

也是需要返回TaskBase对象。实际上都是将返回值设置到缓存中,有一个注意点,缓存返回的时候需要保证自增id是设置了,防止缓存中的id为空。

@TableId(type = IdType.AUTO)
private Long id;
  1. 删除,查询联合使用
@CacheEvict(key = "#id")
public int deleteTask(long id) {

    return taskRepository.deleteById(id);
}

删除后,查询直接返回null。达到要求

idea支持

idea已经对cache缓存单独提供了图标支持,点击CacheConfig和其他的Cache注解时可以提示缓存影响的方法 常规CRUD快速接入Spring Cache

缓存过期

咖啡因缓存框架是可以设置过期时间,到了过期时间,重新从数据库获取缓存加载到内存中。

  • 写入过期

常规CRUD快速接入Spring Cache 连续发起请求,10s钟后,缓存失效,从数据库中重新获取,写入到缓存 常规CRUD快速接入Spring Cache

  • 读过期

常规CRUD快速接入Spring Cache 连续写入数据,只会在最后一次访问数据过后5S钟,数据过期。访问数据的间隔一直没有大于5S,数据会一直在缓存中。

  • 组合过期规则

组合规则会同时生效,各自独立的刷新缓存 常规CRUD快速接入Spring Cache 可以看到时间是两个缓存过期策略都起了作用。

  • refreshAfterWrite

设置写入后什么时候进行刷新,需要设置LoadingCache

Caused by: java.lang.IllegalStateException: refreshAfterWrite requires a LoadingCache

手动实现缓存加载器

private class CustomCacheLoader implements CacheLoader {

    @Nullable
    @Override
    public Object load(@NotNull Object key) throws Exception {
        return null;
    }

    @Nullable
    @Override
    public Object reload(@NotNull Object key, @NotNull Object oldValue) throws Exception {
        TaskBase taskBase = (TaskBase) oldValue;
        taskBase.setTaskName("121212121212");
        return taskBase;
    }
}

覆盖reload方法,进行返回对象的修改,同理我们也可以改到从其他的方式上去获取对象。 😀配置: 常规CRUD快速接入Spring Cache 有一个点要注意,设置缓存加载器要早于配置缓存名称,否则会启动失败。

缓存参数

initialCapacityint初始容量
maximumSizeLong最大容量
maximumWeightLong缓存的最大权重
expireAfterAccessDuration写和读过期
expireAfterWriteDuration写过期
refreshAfterWriteDuration写后自动刷新,需要配合CacheLoader使用

支持哪些缓存框架

redis
caffeine
guava
ehcache
jcache
ConcurrentMapCache(默认)

多级缓存

使用CompositeCacheManager组合两个缓存manager即可