Springboot动态redis 数据源数据库

语言: CN / TW / HK

“我正在参加「掘金·启航计划」” 在日常的开发过程中我们都使用过redis 做缓存什么的。 基本上都是使用官方的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 connectionFactoryMap; /
*
当前redis的名字
* /
private static final ThreadLocal currentRedisName = new ThreadLocal<>(); /

* 当前redis的db数据库
/
private static final ThreadLocal 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 builderCustomizers, ClientResources clientResources,
MultiRedisProperties multiRedisProperties,
ObjectProvider sentinelConfigurationProvider, ObjectProvider clusterConfigurationProvider) { Map 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 的配置类

``` @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; @SpringBootTestclass MultiRedisSourceApplicationTests {

@Autowired RedisTemplate redisTemplate;

@Autowired MultiRedisConnectionFactory multiRedisConnectionFactory;

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

multiRedisConnectionFactory.setCurrentRedis("test",9);

redisTemplate.opsForValue().set("k1","v2"); } } ```

最后结果

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

然后 就是在aop 的前置拦截器上面,或者注解的值, 然后在用MultiRedisConnectionFactory  来设置数据源和db 。 相关的代码已发布到maven 私服, 使用教程请看 https://ducheng.github.io/dynamicRedisPage/#/