SpringBoot整合Redis做缓存-自定义缓存序列化方式,防止缓存数据乱码问题

837 阅读3分钟

首先是添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>${spring-boot.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
    <version>${spring-boot.version}</version>
</dependency>

我这里用的SpringBoot版本是: 2.2.5.RELEASE

一、jedis与lettuce区别

  • 1、Jedis: 在实现上是直连 redis server,多线程环境下非线程安全,除非使用连接池,为每个 redis实例增加 物理连接。
  • 2、Lettuce: 是 一种可伸缩,线程安全,完全非阻塞的Redis客户端,多个线程可以共享一个RedisConnection,它利用Netty NIO 框架来高效地管理多个连接,从而提供了异步和同步数据访问方式,用于构建非阻塞的反应性应用程序。

我这里项目的配置文件使用的是 Lettuce,配置文件内容:


spring:
  datasource:
    url: jdbc:mysql://192.168.104.64:3306/spring_boot_plus?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: huauN2021

  # Redis配置
  redis:
    database: 0
    host: 192.168.104.102
    password: huauN@2021
    port: 6379
    timeout: 6000 # 连接超时时长(毫秒)
    lettuce:
      pool:
        max-active: 1024 # 连接池最大连接数(默认为8,-1表示无限制 如果pool已经分配了超过max_active个jedis实例,则此时pool为耗尽)
        max-wait: 10000 #最大等待连接时间,单位毫秒 默认为-1,表示永不超时,超时会抛出JedisConnectionException
        max-idle: 10
        min-idle: 5

首先是找下redis的配置类,如果没有则新增改配置类:


package gc.cnnvd.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.StringRedisSerializer;

/**
 * <p>
 * Redis Template 配置
 * </p>
 *
 * @author linmengmeng
 * @date 2021-07-21
 */
@Configuration
public class RedisTemplateConfig {


    /**
     * redisTemplate配置
     * @param factory
     * @return
     *
     *
     * 自定义RedisTemplate的原因:
     * 1、修改泛型方式为<String,Object>,避免繁琐的类型转换
     * 2、将value的序列化方式更改为Jackson2JsonRedisSerializer,因为底层的RedisSerializer序列化value时不会带双引号,而使用Jackson2JsonRedisSerializer序列化String类型时会自动添加双引号。
     *
     */

    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){

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

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper mapper = new ObjectMapper();
        //指定要序列化的域,ANY是包括public和private的
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //指定序列化输入的类型,类必须是非final类型的,否则会报错
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(mapper);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        //设置key采用String的序列化方式
        redisTemplate.setKeySerializer(stringRedisSerializer);
        //设置hash的key采用String的序列化方式
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        //设置value采用jackson2的序列化方式
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        //设置hash的value采用jackson2的序列化方式
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

}

如果之前配置过 redisTemplate,再次新增这个配置文件启动时就会提示异常,提示redisTemplatebean创建失败,已存在什么的。

接着是 RedisCacheConfig


package gc.cnnvd.config;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.Arrays;

/**
 * <p>
 *     Redis Cache配置
 * </p>
 * @author geekidea
 * @date 2018-11-08
 */
@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport {

    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuffer redisKey = new StringBuffer();
            redisKey.append(target.getClass().getName()).append("-");
            redisKey.append(method.getName());
            if (params.length > 0) {
                redisKey.append("-").append(Arrays.deepToString(params));
            }
            return redisKey.toString();
        };
    }


    /**
     * redis 缓存配置
     * 修改序列化方式,解决缓存乱码
     * @param factory
     * @return
     */

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        // 配置序列化
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheConfiguration redisCacheConfiguration = config
                //Key序列化方式redisSerializer
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                //value序列化方式jackson2JsonRedisSerializer
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                //设置前缀
//            .prefixKeysWith("project:")
                //设置过期时间
                .entryTtl(Duration.ofSeconds(60*60));
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(redisCacheConfiguration)
                .build();
        return cacheManager;
    }

}

我这里直接将注解@EnableScheduling加在配置文件这里了,如果不加这里可以直接在启动类上要加上注解:@EnableScheduling

随便找了一个 service 方法进行测试:

@Override
@Cacheable(value = "adminUser",key = "#adminUserDetailParam.getId()",unless = "#result==null")
public AdminUserDetailVO getAdminUserDetail(AdminUserDetailParam adminUserDetailParam) {
    AdminUserMerger adminUserMerger = adminUserMapper.selectAdminUserDetail(adminUserDetailParam.getId(), adminUserDetailParam.getVersion());
    log.info("------------走数据库查询-------------");
    if (adminUserMerger == null) {
        throw new BusinessException("用户信息不存在");
    }
    AdminUserDetailVO adminUserDetailVO = new AdminUserDetailVO();
    BeanCopierUtils.copyProperties(adminUserMerger, adminUserDetailVO);
    return adminUserDetailVO;
}

这里仅在原来基础上加了一行注解@Cacheable(value = "adminUser",key = "#adminUserDetailParam.getId()",unless = "#result==null") 其中value为redis的key的前缀,


import com.alibaba.fastjson.JSONObject;
import gc.cnnvd.system.adminuser.param.AdminUserDetailParam;
import gc.cnnvd.system.adminuser.service.AdminUserService;
import gc.cnnvd.system.adminuser.vo.AdminUserDetailVO;
import gc.cnnvd.test.BaseTest;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * @Auther linmengmeng
 * @Date 2021-07-20 19:15
 */

public class QueryTest extends BaseTest {

    @Autowired
    private AdminUserService adminUserService;

    @Test
    public void testGetAdminDetail(){
        AdminUserDetailParam adminUserDetailParam = new AdminUserDetailParam();
        adminUserDetailParam.setId("test001");
        adminUserDetailParam.setVersion(0);
        AdminUserDetailVO adminUserDetail = adminUserService.getAdminUserDetail(adminUserDetailParam);
        System.out.println(JSONObject.toJSONString(adminUserDetail));
    }

}

写了测试用例后,运行发现缓存好使了,第一次会走数据库查询,第二次直接走redis缓存。

缓存内容,使用可视化工具查看如下图:

image.png