springboot创建redis连接实例(自定义配置)

在正常情况下,直接使用springboot提供的配置文件就可以直接配置不同的redis连接,如集群,单体或哨兵。但是,总是有但是。
现在有一个需求,就是用一个redis做缓存,然后用另外一个redis集群做业务数据存储。原有的缓存配置不能改。这就需要手动去配置redis连接了。

这边就直接上代码了,首先展示的是配置类,用来映射自定义的配置文件


import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * <p>
 * rediskv的配置类。
 * <p>
 * 通过修改type来适配不同的模式
 * <p>
 * 在standalone模式下,需要设置 host
 * 在sentinel模式下,需要设置 master, nodes
 * 在cluster模式下,需要设置 nodes
 * <p>
 * 剩下的属性是通用的
 */
@Data
@Component
@ConfigurationProperties(prefix = "config.rediskv.client")
public class RedisKVProperties {

    private String nodes;

    private int maxRedirects = 5;

    private String password;

    private long timeOut = 5;

    private int maxActive = 50;

    private int maxIdle = 50;

    private boolean metrics = false;

    //    类型: cluster, standalone,  sentinel
    private String type = "standalone";

    //    哨兵模式下生效
    private String master;

    //    单体模式下生效
    private String host;
}

相信想象力丰富的已经可以想象出配置文件长什么样子了。
拿到配置的属性后,就要去创建连接,然后定义redisTemplate。自定义的redisTemplate还不能和原有的冲突,所以给设置了bean的name。
这里参考了这个文章。但是每次关闭springboot的时候会报错,似乎是连接池没能正常关闭。
springboot项目中连接2个redis实例

import io.lettuce.core.TimeoutOptions;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import io.lettuce.core.resource.DefaultClientResources;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.MapPropertySource;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.*;

/**
 * @description 序列化用的。用的jackson做的(去stack overflow找的)
 */
@Configuration
@ConditionalOnProperty(prefix = "config", name = "kvClient", havingValue = "rediskv")
public class KVRedisConfig {

    @Resource
    private RedisKVProperties redisKVProperties;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        String type = redisKVProperties.getType();
        //连接池配置
        switch (type) {
            case "cluster":
                return clusterConnectionFactory();
            case "standalone":
                return standaloneConnectionFactory();
            case "sentinel":
                return sentinelConnectionFactory();
            default:
                return null;
        }
    }

    @Bean
    public DefaultClientResources lettuceClientResources() {
        return DefaultClientResources.create();
    }

    @Bean
    @Qualifier("kvRedisTemplate")
    public RedisTemplate<Object, Object> kvRedisTemplate() {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory());
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }

    @Bean
    @Qualifier("kvStringRedisTemplate")
    public StringRedisTemplate kvStringRedisTemplate() {
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(redisConnectionFactory());
        return stringRedisTemplate;
    }

    /**
     * 集群的配置
     */
    private RedisConnectionFactory clusterConnectionFactory() {
        LettuceClientConfiguration lettuceClientConfiguration = getLettuceClientConfiguration();
        Map<String, Object> source = new HashMap<>();
        RedisClusterConfiguration redisClusterConfiguration;
        source.put("spring.redis.cluster.nodes", redisKVProperties.getNodes());
        source.put("spring.redis.cluster.max-redirects", redisKVProperties.getMaxRedirects());
        redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));
        if (!Strings.isEmpty(redisKVProperties.getPassword()))
            redisClusterConfiguration.setPassword(redisKVProperties.getPassword());
        return new LettuceConnectionFactory(
                redisClusterConfiguration, lettuceClientConfiguration
        );
    }

    /**
     * 哨兵的配置
     */
    private RedisConnectionFactory sentinelConnectionFactory() {
        String master = redisKVProperties.getMaster();
        String[] nodes = redisKVProperties.getNodes().split(",");
        Set<String> hostAndPort = new HashSet<>();
        Collections.addAll(hostAndPort, nodes);
        RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(master, hostAndPort);
        if (!Strings.isEmpty(redisKVProperties.getPassword()))
            redisSentinelConfiguration.setPassword(redisKVProperties.getPassword());
        return new LettuceConnectionFactory(
                redisSentinelConfiguration, getLettuceClientConfiguration()
        );
    }

    /**
     * 单机的配置
     */
    private RedisConnectionFactory standaloneConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(redisKVProperties.getHost());
        String password = redisKVProperties.getPassword();
        if (password != null && !"".equals(password))
            redisStandaloneConfiguration.setPassword(password);
        return new LettuceConnectionFactory(
                redisStandaloneConfiguration, getLettuceClientConfiguration()
        );
    }

    /**
     * 连接池配置
     */
    private LettuceClientConfiguration getLettuceClientConfiguration() {
        GenericObjectPoolConfig genericObjectPoolConfig =
                new GenericObjectPoolConfig();
        genericObjectPoolConfig.setMaxTotal(redisKVProperties.getMaxActive());
        genericObjectPoolConfig.setMaxIdle(redisKVProperties.getMaxIdle());
        LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder
                builder = LettucePoolingClientConfiguration.builder()
                .clientOptions(clusterClientOptions())
                .clientResources(lettuceClientResources())
                .commandTimeout(Duration.ofSeconds(redisKVProperties.getTimeOut()));
        builder.poolConfig(genericObjectPoolConfig);
        LettuceClientConfiguration lettuceClientConfiguration = builder.build();
        return lettuceClientConfiguration;
    }

    /**
     * 拓扑刷新机制。主要是为了防止主机挂掉
     */
    private ClusterClientOptions clusterClientOptions() {
        ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
                .enablePeriodicRefresh(Duration.ofSeconds(30)) //按照周期刷新拓扑
                .enableAllAdaptiveRefreshTriggers() //根据事件刷新拓扑
                .build();

        return ClusterClientOptions.builder()
                //redis命令超时时间,超时后才会使用新的拓扑信息重新建立连接
                .timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(10)))
                .topologyRefreshOptions(topologyRefreshOptions)
                .build();
    }

}

这头上的注解可以百度。我也是头一次接触。
基本上,上面就是重点内容了。后面就是拿到redisTemplate然后做get与set了
这里提一个小细节。byte[]是用string的形式存进去的。但是byte[]和string来回转换容易出问题。这个问题可以百度。设置成特定的编码后就能保证来回转换不会变了。也可以用其他方式实现,这就是HexUtil没有展示的内容。
关于为啥用jackson。我懒得去引别的依赖。fastjson之前因为换版本除了问题我现在也没搞清楚到底引入哪个fastjson。

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * <p> 如果遇到getObject方法报错,可以试着给对应的实体类增加无参构造函数或@NoArgsConstructor注解
 */
@Slf4j
public class RedisStoreClient {

    @Autowired
    @Qualifier("kvRedisTemplate")
    private RedisTemplate redisTemplate;

    @Autowired
    @Qualifier("kvStringRedisTemplate")
    private StringRedisTemplate stringRedisTemplate;

    private String Separator = ",";

    @Bean
    public ObjectMapper getObjectMapper(){
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        ObjectMapper objectMapper = new ObjectMapper();
        builder.configure(objectMapper);
        objectMapper = objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
        return objectMapper;
    }

    @Resource
    private ObjectMapper objectMapper;

    /**
     * 存入byte[]对象
     * */
    public void setBytes(String key, byte[] bytes){
        if (key == null || "".equals(key.trim())) {
            throw new RuntimeException("key不能为空!");
        }
        setString(key, HexUtil.bytes2HexString(bytes));
    }

    /**
     * 取出byte[]对象
     * */
    public byte[] getBytes(String key){
        if (key == null || "".equals(key.trim())) {
            throw new RuntimeException("key不能为空!");
        }
        String value = getString(key);
        return HexUtil.hex2Bytes(value);
    }

    /**
    * 取出List<byte[]>
    * */
    public List<byte[]> getBytesList(Set<String> keys){
        if(CollectionUtils.isEmpty(keys)){
            throw new RuntimeException("key不能为空!");
        }
        List<String> multiString = getMultiString(keys);
        List<byte[]> result = new ArrayList<>();
        multiString.forEach(item -> result.add(HexUtil.hex2Bytes(item)));
        return result;
    }

    /**
     * 工具方法,把对象转为json
     * 如果要在redis环境下把对象转为string再存入(调用setString方法)
     * 一定要使用这个。redisTemplate整个序列化啥的都是基于jackson的。
     * */
    public String objectToJsonString(Object object){
        String res = null;
        try {
            res = objectMapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            log.error("将对象转换为json数组时发生异常, {}", e.getMessage());
        }
        return res;
    }

    /**
     * 把string解析为object。是和
     * @see #objectToJsonString(Object) 成对儿的方法。
     * */
    public <T> T jsonStringToObject(String value, Class<T> clazz){
        if (value == null || "".equals(value.trim())){
            throw new RuntimeException("传入空字符串!");
        }
        T obj;
        try {
            obj = (T) clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e.getMessage());
        }
        try {
            obj = objectMapper.readValue(value, clazz);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return obj;
    }

    /**
     * @param key 序列的key。根据key去取出自增序列
     * @return incr 返回key对应的序列的值自增后的结果
     */
    public Long getSequence(String key) {
        if (key == null || "".equals(key.trim())) {
            throw new RuntimeException("序列key不能为空!");
        }
        RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        return redisAtomicLong.incrementAndGet();
    }

    /**
     * @param key  序列的key。根据key去取出自增序列
     * @param step 需要增加的数值
     * @return incr 返回key对应的序列加上指定数值后的结果
     */
    public Long getSequence(String key, Long step) {
        if (key == null || "".equals(key.trim())) {
            throw new RuntimeException("序列key不能为空!");
        }
        RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        return redisAtomicLong.addAndGet(step);
    }

    /**
     * 根据传入的key把自增序列重置(实际上是直接删除了)
     *
     * @param key 序列对应的key
     */
    public Boolean resetSequence(String key) {
        if (key == null || "".equals(key.trim())) {
            throw new RuntimeException("序列key不能为空!");
        }
        return redisTemplate.delete(key);
    }


    /**
     * 使用scan匹配key后批量查询获取value
     * 以下方法需要spring-data-redis版本高于2.7.2才能使用
     */
    /*public <T> List<T> getObjectList(String pattern) {
        Set<String> keySet = new HashSet<>();
        Cursor<String> scan = redisTemplate.scan(ScanOptions.scanOptions().match(pattern).build());
        while (scan.hasNext()) {
            String next = scan.next();
            keySet.add(next);
        }
        return redisTemplate.opsForValue().multiGet(keySet);
    }*/

    /**
     * set方法
     */
    public <T> void setObject(String key, T value) {
        if (key == null || "".equals(key.trim())) {
            throw new RuntimeException("key不能为空!");
        }
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * set方法, 返回bool
     */
    public <T> boolean setObjectR(String key, T value) {
        if (key == null || "".equals(key.trim())) {
            throw new RuntimeException("key不能为空!");
        }
        redisTemplate.opsForValue().set(key, value);
        return true;
    }

    /**
     * get方法
     * <p>
     * 请 一定不要用 这个方法去取出存入的字符串
     * 由于在设置中为redisTemplate(kv)设置了默认用jackson去解析内容,用这个去取字符串会解析失败。
     * <p>
     * 一定不要用来取存入的字符串。
     *
     * @see #getString(String)
     */
    public <T> T getObject(String key) {
        if (key == null || "".equals(key.trim())) {
            throw new RuntimeException("key不能为空!");
        }
        return (T) redisTemplate.opsForValue().get(key);
    }

    /**
     * 为了拯救用string去存对象但是就是想用object去取对象
     * 这个方法就是干这个用的。但是我是不推荐使用的
     * */
    public <T> T getObject(String key, Class<T> clazz){
        if (key == null || "".equals(key.trim())) {
            throw new RuntimeException("key不能为空!");
        }

        String value = stringRedisTemplate.opsForValue().get(key);
        if(value == null){
            log.error("未能取得{}的对应的值", key);
        }
        return jsonStringToObject(value, clazz);
    }

    /**
     * get方法(传入Keys)
     *
     * @param keySet key的集合
     * @return Collection 值的集合
     */
    public <T> T getMultiObject(Set<String> keySet) {
        if (CollectionUtils.isEmpty(keySet)) {
            throw new RuntimeException("key不能为空!");
        }
        return (T) redisTemplate.opsForValue().multiGet(keySet);
    }

    /**
     * delete方法删除存储的键值对
     */
    public Boolean delete(String key) {
        if (key == null || "".equals(key.trim())) {
            throw new RuntimeException("key不能为空!");
        }
        return redisTemplate.delete(key);
    }

    /**
     * @param entrySet 键值对
     *                 通过键值对list批量进行set
     */
    public void batchPutByMap(Map<String, Object> entrySet) {
        if (CollectionUtils.isEmpty(entrySet)) {
            throw new RuntimeException("对象不能为空!");
        }
        redisTemplate.opsForValue().multiSet(entrySet);
    }

    /**
     * 实际存储到数据库的都是字符串。所以就这样存了。
     */
    public void merge(String key, String value) {
        if (key == null || "".equals(key.trim())) {
            throw new RuntimeException("key不能为空!");
        }
        stringRedisTemplate.opsForValue().append(key, Separator + value);
    }

    /**
     * merge方法,判断key在操作后是否存在
     */
    public Boolean mergeR(String key, String value) {
        if (key == null || "".equals(key.trim())) {
            throw new RuntimeException("key不能为空!");
        }
        stringRedisTemplate.opsForValue().append(key, Separator + value);
        return true;
    }

    /**
     * string在redis中的get与set方法
     */
    public void setString(String key, String value) {
        if (key == null || "".equals(key.trim())) {
            throw new RuntimeException("key不能为空!");
        }
        stringRedisTemplate.opsForValue().set(key, value);
    }

    /**
     * string的set方法, 通过再次检查key是否存在返回true或false
     */
    public Boolean setStringR(String key, String value) {
        if (key == null || "".equals(key.trim())) {
            throw new RuntimeException("key不能为空!");
        }
        stringRedisTemplate.opsForValue().set(key, value);
        return true;
    }

    /**
     * 用来取字符串的方法,也建议只用来取字符串。
     */
    public String getString(String key) {
        if (key == null || "".equals(key.trim())) {
            throw new RuntimeException("key不能为空!");
        }
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 根据传入的keySet获取多个String
     * */
    public List<String> getMultiString(Set<String> keys){
        if(CollectionUtils.isEmpty(keys)){
            throw new RuntimeException("key不能为空!");
        }
        return stringRedisTemplate.opsForValue().multiGet(keys);
    }

    /**
     * 传入map来批量存入string
     * */
    public void putMultiString(Map<String, String> stringMap){
        if(CollectionUtils.isEmpty(stringMap)){
            throw new RuntimeException("集合不能为空!");
        }
        stringRedisTemplate.opsForValue().multiSet(stringMap);
    }

    /*
     * 对于业务中使用的deleteRange方法,可认为是清空数据库?
     * 以下方法需要spring-data-redis版本高于2.7.2才能使用
     * */
    /*public void flushDB() {
        Cursor scan = redisTemplate.scan(ScanOptions.scanOptions().match("*").build());
        Set<String> keys = new HashSet<>();
        while (scan.hasNext()) {
            keys.add((String) scan.next());
        }
        redisTemplate.delete(keys);
    }*/

    /**
     * 清空指定的pattern,但是网上说这么做会让redis数据库寄了
     */
    public void flushDB() {
        redisTemplate.getConnectionFactory().getConnection().flushAll();
    }

    /**
     * 根据传入的Set<String> keys 批量删除
     *
     * @param keys key的集合
     */
    public void batchDelete(Set<String> keys) {
        if (CollectionUtils.isEmpty(keys)) {
            throw new RuntimeException("key不能为空!");
        }
        redisTemplate.delete(keys);
    }
}

代码也没写得多好,也就是上网看了各种文章后汇总出来的东西。就写这么多吧。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值