SpringBoot整合SpringCache

97 阅读4分钟

1、Spring Cache是什么

Spring Cache是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能,大大简化我们在业务中操作缓存的代码。

2、为什么要使用缓存

1、缓存是将数据直接存入内容中,读取效率比数据库的更高

2、缓存可以有效地降低数据库压力,为数据库减轻负担

3、为什么要使用SpringCache

我们在很多老项目中使用缓存步骤:

1、查寻缓存中是否存在数据,如果存在则直接返回结果

2、如果不存在则查询数据库,查询出结果后将结果存入缓存并返回结果

3、数据更新时,先更新数据库

4、然后更新缓存,或者直接删除缓存

此时我们会发现一个问题,所有我们需要使用缓存的地方都必须按照这个步骤去书写,这样就会出现很多逻辑上相似的代码。并且我们程序里面也需要显示的去调用第三方的缓存中间件的API,如此一来就大大的增加了我们项目和第三方中间件的耦合度。

有了Spring Cache一个这样的框架,它利用了AOP,实现了基于注解的缓存功能,并且进行了合理的抽象,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解,就能实现缓存功能了。而且Spring Cache也提供了很多默认的配置,开发者可以很快上手。

4、基本概念说明

注解说明
@Cacheable在方法执行前 spring 先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中
@CacheEvict将一条或多条数据从缓存中删除
@CachePut将方法的返回值放到缓存中
@Caching组合以上多个操作(点击注解看源码就知道了,组合注解))
@CacheConfig在类级别共享缓存的相同配置
@EnableCaching开启缓存注解功能

5、SpringBoot整合

pom依赖引入

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

yml配置文件

spring:
  #SpringCache配置
  cache:
    type: redis
    redis:
      #缓存过期时间为10分钟,单位为毫秒
      time-to-live: 600000
      #是否拼接KEY前缀
      use-key-prefix: true

创建配置类

package com.example.springbootdemo.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

/**
 * <p>
 *  redis配置类
 * </p>
 *
 * @author yurenwei
 * @since 2023/9/5
 */
@EnableConfigurationProperties(CacheProperties.class)//开启属性绑定配置的功能
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * redisTemplate相关配置
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 配置连接工厂
        template.setConnectionFactory(factory);

        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer jackson = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson.setObjectMapper(om);

        // 值采用json序列化
        template.setValueSerializer(jackson);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());

        // 设置hash key和value序列化模式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(jackson);
        template.afterPropertiesSet();

        return template;
    }


    /**
     * 监听redis的过期事件
     */
    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory factory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(factory);
        return container;
    }

    @Bean
    public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        config= config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
        //指定序列化-Jackson
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        //从所以配置中取出redis的配置
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        //将配置文件中所有的配置都生效(之间从源码里面拷 RedisCacheConfiguration)
        if (redisProperties.getTimeToLive() != null) {
            config = config.entryTtl(redisProperties.getTimeToLive());
        }
        if (redisProperties.getKeyPrefix() != null) {
            config = config.prefixKeysWith(redisProperties.getKeyPrefix());
        }
        if (!redisProperties.isCacheNullValues()) {
            config = config.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
            config = config.disableKeyPrefix();
        }

        return config;
    }

}

使用注解

新增用户

代码

/**
 * 添加用户
 *
 * @param params 新增参数
 */
@CachePut(value = "userCache",key = "#params.id")
public User add(User params){
    params.setId(IdUtil.getSnowflakeNextId());
    // 保存用户
    iUserService.save(params);
    log.info("保存用户成功!");

    SmsDTO sms = new SmsDTO()
            .setUserName(params.getUserName())
            .setPhone(params.getPhone())
            .setMsg("恭喜您注册成功!");
    // 调用短信事件发送短信
    applicationContext.publishEvent(new SmsEvent(this,"短信通知事件",sms));
    log.info("保存用户结束!");

    return params;
}

apifox测试

image.png

数据库数据

image.png

redis数据

image.png

修改

代码

/**
 * 修改用户
 *
 * @param params 修改参数
 */
@CachePut(value = "userCache",key = "#params.id")
public User update(User params) {
    User user = iUserService.getById(params.getId());
    BeanUtils.copyProperties(params,user);
    iUserService.updateById(user);

    return user;
}

apifox测试

image.png

数据库数据

image.png

redis数据

image.png

详情

代码

/**
 * 查询详情
 *
 * @param id 用户id
 * @return User
 */
@Cacheable(value = "userCache",key = "#id")
public User queryDetails(Long id) {
    return iUserService.getById(id);
}

apifox测试

image.png

数据库数据

image.png

redis数据

image.png

删除

代码

/**
 * 删除用户
 *
 * @param id 用户id
 */
@CacheEvict(value = "userCache",key = "#id")
public void delete(Long id) {
    iUserService.removeById(id);
}

apifox测试

image.png

数据库数据

image.png

redis数据

image.png

6、总结

通过上面整合案例发现,使用SpringCache的注解来缓存数据和清除缓存中数据,还是很方便的,可以减少一些冗余代码,至此SpringCache的简单使用结束了。