SpringBoot使用Cache缓存,并使用Redis作为缓存管理器的示例

78 阅读5分钟

启用缓存

@SpringBootApplication  
@EnableCaching  
public class SpringCacheApp {  
  
    public static void main(String[] args) {  
        SpringApplication.run(Cache.class, args);  
    }  
}

默认情况下,@EnableCaching将注册一个ConcurrentMapCacheManager的Bean储存在jvm的内存中,ConcurrentMapCacheManager将值存储在ConcurrentHashMap的实例中

使用Redis作为缓存管理器

@Configuration  
@EnableCaching  
public class CacheConfig {  
  
    @Bean  
    public RedisConnectionFactory redisConnectionFactory() {  
        return new LettuceConnectionFactory();  
    }  
  
    @Bean  
    public CacheManager cacheManager() {  
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()  
            .disableCachingNullValues()  
            .serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));  
  
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory())  
            .cacheDefaults(redisCacheConfiguration)  
            .build();  
  
        return redisCacheManager;  
    }  
}

使用注解操作缓存

@Cacheable

将方法运行的结果进行缓存, 在缓存时效内再次调用该方法时不会调用方法本身,而是直接从缓存获取结果并返回给调用方.

属性名描述
value指定缓存的名称,Spring Cache 使用 CacheManager 管理多个缓存组件 Cache,此处的 Cache 组件就是根据名称进行区分的,它负责对缓存执行真正的 CRUD
key缓存数据时Key的值,默认是使用方法参数值,可以使用SpEL表达式计算Key值
keyGenerator缓存 Key 的生成策略,和 Key 属性互斥
cacheManager指定缓存管理器
cacheResolver作用和 cacheManager 属性一样,二者二选一
condition指定缓存的条件(满足什么条件才缓存),可用 SpEl 表达式(如: #id > 0, 表示当入参id大于0时才缓存)
unless是否缓存,即满足 unless 指定的条件时,方法的结果不进行缓存,使用 unless 时可以在调用的方法获取到结果之后再进行判断(如 null == #result,表示结果为 null 时不缓存)
sync是否使用异步模式进行缓存,默认是false

示例: 缓存 getById() 方法的结果

    @Service
    public class MyService{
        
        @Resource  
        private MyRepository repository;  
  
        @Cacheable(value = "myCache", key = "#id")  
        public MyEntity getById(Long id) {  
            return repository.findById(id).orElse(null);  
        }
    }

@CachePut

@CachePut用于更新缓存数据,相当于缓存使用的是写模式中的双写模式 示例

@Service  
public class MyService {  
  
    @Resource  
    private MyRepository repository;  
  
    @CachePut(value = "myCache", key = "#entity.id")  
    public void saveEntity(MyEntity entity) {  
        repository.save(entity);  
    }  
}

@CacheEvict

删除缓存数据,相当于缓存使用的是写模式中的失效模式

属性名描述
value/cacheNames缓存的名称
key缓存的键
allEntries是否根据缓存名称清空所有缓存数据,默认值为 false ,当值指定为 true 时,Spring Cache 将忽略注解上指定的 key 属性
beforeInvovation是否在执行方法前清空缓存,默认为 false

示例

@Service  
public class MyService {  
  
    @Resource  
    private MyRepository repository;  
  
     @CacheEvict(value = "myCache", key = "#id")  
    public void deleteEntityById(Long id) {  
        repository.deleteById(id);  
    }  
}
    

@Caching

用于指定多个 Spring Cache 相关的注解

属性名描述
cacheable用于指定 @Cacheable 注解
put用于指定 @CachePut 注解
evict用于指定 @CacheEvict 注解

当调用save方法时,Spring会根据 @CacheEvict 注解先从 thirdCache 缓存中移除数据。然后,Spring 将执行该方法并将结果保存到数据库或外部 API。

方法执行后,Spring 会根据@CachePut注解将结果添加到 myCache 和 secondCache 缓存中。Spring 还将根据@Cacheable 注解检查结果是否已缓存在 fourthCache 和 fifthCache 缓存中。如果结果尚未缓存,Spring 会将结果缓存在适当的缓存中。如果结果已经被缓存,Spring 将返回缓存的结果,而不是再次执行该方法。

@Service  
public class MyService {  
  
    @Caching(  
        put = {  
            @CachePut(value = "myCache", key = "#result.id"),  
            @CachePut(value = secondCache", key = "#result.id")
        },  
        evict = {  
            @CacheEvict(value = "thirdCache", key = "#id")  
        },  
        cacheable = {  
            @Cacheable(value = "fourthCache", key = "#id"),  
            @Cacheable(value = "fifthCache", key = "#result.id")  
        }  
    )  
    public MyEntity save(Long id, String name) {  
        MyEntity entity = new MyEntity(id, name);  
        return entity;  
    }  
  
}

@CacheConfig

将一些缓存配置简化到类级别的一个地方,这样我们就不必多次声明相关值

@CacheConfig(cacheNames={"myCache"})  
@Service  
public class MyService {  
  
    @Resource  
    private MyRepository repository;  
  
    @Cacheable(key = "#id")  
    public MyEntity getById(Long id) {  
        return repository.findById(id).orElse(null);  
    }  
  
    @CachePut(key = "#entity.id")  
    public void save(MyEntity entity) {  
        repository.save(entity);  
    }  
  
    @CacheEvict(key = "#id")  
    public void deleteById(Long id) {  
        repository.deleteById(id);  
    }  
}

Condition & Unless

  • condition: 满足条件时缓存, 可用SpEL表达式(如 #id>0,表示当入参 id 大于 0 时才缓存)
  • unless: 满足条件时不缓存, 可在调用的方法获取到结果之后再进行判断(如 #result == null,表示如果结果为 null 时不缓存)
// id > 10 缓存
@CachePut(key = "#entity.id", condition="#entity.id > 10")  
public void save(MyEntity entity) {  
 repository.save(entity);  
}  
  
// 结果为 null 时不缓存
@CachePut(key = "#id", condition="#result == null")  
public void save(MyEntity entity) {  
 repository.save(entity);  
}

清理全部缓存 (allEntries、beforeInvocation)

  • allEntries 方法调用后清理
  • beforeInvocation 方法调用前清理
//方法调用完成之后,清理所有缓存  
@CacheEvict(value="myCache",allEntries=true)  
public void delectAll() {  
    repository.deleteAll();  
}  
  
//方法调用之前,清除所有缓存  
@CacheEvict(value="myCache",beforeInvocation=true)  
public void delectAll() {  
    repository.deleteAll();  
}

SpEL表达式的语法

类型运算符
算术<, >, <=, >=, ==, !=, lt, gt, le, ge, eq, ne
关系+, -, *, /, %, ^
逻辑&&, ||, !, and, or, not, between, instanceof
条件?:(ternary), ?:(elvis)
正则表达式matches
其它?., ?[...], ![...], ^[...], $[...]

Spring Cache 可用的变量

类型位置运算符示例
methodName根对象要调用的方法的名称#root.methodName
method根对象正在调用的方法#root.method.name
target根对象正在调用的目标对象#root.target
targetClass根对象要调用的目标类#root.targetClass
args根对象用于调用目标的参数(作为数组)#root.args[0]
caches根对象正在调用的方法使用的缓存队列(如 @Cacheable(value={"cache1","cache2"}),则有两个缓存)#root.caches[0].name
argument name评估背景方法参数名,可以直接使用 #参数名 ,也可以使用 #p0 或 #a0 的形式, 0 代表参数的索引#a0, #p0
result评估背景方法执行后的返回值,仅当方法执行之后的判断有效, 如unless、 cache put、cache evict (当 beforeInvocation = false)的表达式#root

使用场景

  • 只缓存经常读取的数据,很少或从不访问的的缓存数据会占用内存资源,从而导致性能问题
  • 根据程序特定需求选择合适的缓存提供程序(Ehcache, Hazelcast 和 Redis)和策略
  • 使用缓存时注意线程安全问题,并发情况下可能存在缓存不一致问题,必要时使用适当的同步机制
  • 使用适当的缓存键设计。缓存键对于每个数据项都应该是唯一的,并且应该考虑可能影响缓存数据的任何相关参数,例如用户 ID、时间或位置
  • 读多写少、即时性与一致性要求不高的数据 可以使用 Spring Cache
  • 读多写多、即时性与一致性要求非常高的数据, 不能使用 Spring Cache, 建议考虑特殊的设计(例如使用 Cancal 中间件)