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