前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Springboot 整合redis 多数据源 数据库切换

Springboot 整合redis 多数据源 数据库切换

作者头像
分享干货的你
发布2022-12-19 20:40:41
2.2K1
发布2022-12-19 20:40:41
举报
文章被收录于专栏:分享干货的你

在日常的开发过程中我们都使用过redis {nosql} 做缓存什么的。 基本上都是使用官方的data-redis 来进行整合使用。 但是官方的只能支持单数据源的, 不支持多数据源的。 要是配置多数据源的情况下, 还要配置多个redisConnectionfactory , 配置多个redistemplate 同样的代码要写多份。这个很不友好,最近在想,能不能搞一个starts 封装一下。类似mybatis-plus 团队的动态数据源一样是基于注解和配置文件的。 我在网上找了很多资料,大部分都是怎么切换redis 数据库的, 没有切换redis数据源的。最后在知乎上面找到老哥的这篇文章, https://zhuanlan.zhihu.com/p/405242915 (如有侵权,请联系删除)。给了我新思路的大门。下面我们就来自己搞一个基于配置文件和注解的redis 动态数据源和动态数据库的切换。

1, 准备工作,新建一个一个springboot maven 工程, 这里我们不需要web的依赖,只需要data-redis 的依赖就行的。

2, 代码逻辑

3, 正式的写代码

大部分的代码都和之前那个老哥文章代码差不多, 这里我只是加上了切换redis 数据库的逻辑。

核心代码

代码语言:javascript
复制
package com.ducheng.multi.redis;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.util.ObjectUtils;
import java.util.Map;

public class MultiRedisConnectionFactory
        implements InitializingBean, DisposableBean, RedisConnectionFactory, ReactiveRedisConnectionFactory {
    private final Map<String, LettuceConnectionFactory> connectionFactoryMap;

    /**
     *
     * 当前redis的名字
     *
     */
    private static final ThreadLocal<String> currentRedisName = new ThreadLocal<>();

    /**
     *  当前redis的db数据库
     */
    private static final ThreadLocal<Integer> currentRedisDb = new ThreadLocal<>();


    public MultiRedisConnectionFactory(Map<String, LettuceConnectionFactory> connectionFactoryMap) {
        this.connectionFactoryMap = connectionFactoryMap;
    }

    public void setCurrentRedis(String currentRedisName) {
        if (!connectionFactoryMap.containsKey(currentRedisName)) {
            throw new RuntimeException("invalid currentRedis: " + currentRedisName + ", it does not exists in configuration");
        }
        MultiRedisConnectionFactory.currentRedisName.set(currentRedisName);
    }

    /**
     * 选择连接和数据库
     * @param currentRedisName
     * @param db
     */
    public void setCurrentRedis(String currentRedisName,Integer db) {
        if (!connectionFactoryMap.containsKey(currentRedisName)) {
            throw new RuntimeException("invalid currentRedis: " + currentRedisName + ", it does not exists in configuration");
        }
        MultiRedisConnectionFactory.currentRedisName.set(currentRedisName);
        MultiRedisConnectionFactory.currentRedisDb.set(db);
    }


    @Override
    public void destroy() throws Exception {
        connectionFactoryMap.values().forEach(LettuceConnectionFactory::destroy);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        connectionFactoryMap.values().forEach(LettuceConnectionFactory::afterPropertiesSet);
    }

    private LettuceConnectionFactory currentLettuceConnectionFactory() {
        String currentRedisName = MultiRedisConnectionFactory.currentRedisName.get();
        if (!ObjectUtils.isEmpty(currentRedisName)) {
            MultiRedisConnectionFactory.currentRedisName.remove();
            return connectionFactoryMap.get(currentRedisName);
        }
        return connectionFactoryMap.get(MultiRedisProperties.DEFAULT);
    }

    @Override
    public ReactiveRedisConnection getReactiveConnection() {
        return currentLettuceConnectionFactory().getReactiveConnection();
    }

    @Override
    public ReactiveRedisClusterConnection getReactiveClusterConnection() {
        return currentLettuceConnectionFactory().getReactiveClusterConnection();
    }

    @Override
    public RedisConnection getConnection() {
    // 这里就是切换数据库的地方
        Integer currentRedisDb = MultiRedisConnectionFactory.currentRedisDb.get();
        if (!ObjectUtils.isEmpty(currentRedisDb)) {
            LettuceConnectionFactory lettuceConnectionFactory = currentLettuceConnectionFactory();
            lettuceConnectionFactory.setShareNativeConnection(false);
            RedisConnection connection = lettuceConnectionFactory.getConnection();
            connection.select(currentRedisDb);
            return connection;
        }
        return   currentLettuceConnectionFactory().getConnection();
    }

    @Override
    public RedisClusterConnection getClusterConnection() {
        return currentLettuceConnectionFactory().getClusterConnection();
    }

    @Override
    public boolean getConvertPipelineAndTxResults() {
        return currentLettuceConnectionFactory().getConvertPipelineAndTxResults();
    }

    @Override
    public RedisSentinelConnection getSentinelConnection() {
        return currentLettuceConnectionFactory().getSentinelConnection();
    }

    @Override
    public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
        return currentLettuceConnectionFactory().translateExceptionIfPossible(ex);
    }

}

根据条件注解注入redis 数据库的工厂

核心代码

代码语言:javascript
复制
package org.springframework.boot.autoconfigure.data.redis;


import com.ducheng.multi.redis.MultiRedisConnectionFactory;
import com.ducheng.multi.redis.MultiRedisProperties;
import io.lettuce.core.resource.ClientResources;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;

import java.util.HashMap;
import java.util.Map;

@ConditionalOnProperty(prefix = "spring.redis", value = "enable-multi", matchIfMissing = false)
@Configuration(proxyBeanMethods = false)
public class RedisCustomizedConfiguration {

    /**
     * @param builderCustomizers
     * @param clientResources
     * @param multiRedisProperties
     * @return
     * @see org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration
     */
    @Bean
    public MultiRedisConnectionFactory multiRedisConnectionFactory(
            ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
            ClientResources clientResources,
            MultiRedisProperties multiRedisProperties,
            ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
            ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
        Map<String, LettuceConnectionFactory> connectionFactoryMap = new HashMap<>();
        Map<String, RedisProperties> multi = multiRedisProperties.getMulti();
        multi.forEach((k, v) -> {
            LettuceConnectionConfiguration lettuceConnectionConfiguration = new LettuceConnectionConfiguration(
                    v,
                    sentinelConfigurationProvider,
                    clusterConfigurationProvider
            );
            LettuceConnectionFactory lettuceConnectionFactory = lettuceConnectionConfiguration.redisConnectionFactory(builderCustomizers, clientResources);
            connectionFactoryMap.put(k, lettuceConnectionFactory);
        });
        return new MultiRedisConnectionFactory(connectionFactoryMap);
    }

}

redis 的配置类

代码语言:javascript
复制
package com.ducheng.multi.redis;


import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Map;


@ConfigurationProperties(prefix = "spring.redis")
public class MultiRedisProperties {
    /**
     * 默认连接必须配置,配置 key 为 default
     */
    public static final String DEFAULT = "default";

    private boolean enableMulti = false;


    private Map<String, RedisProperties> multi;

    public boolean isEnableMulti() {
        return enableMulti;
    }

    public void setEnableMulti(boolean enableMulti) {
        this.enableMulti = enableMulti;
    }

    public Map<String, RedisProperties> getMulti() {
        return multi;
    }

    public void setMulti(Map<String, RedisProperties> multi) {
        this.multi = multi;
    }

    public MultiRedisProperties() {
    }
}

代码测试:

配置文件配置多数据源:

代码语言:javascript
复制
spring.redis.enable-multi=true
spring.redis.multi.default.host=xxxxxxxx
spring.redis.multi.default.port=6381
spring.redis.multi.test.host=xxxxxxxx
spring.redis.multi.test.port=6380

配置redisTemplate

代码语言:javascript
复制
@Bean
  public RedisTemplate<String, Object> template(RedisConnectionFactory factory) {
    // 创建RedisTemplate<String, Object>对象
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    // 配置连接工厂
    template.setConnectionFactory(factory);
    // 定义Jackson2JsonRedisSerializer序列化对象
    Jackson2JsonRedisSerializer<Object> jacksonSeial = new Jackson2JsonRedisSerializer<>(Object.class);
    ObjectMapper om = new ObjectMapper();
    // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会报异常
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jacksonSeial.setObjectMapper(om);
    StringRedisSerializer stringSerial = new StringRedisSerializer();
    // redis key 序列化方式使用stringSerial
    template.setKeySerializer(stringSerial);
    // redis value 序列化方式使用jackson
    template.setValueSerializer(jacksonSeial);
    // redis hash key 序列化方式使用stringSerial
    template.setHashKeySerializer(stringSerial);
    // redis hash value 序列化方式使用jackson
    template.setHashValueSerializer(jacksonSeial);
    template.afterPropertiesSet();
    return template;
  }

编写测试代码:

代码语言:javascript
复制
package com.ducheng.multi.redis;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class MultiRedisSourceApplicationTests {

  @Autowired
  RedisTemplate<String, Object> redisTemplate;

  @Autowired
  MultiRedisConnectionFactory multiRedisConnectionFactory;

  @Test
  void contextLoads() {
    // 走默认的数据源
   redisTemplate.opsForValue().set("k1","v1");
    // 走test数据源0 库
   multiRedisConnectionFactory.setCurrentRedis("test",0);
   redisTemplate.opsForValue().set("k1","v2");
    // 走test数据源9 库
   multiRedisConnectionFactory.setCurrentRedis("test",9);
   redisTemplate.opsForValue().set("k1","v2");
  }

}

最后结果

完美,自定义注解加上aop 来动态切换,就是定义一个自定义的注解里面包含库名称和db 的名称

然后 就是在aop 的前置拦截器上面,或者注解的值, 然后在用MultiRedisConnectionFactory 来设置数据源和db 。相关的代码我就不写了,大家可以自己实现。后面我会封装一下,上传maven仓库。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-09-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 分享干货的你 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis®
腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档