Springboot 中 Redis缓存使用 @Cacheable

3,593 阅读4分钟

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

今天看看Cacheable的问题,ES后续继续。。。

每次调用需要缓存功能的方法时,Spring 会检查指定参数的指定目标方法是否已经被调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。

一、Cacheable讲解

1、缓存介绍

Spring 从 3.1 开始就引入了对 Cache 的支持。定义了 org.springframework.cache.Cacheorg.springframework.cache.CacheManager 接口来统一不同的缓存技术。并支持使用 JCache(JSR-107)注解简化我们的开发。

其使用方法和原理都类似于 Spring 对事务管理的支持。Spring Cache 是作用在方法上的,其核心思想是,当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存在缓存中。

2、Cache 和 CacheManager 接口说明

  • Cache 接口包含缓存的各种操作集合,你操作缓存就是通过这个接口来操作的。
  • Cache 接口下 Spring 提供了各种 xxxCache 的实现,比如:RedisCache、EhCache、ConcurrentMapCache
  • CacheManager 定义了创建、配置、获取、管理和控制多个唯一命名的 Cache。这些 Cache 存在于 CacheManager 的上下文中。

@Cacheable实现自动缓存,属性为value、key和condition:

参数作用
value缓存的名称
key缓存的 key, SpEL 表达式
condition缓存的条件

key值常用属性说明

20200526175611599.png

@Cacheable 注解常用几个属性:

  • cacheNames/value :用来指定缓存组件的名字
  • key :缓存数据时使用的 key,可以用它来指定。默认是使用方法参数的值。(这个 key 你可以使用 spEL 表达式来编写)
  • keyGenerator :key 的生成器。 key 和 keyGenerator 二选一使用

eg--keyGenerator:

@Cacheable(cacheNames="test",keyGenerator = "myKeyGenerator")
public String test(String id){
    log.info("meizou redis");
    return id;
}
  • cacheManager :可以用来指定缓存管理器。从哪个缓存管理器里面获取缓存。
  • condition :可以用来指定符合条件的情况下才缓存
  • unless :否定缓存。当 unless 指定的条件为 true ,方法的返回值就不会被缓存。当然你也可以获取到结果进行判断。(通过 #result 获取方法结果)
  • sync :是否使用异步模式。

二、springboot配置

引入依赖:

 <!--这边用的是Redis缓存,所以加上这个依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

配置文件:

spring:
  cache:
    type: redis
  redis:
    database: 5
    host: 192.168.25.129
    port: 6379
    password: 123456
    redis: cache-null-values: false #不缓存null数据 
    time-to-live: 50000ms #超时时间 
    use-key-prefix: false #不使用前缀

配置类RedisConfig文件:

/**
 * 缓存Redis配置
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

	/**
	 * 使用Jackson2JsonRedisSerialize 替换默认序列化
	 */
	@Bean
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
		RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
		// 使用Jackson2JsonRedisSerialize 替换默认序列化
		Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(
				Object.class);
		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
		jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
		// 设置value的序列化规则和 key的序列化规则
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
		// hash参数序列化方式
		redisTemplate.setHashKeySerializer(new StringRedisSerializer());
		redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
		// 缓存支持回滚(事务管理)
		redisTemplate.setEnableTransactionSupport(true);
		redisTemplate.setConnectionFactory(redisConnectionFactory);
		redisTemplate.afterPropertiesSet();
		return redisTemplate;
	}

	// 配置事务管理器
	@Bean
	public PlatformTransactionManager transactionManager(DataSource dataSource) throws SQLException {
		return new DataSourceTransactionManager(dataSource);
	}

	/**
	 * 配置自动化缓存使用的序列化方式以及过期时间
	 */
	@Bean
	public RedisCacheConfiguration redisCacheConfiguration() {
		Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(
				Object.class);
		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
		jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
		RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
		configuration = configuration
				.serializeValuesWith(
						RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
				.entryTtl(Duration.ofDays(1));
		return configuration;
	}

	/**
	 * 缓存过期时间自定义配置
	 */
	@Bean
	public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
		// 设置CacheManager的值序列化方式为Jackson2JsonRedisSerializer,默认就是使用StringRedisSerializer序列化key,JdkSerializationRedisSerializer序列化value
		Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(
				Object.class);
		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
		jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
		// 配置value序列化方式为Jackson2JsonRedisSerializer,key序列化方式采用默认的StringRedisSerializer
		RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(
				RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
		// 每一类信息进行缓存配置
		Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
		// 用户信息
		redisCacheConfigurationMap.put(RedisConstant.USER, cacheConfiguration.entryTtl(Duration.ofDays(10))
				.disableCachingNullValues().prefixKeysWith(RedisConstant.USER));
	
		// 初始化一个RedisCacheWriter
		RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
		RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(
				RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
		// 设置默认超过期时间是1天(短时间的已经做了其他的处理,不会采用注解形式加入缓存)
		defaultCacheConfig.entryTtl(Duration.ofDays(1));
		// 初始化RedisCacheManager
		RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig,
				redisCacheConfigurationMap);
		return cacheManager;
	}
        
        // @Cacheable(cacheNames = {"emp"},keyGenerator = "myKeyGenerator")
        @Bean("myKeyGenerator")
    public KeyGenerator keyGenerator(){
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                return method.getName()+"["+ Arrays.asList(params).toString()+"]";
            }
        };
    }

}

添加注解:

@Cacheable(value = {"REDIS:GETSTRING3"}, key = "#tag + #name")
    public String getString4(String tag, String name) {
        return tag + " " + name;
    }

三、Cacheable失效原因

如果Spring boot和Redis集成,那么@cacheble可用,会把缓存数据写入Redis;可以通过redis可视化工具查看。

1、配置错误

2、EnableCaching注解未添加

3、配置失效时间太短,看不出来

4、Spring 缓存注解是基于Spring AOP切面,必须走代理才能生效,同类调用或者子类调用父类带有缓存注解的方法时属于内部调用,没有走代理;Cacheable注解的方法不能内部调用,内部调用不生效

注意事项:

@Cacheable基于Spring AOP代理,当方法被其他注解切入,当缓存命中,其他注解不能正常切入执行。