zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

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

2023-02-18 16:34:17 时间

在日常的开发过程中我们都使用过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 数据库的逻辑。

核心代码

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 数据库的工厂

核心代码

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 的配置类

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() {
    }
}

代码测试:

配置文件配置多数据源:

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

@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;
  }

编写测试代码:

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仓库。