快乐学习 一个网站喵查铺子(catpuzi.com)全搞定~

springboot如何应用访问Redis呢?

编程杂谈 尔雅学习君 2023-09-12 扫描二维码
文章目录[隐藏]

Spring boot下的应用访问Redis数据库,采用属性配置文件读取数据,哨兵方式访问数据库。

pom文件

在pom文件中引入依赖包:

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

配置文件

在application.properties配置文件中添加:

# ------------------------------------------------
# Redis配置
# 是否启用Redis服务
redis.redisService.enabled=false
# redis 连接
# 客户端超时时间单位是毫秒 默认是2000,未使用
redis.timeout=2000
# 最大空闲数
redis.maxIdle=300
# 连接池的最大数据库连接数。设为0表示无限制,如果是jedis 2.4以后用redis.maxTotal
#redis.maxActive=600
# 控制一个pool可分配多少个jedis实例,用来替换上面的redis.maxActive,如果是jedis 2.4以后用该属性
redis.maxTotal=1000
# 最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
redis.maxWaitMillis=1000
# 连接的最小空闲时间 默认1800000毫秒(30分钟)
redis.minEvictableIdleTimeMillis=300000
# 每次释放连接的最大数目,默认3
redis.numTestsPerEvictionRun=1024
# 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
redis.timeBetweenEvictionRunsMillis=30000
# 是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
redis.testOnBorrow=true
# 在空闲时检查有效性, 默认false
redis.testWhileIdle=true
# 哨兵
redis.sentinel.master=mymaster
redis.sentinel.nodes=127.0.0.1:9379
redis.database=0
redis.password=

这里要说明一下,redis.redisService.enabled 是标识是否需要启用访问redis功能,特别是在使用自身电脑在移动办公开发时,有时并不需要连接Redis服务(例如我)。具体实现的原理是通过 @ConditionalOnExpression(“${redis.redisService.enabled:true}”) 实现对bean的注入控制。

redis配置注入

使用注解 @Configuration 声明java文件作为配置文件,注入相关的java类,完整文件见 redis\RedisCacheConfig.java。

为方便理解,以思考顺序描述相关内容。

注入redisService类

spring-data-redis针对jedis提供了一个高度封装的 RedisTemplate 类,针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口。

ValueOperations:简单K-V操作

SetOperations:set类型数据操作

ZSetOperations:zset类型数据操作

HashOperations:针对map类型的数据操作

ListOperations:针对list类型的数据操作

由于业务逻辑使用时,需要@Autowired注入redisService类,所以此处进行注入类的声明。

/**
 * 注入封装RedisTemplate
 */
@Bean(name = "redisService")
@ConditionalOnExpression("${redis.redisService.enabled:true}")
public RedisService redisService(RedisTemplate<String, Object> redisTemplate) {
    RedisService redisService = new RedisService();
    redisService.setRedisTemplate(redisTemplate);
    return redisService;
}

在进行redisService初始化时,需要参数对象RedisTemplate,有了下文的注入RedisTemplate类。

注入RedisTemplate类

此处使用JedisPoolConfig进行RedisTemplate的配置,同时设置序列化方式。

/**
 * RedisTemplate
 *
 * @return redisTemplate
 */
@Bean
@ConditionalOnExpression("${redis.redisService.enabled:true}")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    // 设置数据存入 redis 的序列化方式
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
    redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    // 事务开关
    redisTemplate.setEnableTransactionSupport(false);
    redisTemplate.setConnectionFactory(redisConnectionFactory);
    return redisTemplate;
}

此处的初始化需要redisConnectionFactory类。所以有redisConnectionFactory类的注入。

注入redisConnectionFactory类

通过连接工厂,以哨兵的方式连接Redis数据库服务。

/**
 * jedis连接工厂
 * @param jedisPoolConfig
 * @return
 */
@Bean
@ConditionalOnExpression("${redis.redisService.enabled:true}")
public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPoolConfig) {
    //哨兵
    RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration();
    String[] host = redisNodes.split(",");
    for(String redisHost : host){
        String[] item = redisHost.split(":");
        String ip = item[0];
        String port = item[1];
        redisSentinelConfiguration.addSentinel(new RedisNode(ip, Integer.parseInt(port)));
    }
    redisSentinelConfiguration.setDatabase(database);
    redisSentinelConfiguration.setMaster(master);
    //获得默认的连接池构造器
    JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpcb =
            (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder)JedisClientConfiguration.builder();
    //指定jedisPoolConifig来修改默认的连接池构造器
    jpcb.poolConfig(jedisPoolConfig);
    //通过构造器来构造jedis客户端配置
    JedisClientConfiguration jedisClientConfiguration = jpcb.build();
    //jedis连接工厂
    return new JedisConnectionFactory(redisSentinelConfiguration, jedisClientConfiguration);
}
注入jedisPoolConfig类
edisPoolConfig类配置所需的参数由@Value从配置文件中读取。

/**
 * 连接池配置信息
 * @return
 */
@Bean
@ConditionalOnExpression("${redis.redisService.enabled:true}")
public JedisPoolConfig jedisPoolConfig() {
    JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
    // 最大空闲数
    jedisPoolConfig.setMaxIdle(maxIdle);
    // 连接池的最大数据库连接数
    jedisPoolConfig.setMaxTotal(maxTotal);
    // 最大建立连接等待时间
    jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
    // 逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
    jedisPoolConfig.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
    // 每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3
    jedisPoolConfig.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
    // 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
    jedisPoolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
    // 是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
    jedisPoolConfig.setTestOnBorrow(testOnBorrow);
    // 在空闲时检查有效性, 默认false
    jedisPoolConfig.setTestWhileIdle(testWhileIdle);
    return jedisPoolConfig;
}

redis操作服务

  • 设置RedisTemplate
  • @Component
    @Slf4j
    public class RedisService {
        private RedisTemplate<String, Object> redisTemplate;
        public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
    
  • 自定义对RedisTemplate的封装函数
  • //=============================common============================
    /**
     * 指定缓存失效时间
     * @param key 键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key,long time){
        try {
            if(time> 0){
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            log.error("指定缓存失效时间异常,信息为:{}", e);
            return false;
        }
    }
    

    redis服务使用

    数据存储

    注入redis服务

    
    @Autowired
    private RedisService redisService;
    

    存储数据

  • 常量声明
  • /**
     * 验证码存放在Redis中的itemkey
     */
    public static final String ITEMKEY = "verifyCode";
    /**
     * 验证码存放在Redis中的有效期,以秒为单位
     */
    public static final Integer EXPIRETIME = 180;
    
  • 存储数据
  • //验证码存放Redis,有效期3分钟
    redisService.hset(Constant.ITEMKEY, mobile, verifyCode, Constant.EXPIRETIME);
    
  • 获取数据
  • //是否验证验证码
    String realSmsCode = (String) redisService.hget(Constant.ITEM, mobile);
    

    缓存mybatis数据

    分布式项目中最常见的缓存机制就是通过redis缓存mybatis的查询数据,一般称为二级缓存,可以使用@CacheConfig,@Cacheable,@CachePut,@CacheEvict。

    public interface UserMapper {
        @Cacheable(cacheNames = "User:Id")
        public User findById(@Param("id") Integer id);
    }
    

    CacheManagerConfig

    缓存对象集合中,缓存是以key-value形式保存的。当不指定缓存的key时,SpringBoot会使用SimpleKeyGenerator生成key。它是使用方法的参数组合生成的一个key。key采用 参数列表。

    public class SimpleKeyGenerator implements KeyGenerator {
        @Override
        public Object generate(Object target, Method method, Object... params) {
            return generateKey(params);
        }
        /**
         * Generate a key based on the specified parameters.
         */
        public static Object generateKey(Object... params) {
            if (params.length == 0) {
                return SimpleKey.EMPTY;
            }
            if (params.length == 1) {
                Object param = params[0];
                if (param != null && !param.getClass().isArray()) {
                    return param;
                }
            }
            return new SimpleKey(params);
        }
    }
    

    此时有一个问题:如果2个方法,参数是一样的,但执行逻辑不同,那么将会导致执行第二个方法时命中第一个方法的缓存。

    解决办法是在@Cacheable注解参数中指定key,或者自己实现一个KeyGenerator,在注解中指定KeyGenerator。但是如果这样的情况很多,每一个都要指定key、KeyGenerator很麻烦。

    Spring同样提供了方案:继承CachingConfigurerSupport并重写keyGenerator()。key采用 包名+方法名+参数列表。

    完整代码文件见CacheManagerConfig.java。

    @Bean
    @Override
    public KeyGenerator keyGenerator(){
        return new KeyGenerator() {
            @Override
            public Object generate(Object o, Method method, Object... objects) {
                StringBuilder sb = new StringBuilder();
                sb.append(o.getClass().getName());
                sb.append(method.getName());
                for (Object obj:objects){
                    sb.append(obj.toString());
                }
                log.info("keyGenerator=" + sb.toString());
                return sb.toString();
            }
        };
    }
    

    redisCacheManager

    同样使用redisConnectionFactory配置缓存的连接。

    @Bean(name = "redisCacheManager")
    @ConditionalOnExpression("${redis.redisService.enabled:true}")
    public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheManager redisCacheManager = RedisCacheManager.create(redisConnectionFactory);
        log.info("redis初始化-----------------------------------");
        return redisCacheManager;
    }
    
    
    喜欢 (0)
    关于作者: