Springboot动态redis 数据源数据库
“我正在参加「掘金·启航计划」” 在日常的开发过程中我们都使用过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
* 当前redis的名字
* /
private static final ThreadLocal
* 当前redis的db数据库
/
private static final 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
MultiRedisProperties multiRedisProperties,
ObjectProvider
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
@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/#/