@Cacheable的使用

一、配置

1.配置redis

默认是有配置redis的情况会使用redis作为缓存。

2.配置Cache

@Configuration
// 使用redis作为cache的缓存,前提是要配置redis
@ConditionalOnBean(RedisConfiguration.class)
public class CacheConfiguration {

    @Resource
    private RedisConnectionFactory factory;

    @Resource
    private RedisCacheProperties redisCacheProperties;

    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(method.getName());
            for (Object obj : params) {
                sb.append(obj.toString());
            }
            return sb.toString();
        };
    }

    @Bean
    public CacheResolver cacheResolver() {
        return new SimpleCacheResolver(cacheManager());
    }

    @Bean
    public CacheErrorHandler errorHandler() {
        // 用于捕获从Cache中进行CRUD时的异常的回调处理器。
        return new SimpleCacheErrorHandler();
    }

    @Bean
    public CacheManager cacheManager() {
        final Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        // 解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        //POJO无public的属性或方法时,不报错
        om.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        // null值字段不显示
        om.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 序列化JSON串时,在值上打印出对象类型
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
        // 解决jackson2无法反序列化LocalDateTime的问题
        om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // 配置序列化(解决乱码的问题)
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                // 过期时间
                .entryTtl(Duration.ofMillis(redisCacheProperties.getExpireDefaultMillis()))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();

        final RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(factory)
                .cacheDefaults(config);

        // 为每个key配置不同的过期时间
        final Map<String, Long> expireMap = redisCacheProperties.getExpireMapMillis();
        if (CollUtil.isNotEmpty(expireMap)) {
            final ConcurrentHashMap<String, RedisCacheConfiguration> configMap = new ConcurrentHashMap<>();
            final Set<String> cacheNames = expireMap.keySet();
            expireMap.entrySet().forEach(e ->
                    configMap.put(e.getKey(), config.entryTtl(Duration.ofMillis(e.getValue())))
            );
            builder.initialCacheNames(cacheNames)
                    .withInitialCacheConfigurations(configMap);
        }

        return builder.build();
    }
}

需要用到的配置类:

@Getter
@Setter
@NoArgsConstructor
@Component
@ConfigurationProperties(prefix = "redis-cache")
public class RedisCacheProperties {

    /**
     * 缓存默认过期时间,单位毫秒
     */
    private long expireDefaultMillis = 86400000;

    /**
     * 缓存针对不同的key设置不同过期时间,单位毫秒
     */
    private Map<String, Long> expireMapMillis;
}

yaml对应的配置:

##cache缓存设置
redis-cache:
  # 默认超时时间,单位毫秒
  expire-default-millis: 86400000
  # 自定义超时时间map,单位毫秒
  expire-map-millis:
    mapTestCache: 6000

3.将Cache配置放入starter

将这些配置放入新的maven工程里面(即starter工程),如果需要此功能只需要引入这个starter工程即可。

starter工程中增加如下两个文件:

  • 增加注解定义,在项目的启动类中增加此注解即可扫描到starter工程的包,并注入到spring:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(RootConfiguration.class)
public @interface EnableXXConfiguration {
}
  • 增加类即上面的@Import(RootConfiguration.class),并增加包的扫描位置:
@Configuration
@ComponentScan("com.xx.xxx")
public class RootConfiguration {
}

二、使用

使用前启动类需要加注解:@EnableCaching

使用注解:

  • @Cacheable:触发缓存写入。
  • @CacheEvict:触发缓存清除。
  • @CachePut:更新缓存(不会影响到方法的运行)。
  • @Caching:重新组合要应用于方法的多个缓存操作。
  • @CacheConfig:设置类级别上共享的一些常见缓存设置。

示例代码:

@Service
@Slf4j
public class CacheTestService {

    @Cacheable(cacheNames = "mapTestCache", key = "#mapId")
    public MapEntity findMapEntity(String mapId) {
        log.info("进入数据库查询。。。");
        return new MapEntity()
                .setMapName("测试地图cache")
                .setMapDescription("")
                .setDel(1)
                .setUpdateTime(System.currentTimeMillis());
    }

    @CachePut(cacheNames = "mapTestCache", key = "#map.mapId")
    public MapEntity updateEntity(MapEntity map) {
        log.info("进入数据库更新。。。");
        return map;
    }

    @CacheEvict(cacheNames = "mapTestCache", key = "#mapId")
    public void deleteEntity(String mapId) {
        log.info("进入数据库删除。。。");
    }

}

测试:

    @Test
    public void testCacheFind() {
        final MapEntity mapEntity = cacheTestService.findMapEntity("110");
        log.info("查出来数据:{}", JSONUtil.toJsonStr(mapEntity));
        Assert.assertTrue(true);
    }

    @Test
    public void testCacheUpdate() {
        final MapEntity mapEntity = new MapEntity().setMapName("test更新。。").setMapId("110");
        cacheTestService.updateEntity(mapEntity);
        Assert.assertTrue(true);
    }

    @Test
    public void testCacheDelete() {
        cacheTestService.deleteEntity("110");
        Assert.assertTrue(true);
    }

三、Spring-Cache的不足

1)读模式

  • 缓存穿透:查询一个null数据。解决:缓存空数据:cache-null-values=true;
  • 缓存击穿:大量并发进来同时查询一个正好过期的数据。解决:加锁,sync=true,只有@Cacheable有;
  • 缓存雪崩:大量的key同时过期。解决:加随机时间。

2)写模式

如何保证缓存数据库一致性:

  • 读写加锁,有序进行;(写少的情况,不然一直在等待)
  • 引入canal,感知到MySQL的更新去更新数据库;
  • 读多写多,直接去数据库查询就行;

总结

  • 常规数据(读多写少,即时性,一致性要求不高的数据):完全可以使用Spring-Cache;写模式(只要缓存的数据有过期时间就足够)
  • 特殊数据:特殊设计;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值