项目缓存提效神器-SpringCache缓存框架介绍

DBC 698 0

SpringCache缓存框架介绍

  • SpringCache简介
    • 文档:https://spring.io/guides/gs/caching/
    • 自Spring 3.1起,提供了类似于@Transactional注解事务的注解Cache支持,且提供了Cache抽象
    • 提供基本的Cache抽象,方便切换各种底层Cache
    • 只需要更少的代码就可以完成业务数据的缓存
    • 提供事务回滚时也自动回滚缓存,支持比较复杂的缓存逻辑
    • 核心
      • 一个是Cache接口,缓存操作的API;
      • 一个是CacheManager管理各类缓存,有多个缓存框架的实现
  • 讲课方式
    • 很多其他地方的教程,使用单元测试的方式教SpringCache使用
    • 多个同学反馈看了也不懂在SpringBoot或者Cloud微服务项目中使用
    • 本章内容采用案例实战的方式,教大家怎么使用,工作中开发项目直接采用即可
    • 学会触类旁通,举一反三
  • 使用
    • 项目中引入starter
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>
配置设置

Redis必不可少

SpringBoot2.x实战整合Redis客户端+单元测试

2年前 (2021-06-16) 0
项目缓存提效神器-SpringCache缓存框架介绍插图
spring:
  #指定缓存的类型
  cache:
    type: redis
启动类添加
@EnableCaching
需要一个自定义CacheManager配置
package net.xdclass.xdclassredis.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.time.Duration;



@Configuration
public class AppConfiguration {


    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {

        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // 使用Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        // 设置key和value的序列化规则
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);

        // 设置hashKey和hashValue的序列化规则
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        // 设置支持事物
        //redisTemplate.setEnableTransactionSupport(true);

        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }


    /**
     * 新的分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }


    /**
     * 1分钟过期
     *
     * @param connectionFactory
     * @return
     */
    @Bean
    public RedisCacheManager cacheManager1Minute(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration config = instanceConfig(60L);
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
    }


    /**
     * 默认是1小时
     *
     * @param connectionFactory
     * @return
     */
    @Bean
    @Primary
    public RedisCacheManager cacheManager1Hour(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration config = instanceConfig(3600L);
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
    }

    /**
     * 1天过期
     *
     * @param connectionFactory
     * @return
     */
    @Bean
    public RedisCacheManager cacheManager1Day(RedisConnectionFactory connectionFactory) {

        RedisCacheConfiguration config = instanceConfig(3600 * 24L);
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
    }

    private RedisCacheConfiguration instanceConfig(Long ttl) {

        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.registerModule(new JavaTimeModule());
        // 去掉各种@JsonSerialize注解的解析
        objectMapper.configure(MapperFeature.USE_ANNOTATIONS, false);
        // 只针对非空的值进行序列化
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        // 将类型序列化到属性json字符串中
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(ttl))
                //.disableCachingNullValues()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));

    }


    /**
     * 自定义缓存key规则
     * @return
     */
    @Bean
    public KeyGenerator springCacheCustomKeyGenerator() {

        return new KeyGenerator() {
            @Override
            public Object generate(Object o, Method method, Object... objects) {
                String key = o.getClass().getSimpleName() + "_" + method.getName() + "_" + StringUtils.arrayToDelimitedString(objects, "_");
                System.out.println(key);
                return key;
            }
        };

    }


}
举两个小例子看看
    @Override
    @Cacheable(value = {"product"},key = "#root.args[0]",cacheManager = "cacheManager1Minute")
    //@Cacheable(value = {"product"}, keyGenerator = "springCacheCustomKeyGenerator",cacheManager = "cacheManager1Minute")
    public ProductDO findById(int id) {
        return productMapper.selectById(id);
    }

    @Override
    //@Cacheable(value = {"product_page"},key = "#root.methodName+'_'+#page+'_'+#size")
    @Cacheable(value = {"product_page"},keyGenerator = "springCacheCustomKeyGenerator")
    public Map<String, Object> page(int page, int size) {

        Page pageInfo = new Page<>(page,size);

        IPage<ProductDO> iPage = productMapper.selectPage(pageInfo,null);

        Map<String,Object> pageMap = new HashMap<>(3);

        pageMap.put("total_record",iPage.getTotal());
        pageMap.put("total_page",iPage.getPages());
        pageMap.put("current_data",iPage.getRecords());

        return pageMap;
    }

解析

value = {"product"}

定义缓存名字

#root.args[0]

获取第一个参数

key = "#root.methodName+'_'+#page+'_'+#size"

按照参数名字来进行区分

keyGenerator = "springCacheCustomKeyGenerator"

自定义参数规则,具体如下图。
项目缓存提效神器-SpringCache缓存框架介绍插图2

如果我们需要更新缓存,那么我们看下面的例子

CachePut实战

CachePut

  • 根据方法的请求参数对其结果进行缓存,每次都会触发真实方法的调用
  • value 缓存名称,可以有多个
  • key 缓存的key规则,可以用springEL表达式,默认是方法参数组合
  • condition 缓存条件,使用springEL编写,返回true才缓存
    @Override
    @CachePut(value = {"product"},key="#productDO.id", cacheManager = "cacheManager1Minute")
    public ProductDO updateById(ProductDO productDO) {
        productMapper.updateById(productDO);
        return productDO;
    }
删除缓存的情况
CacheEvict实战

CacheEvict

  • 从缓存中移除相应数据, 触发缓存删除的操作
  • value 缓存名称,可以有多个
  • key 缓存的key规则,可以用springEL表达式,默认是方法参数组合
  • beforeInvocation = false
    • 缓存的清除是否在方法之前执行 ,默认代表缓存清除操作是在方法执行之后执行;
    • 如果出现异常缓存就不会清除
  • beforeInvocation = true
    • 代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
    @Override
    @CacheEvict(value = {"product"},key = "#root.args[0]")
    public int delById(int id) {
        return productMapper.deleteById(id);
    }
多个注解
Caching实战

Caching

  • 组合多个Cache注解使用
  • 允许在同一方法上使用多个嵌套的@Cacheable、@CachePut和@CacheEvict注释
    @Override
    //@Cacheable(value = {"product"},key = "#root.args[0]",cacheManager = "cacheManager1Minute")
    //@Cacheable(value = {"product"}, keyGenerator = "springCacheCustomKeyGenerator",cacheManager = "cacheManager1Minute")

    @Caching(
            cacheable = {
                    @Cacheable(value = {"product"},key = "#root.args[0]"),
                    @Cacheable(value = {"product"},key = "'xdclass_'+#root.args[0]")
            },
            put = {
                    @CachePut(value = {"product_test"},key="#id", cacheManager = "cacheManager1Minute")
            }
    )
    public ProductDO findById(int id) {
        return productMapper.selectById(id);
    }

发表评论 取消回复
表情 图片 链接 代码

分享