Springboot動態redis 資料來源資料庫
“我正在參加「掘金·啟航計劃」” 在日常的開發過程中我們都使用過redis 做快取什麼的。 基本上都是使用官方的data-redis 來進行整合使用。 但是官方的只能支援單資料來源的, 不支援多資料來源的。 要是配置多資料來源的情況下, 還要配置多個redisConnectionfactory , 配置多個redistemplate 同樣的程式碼要寫多份。這個很不友好,最近在想,能不能搞一個starts 封裝一下。類似mybatis-plus 團隊的動態資料來源一樣是基於註解和配置檔案的。 我在網上找了很多資料,大部分都是怎麼切換redis 資料庫的, 沒有切換redis資料來源的。最後在知乎上面找到老哥的這篇文章, http://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 私服, 使用教程請看 http://ducheng.github.io/dynamicRedisPage/#/