写在开头:本文的代码实现思路参考了: wen-pan大佬的项目链接: enhance-boot-data-redis,大佬的代码考虑周全,学到很多,感谢大佬指点。
本文的的目的是实现程序运行中,可以动态切换redis的db。
本项目完整代码git链接:redis-dynamic-switch-db
在redis的源码中,程序启动时从配置文件中读取redis配置的db,如果没有配置则默认初始化0号db。初始化好数据源的bean对象交给spring管理,需要时直接注入初始化好的数据源对象,来操作redis。
但是如果想要在操作redis的时候指定要操作的db,原生代码中并没有提供直接切换的功能,我们只能在程序启动时,多初始化几个数据源,例如我想要操作1、2、3号db,就在一开始初始化3个数据源对象,需要时分别调用。
显然这样并不优雅。
一是不灵活,如果我中途又想操作4号db呢?只能修改代码然后重启我的程序再调用,难道每次想要操作不同的db,都要更改一次代码?
二是不简洁,如果我想要操作100个db,那我就需要初始化100个数据源对象?1000个呢?10000个呢?
于是就有了下面的代码,把部分redis的代码抽出来重写,使redis的database不是在程序启动时就已经固定,而是可以在程序运行中根据我们的需要,去动态的创建指向不同db的连接,任意操作我们想操作的database。
原:从redisProperties 中读取database
修改后:手动指定database
package com.yebuxiu.config;
import com.yebuxiu.config.properties.MyRedisProperties;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.util.StringUtils;
import java.net.URI;
import java.net.URISyntaxException;
/**
* Base Redis connection configuration.
* 参考:spring-boot-autoconfigure 下的 RedisConnectionConfiguration ,参考版本2.2.7
* 原:从redisProperties 中读取database
* 修改后:手动指定database
*
* @author Mark Paluch
* @author Stephane Nicoll
* @author Alen Turkovic
*/
public abstract class RedisConnectionConfiguration {
/**
* redis配置properties
*/
private final MyRedisProperties myRedisProperties;
/**
* redis所使用的库,为指定的db动态的创建RedisTemplate
*/
private final int database;
protected RedisConnectionConfiguration(MyRedisProperties myRedisProperties,
int database) {
this.myRedisProperties = myRedisProperties;
this.database = database;
}
/**
* redis 单机模式配置信息
*/
protected final RedisStandaloneConfiguration getStandaloneConfig() {
RedisProperties gps = myRedisProperties.getRedisProperties();
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
if (StringUtils.hasText(gps.getUrl())) {
ConnectionInfo connectionInfo = parseUrl(gps.getUrl());
config.setHostName(connectionInfo.getHostName());
config.setPort(connectionInfo.getPort());
config.setPassword(RedisPassword.of(connectionInfo.getPassword()));
} else {
config.setHostName(gps.getHost());
config.setPort(gps.getPort());
config.setPassword(RedisPassword.of(gps.getPassword()));
}
// 使用自定义db
config.setDatabase(database);
return config;
}
protected final RedisProperties getProperties() {
return myRedisProperties.getRedisProperties();
}
/**
* 解析Redis url连接,创建连接信息
*/
protected static ConnectionInfo parseUrl(String url) {
try {
URI uri = new URI(url);
String scheme = uri.getScheme();
if (!"redis".equals(scheme) && !"rediss".equals(scheme)) {
// 无效的或畸形的url
throw new RedisUrlSyntaxException(url);
}
boolean useSsl = ("rediss".equals(scheme));
String password = null;
if (uri.getUserInfo() != null) {
String candidate = uri.getUserInfo();
int index = candidate.indexOf(':');
if (index >= 0) {
password = candidate.substring(index + 1);
} else {
password = candidate;
}
}
return new ConnectionInfo(uri, useSsl, password);
} catch (URISyntaxException ex) {
// 无效的或畸形的url
throw new RedisUrlSyntaxException("Malformed url '" + url + "'", ex);
}
}
/**
* Redis连接信息
*/
static class ConnectionInfo {
private final URI uri;
private final boolean useSsl;
private final String password;
ConnectionInfo(URI uri, boolean useSsl, String password) {
this.uri = uri;
this.useSsl = useSsl;
this.password = password;
}
boolean isUseSsl() {
return useSsl;
}
String getHostName() {
return uri.getHost();
}
int getPort() {
return uri.getPort();
}
String getPassword() {
return password;
}
}
}
原:从配置文件读取redisProperties,由spring管理这个bean对象
修改后:每次new对象的时候传入database参数
package com.yebuxiu.config;
import com.yebuxiu.config.properties.MyRedisProperties;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.TimeoutOptions;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DefaultClientResources;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* Redis connection configuration using Lettuce.
* Author:Mark Paluch, Andy Wilkinson
* 自定义改造lettuce连接配置:
* 原:从配置文件读取redisProperties,由spring管理这个bean对象
* 修改后:每次new对象的时候传入database参数
*/
class LettuceConnectionConfigure extends RedisConnectionConfiguration {
/**
* redis配置properties
*/
private final MyRedisProperties myRedisProperties;
/**
* Lettuce客户端定制
*/
private final List<LettuceClientConfigurationBuilderCustomizer> builderCustomizers;
private final ClientResources clientResources;
LettuceConnectionConfigure(MyRedisProperties myRedisProperties,
List<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
int database) {
super(myRedisProperties, database);
this.myRedisProperties = myRedisProperties;
this.builderCustomizers = Optional.ofNullable(builderCustomizers).orElse(new ArrayList<>());
// 每次new LettuceConnectionConfiguration 都新建一个clientResources,也可以使用容器中默认注入的
// 参考 LettuceConnectionConfiguration
clientResources = DefaultClientResources.create();
}
/**
* 创建lettuce连接工厂
*/
LettuceConnectionFactory redisConnectionFactory() {
// 获取lettuce连接工厂配置信息
LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(clientResources, myRedisProperties.getRedisProperties().getLettuce().getPool());
// 创建lettuce连接工厂
return createLettuceConnectionFactory(clientConfig);
}
/**
* 创建lettuce连接工厂
*/
private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) {
LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
// 由于我们手动创建lettuceConnectionFactory连接工厂,所以afterPropertiesSet并不会像spring一样自动被吊起
// 必须手动调用afterPropertiesSet(),初始化connectionProvider,不然创建连接会报错空指针
lettuceConnectionFactory.afterPropertiesSet();
return lettuceConnectionFactory;
}
/**
* 获取lettuce客户端配置
*/
private LettuceClientConfiguration getLettuceClientConfiguration(ClientResources clientResources, Pool pool) {
LettuceClientConfigurationBuilder builder = createBuilder(pool);
applyProperties(builder);
if (StringUtils.hasText(getProperties().getUrl())) {
customizeConfigurationFromUrl(builder);
}
builder.clientOptions(createClientOptions());
builder.clientResources(clientResources);
customize(builder);
return builder.build();
}
/**
* 变更源码排序
*/
private void customize(LettuceClientConfigurationBuilder builder) {
for (LettuceClientConfigurationBuilderCustomizer customizer : builderCustomizers) {
customizer.customize(builder);
}
}
/**
* 创建客户端配置构建器,用于构建客户端配置
*/
private static LettuceClientConfigurationBuilder createBuilder(Pool pool) {
if (pool == null) {
return LettuceClientConfiguration.builder();
}
return PoolBuilderFactory.createBuilder(pool);
}
private LettuceClientConfigurationBuilder applyProperties(
LettuceClientConfigurationBuilder builder) {
if (getProperties().isSsl()) {
builder.useSsl();
}
if (getProperties().getTimeout() != null) {
builder.commandTimeout(getProperties().getTimeout());
}
if (getProperties().getLettuce() != null) {
RedisProperties.Lettuce lettuce = getProperties().getLettuce();
if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) {
builder.shutdownTimeout(getProperties().getLettuce().getShutdownTimeout());
}
}
if (StringUtils.hasText(getProperties().getClientName())) {
builder.clientName(getProperties().getClientName());
}
return builder;
}
private ClientOptions createClientOptions() {
ClientOptions.Builder builder = initializeClientOptionsBuilder();
return builder.timeoutOptions(TimeoutOptions.enabled()).build();
}
private ClientOptions.Builder initializeClientOptionsBuilder() {
return ClientOptions.builder();
}
private void customizeConfigurationFromUrl(LettuceClientConfigurationBuilder builder) {
ConnectionInfo connectionInfo = parseUrl(getProperties().getUrl());
if (connectionInfo.isUseSsl()) {
builder.useSsl();
}
}
/**
* Inner class to allow optional commons-pool2 dependency.
*/
private static class PoolBuilderFactory {
static LettuceClientConfigurationBuilder createBuilder(Pool properties) {
return LettucePoolingClientConfiguration.builder().poolConfig(getPoolConfig(properties));
}
private static GenericObjectPoolConfig<?> getPoolConfig(Pool properties) {
GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
config.setMaxTotal(properties.getMaxActive());
config.setMaxIdle(properties.getMaxIdle());
config.setMinIdle(properties.getMinIdle());
if (properties.getTimeBetweenEvictionRuns() != null) {
config.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRuns().toMillis());
}
if (properties.getMaxWait() != null) {
config.setMaxWaitMillis(properties.getMaxWait().toMillis());
}
return config;
}
}
}
用于为指定的db创建RedisTemplate。
package com.yebuxiu.config;
import com.yebuxiu.config.properties.MyRedisProperties;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.Assert;
import java.util.List;
/**
* 动态 RedisTemplate 工厂类,用于创建和管理RedisTemplate
*/
public class DynamicRedisTemplateFactory<K, V> {
// ==============================================================================================================
// 从data-redis源码得知,构建lettuce客户端配置(LettuceConnectionConfigure)
// 需要如下参数,并且从spring自动配置模块的源码可以看到,这些属性会由springboot自动配置帮我们注入到容器中,所以这里可以通过构造器
// 将这些属性传递进来,并保存到属性上以便后面使用。
// ==============================================================================================================
/**
* Redis配置信息
*/
private final MyRedisProperties myRedisProperties;
/**
* lettuce配置定制
*/
private final List<LettuceClientConfigurationBuilderCustomizer> lettuceBuilderCustomizers;
/**
* 这些参数由springboot自动配置帮我们自动配置并注入到容器
* ObjectProvider更加宽松的依赖注入
*/
public DynamicRedisTemplateFactory(MyRedisProperties myRedisProperties,
List<LettuceClientConfigurationBuilderCustomizer> lettuceBuilderCustomizers) {
this.myRedisProperties = myRedisProperties;
this.lettuceBuilderCustomizers = lettuceBuilderCustomizers;
}
/**
* 为指定的db创建RedisTemplate,用于操作Redis
*
* @param database redis db
* @return org.springframework.data.redis.core.RedisTemplate
* @author [email protected] 2021/8/7 1:47 下午
*/
public RedisTemplate<K, V> createRedisTemplate(int database) {
RedisConnectionFactory redisConnectionFactory = null;
// 根据Redis客户端类型创建Redis连接工厂(用于创建RedisTemplate)
// 使用指定的db创建lettuce redis连接工厂(创建方式参照源码:LettuceConnectionConfiguration)
LettuceConnectionConfigure lettuceConnectionConfigure = new LettuceConnectionConfigure(
myRedisProperties, lettuceBuilderCustomizers, database);
redisConnectionFactory = lettuceConnectionConfigure.redisConnectionFactory();
Assert.notNull(redisConnectionFactory, "redisConnectionFactory is null.");
// 通过Redis连接工厂创建RedisTemplate
return createRedisTemplate(redisConnectionFactory);
}
/**
* 通过Redis连接工厂来创建一个redisTemplate用于操作Redis db
*/
private RedisTemplate<K, V> createRedisTemplate(RedisConnectionFactory factory) {
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
RedisTemplate<K, V> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setStringSerializer(stringRedisSerializer);
redisTemplate.setDefaultSerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(stringRedisSerializer);
// 设置Redis连接工厂用于创建连接
redisTemplate.setConnectionFactory(factory);
// 调用afterPropertiesSet方法,在属性设置完成后做一些检查和额外工作
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
package com.flybees.pass32960.data.redis.gps.template;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.query.SortQuery;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.script.ScriptExecutor;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.lang.NonNull;
import java.io.Closeable;
import java.util.*;
import java.util.concurrent.TimeUnit;
public abstract class AbstractRoutingRedisTemplate<K, V> extends RedisTemplate<K, V> implements InitializingBean {
/**
* 存放对应库的redisTemplate,用于操作对应的db
*/
private Map<Object, RedisTemplate<K, V>> redisTemplates;
/**
* 当不指定库时默认使用的redisTemplate
*/
private RedisTemplate<K, V> defaultRedisTemplate;
/**
* 当类被加载到容器中属性设置完毕后检查redisTemplates和defaultRedisTemplate是否为空
*/
@Override
public void afterPropertiesSet() {
if (redisTemplates == null) {
throw new IllegalArgumentException("Property 'redisTemplates' is required");
}
if (defaultRedisTemplate == null) {
throw new IllegalArgumentException("Property 'defaultRedisTemplate' is required");
}
}
/**
* 获取要操作的RedisTemplate
*/
protected RedisTemplate<K, V> determineTargetRedisTemplate() {
// 当前要操作的DB
Object lookupKey = determineCurrentLookupKey();
// 如果当前要操作的DB为空则使用默认的RedisTemplate(使用0号库)
if (lookupKey == null) {
return defaultRedisTemplate;
}
RedisTemplate<K, V> redisTemplate = redisTemplates.get(lookupKey);
// 如果当前要操作的db还没有维护到redisTemplates中,则创建一个对该库的连接并缓存起来
if (redisTemplate == null) {
// 双重检查,这里直接使用synchronized锁,因为创建redisTemplate不会很频繁,一般整个生命周期只有几次,不会有性能问题
synchronized (DynamicRedisTemplate.class) {
if (null == redisTemplates.get(lookupKey)) {
redisTemplate = createRedisTemplateOnMissing(lookupKey);
redisTemplates.put(lookupKey, redisTemplate);
}
}
}
return redisTemplate;
}
/**
* 获取当前 Redis db
*
* @return current redis db
*/
protected abstract Object determineCurrentLookupKey();
/**
* 没有对应 db 的 RedisTemplate 时,则调用此方法创建 RedisTemplate
*
* @param lookupKey RedisDB
* @return RedisTemplate
*/
public abstract RedisTemplate<K, V> createRedisTemplateOnMissing(Object lookupKey);
public void setRedisTemplates(Map<Object, RedisTemplate<K, V>> redisTemplates) {
this.redisTemplates = redisTemplates;
}
public void setDefaultRedisTemplate(RedisTemplate<K, V> defaultRedisTemplate) {
this.defaultRedisTemplate = defaultRedisTemplate;
}
public Map<Object, RedisTemplate<K, V>> getRedisTemplates() {
return redisTemplates;
}
public RedisTemplate<K, V> getDefaultRedisTemplate() {
return defaultRedisTemplate;
}
// ====================以下都是继承自父类的方法,RedisTemplate中的方法执行时会调用下面的方法=====================
@Override
public <T> T execute(@NonNull RedisCallback<T> action) {
return determineTargetRedisTemplate().execute(action);
}
@Override
public <T> T execute(@NonNull RedisCallback<T> action, boolean exposeConnection) {
return determineTargetRedisTemplate().execute(action, exposeConnection);
}
@Override
public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {
return determineTargetRedisTemplate().execute(action, exposeConnection, pipeline);
}
@Override
public <T> T execute(SessionCallback<T> session) {
return determineTargetRedisTemplate().execute(session);
}
@NonNull
@Override
public List<Object> executePipelined(@NonNull SessionCallback<?> session) {
return determineTargetRedisTemplate().executePipelined(session);
}
@NonNull
@Override
public List<Object> executePipelined(SessionCallback<?> session, RedisSerializer<?> resultSerializer) {
return determineTargetRedisTemplate().executePipelined(session, resultSerializer);
}
@NonNull
@Override
public List<Object> executePipelined(@NonNull RedisCallback<?> action) {
return determineTargetRedisTemplate().executePipelined(action);
}
@NonNull
@Override
public List<Object> executePipelined(RedisCallback<?> action, RedisSerializer<?> resultSerializer) {
return determineTargetRedisTemplate().executePipelined(action, resultSerializer);
}
@Override
public <T> T execute(@NonNull RedisScript<T> script, @NonNull List<K> keys, @NonNull Object... args) {
return determineTargetRedisTemplate().execute(script, keys, args);
}
@Override
public <T> T execute(@NonNull RedisScript<T> script,
@NonNull RedisSerializer<?> argsSerializer,
@NonNull RedisSerializer<T> resultSerializer,
@NonNull List<K> keys, @NonNull Object... args) {
return determineTargetRedisTemplate().execute(script, argsSerializer, resultSerializer, keys, args);
}
@Override
public <T extends Closeable> T executeWithStickyConnection(RedisCallback<T> callback) {
return determineTargetRedisTemplate().executeWithStickyConnection(callback);
}
@Override
public boolean isExposeConnection() {
return determineTargetRedisTemplate().isExposeConnection();
}
@Override
public void setExposeConnection(boolean exposeConnection) {
determineTargetRedisTemplate().setExposeConnection(exposeConnection);
}
@Override
public boolean isEnableDefaultSerializer() {
return determineTargetRedisTemplate().isEnableDefaultSerializer();
}
@Override
public void setEnableDefaultSerializer(boolean enableDefaultSerializer) {
determineTargetRedisTemplate().setEnableDefaultSerializer(enableDefaultSerializer);
}
@Override
public RedisSerializer<?> getDefaultSerializer() {
return determineTargetRedisTemplate().getDefaultSerializer();
}
@Override
public void setDefaultSerializer(@NonNull RedisSerializer<?> serializer) {
determineTargetRedisTemplate().setDefaultSerializer(serializer);
}
@Override
public void setKeySerializer(@NonNull RedisSerializer<?> serializer) {
determineTargetRedisTemplate().setKeySerializer(serializer);
}
@NonNull
@Override
public RedisSerializer<?> getKeySerializer() {
return determineTargetRedisTemplate().getKeySerializer();
}
@Override
public void setValueSerializer(@NonNull RedisSerializer<?> serializer) {
determineTargetRedisTemplate().setValueSerializer(serializer);
}
@NonNull
@Override
public RedisSerializer<?> getValueSerializer() {
return determineTargetRedisTemplate().getValueSerializer();
}
@NonNull
@Override
public RedisSerializer<?> getHashKeySerializer() {
return determineTargetRedisTemplate().getHashKeySerializer();
}
@Override
public void setHashKeySerializer(@NonNull RedisSerializer<?> hashKeySerializer) {
determineTargetRedisTemplate().setHashKeySerializer(hashKeySerializer);
}
@NonNull
@Override
public RedisSerializer<?> getHashValueSerializer() {
return determineTargetRedisTemplate().getHashValueSerializer();
}
@Override
public void setHashValueSerializer(@NonNull RedisSerializer<?> hashValueSerializer) {
determineTargetRedisTemplate().setHashValueSerializer(hashValueSerializer);
}
@NonNull
@Override
public RedisSerializer<String> getStringSerializer() {
return determineTargetRedisTemplate().getStringSerializer();
}
@Override
public void setStringSerializer(@NonNull RedisSerializer<String> stringSerializer) {
determineTargetRedisTemplate().setStringSerializer(stringSerializer);
}
@Override
public void setScriptExecutor(@NonNull ScriptExecutor<K> scriptExecutor) {
determineTargetRedisTemplate().setScriptExecutor(scriptExecutor);
}
@NonNull
@Override
public List<Object> exec() {
return determineTargetRedisTemplate().exec();
}
@NonNull
@Override
public List<Object> exec(@NonNull RedisSerializer<?> valueSerializer) {
return determineTargetRedisTemplate().exec(valueSerializer);
}
@Override
public Boolean delete(K key) {
return determineTargetRedisTemplate().delete(key);
}
@Override
public Long delete(@NonNull Collection<K> keys) {
return determineTargetRedisTemplate().delete(keys);
}
@Override
public Boolean hasKey(K key) {
return determineTargetRedisTemplate().hasKey(key);
}
@Override
public Boolean expire(K key, long timeout, TimeUnit unit) {
return determineTargetRedisTemplate().expire(key, timeout, unit);
}
@Override
public Boolean expireAt(K key, Date date) {
return determineTargetRedisTemplate().expireAt(key, date);
}
@Override
public void convertAndSend(@NonNull String channel, @NonNull Object message) {
determineTargetRedisTemplate().convertAndSend(channel, message);
}
@Override
public Long getExpire(K key) {
return determineTargetRedisTemplate().getExpire(key);
}
@Override
public Long getExpire(K key, @NonNull TimeUnit timeUnit) {
return determineTargetRedisTemplate().getExpire(key, timeUnit);
}
@Override
public Set<K> keys(K pattern) {
return determineTargetRedisTemplate().keys(pattern);
}
@Override
public Boolean persist(K key) {
return determineTargetRedisTemplate().persist(key);
}
@Override
public Boolean move(K key, int dbIndex) {
return determineTargetRedisTemplate().move(key, dbIndex);
}
@Override
public K randomKey() {
return determineTargetRedisTemplate().randomKey();
}
@Override
public void rename(K oldKey, K newKey) {
determineTargetRedisTemplate().rename(oldKey, newKey);
}
@Override
public Boolean renameIfAbsent(K oldKey, K newKey) {
return determineTargetRedisTemplate().renameIfAbsent(oldKey, newKey);
}
@Override
public DataType type(K key) {
return determineTargetRedisTemplate().type(key);
}
@Override
public byte[] dump(K key) {
return determineTargetRedisTemplate().dump(key);
}
@Override
public void restore(@NonNull K key, @NonNull byte[] value, long timeToLive, @NonNull TimeUnit unit) {
determineTargetRedisTemplate().restore(key, value, timeToLive, unit);
}
@Override
public void multi() {
determineTargetRedisTemplate().multi();
}
@Override
public void discard() {
determineTargetRedisTemplate().discard();
}
@Override
public void watch(K key) {
determineTargetRedisTemplate().watch(key);
}
@Override
public void watch(Collection<K> keys) {
determineTargetRedisTemplate().watch(keys);
}
@Override
public void unwatch() {
determineTargetRedisTemplate().unwatch();
}
@Override
public List<V> sort(@NonNull SortQuery<K> query) {
return determineTargetRedisTemplate().sort(query);
}
@Override
public <T> List<T> sort(SortQuery<K> query, RedisSerializer<T> resultSerializer) {
return determineTargetRedisTemplate().sort(query, resultSerializer);
}
@Override
public <T> List<T> sort(@NonNull SortQuery<K> query, @NonNull BulkMapper<T, V> bulkMapper) {
return determineTargetRedisTemplate().sort(query, bulkMapper);
}
@Override
public <T, S> List<T> sort(@NonNull SortQuery<K> query,
@NonNull BulkMapper<T, S> bulkMapper,
RedisSerializer<S> resultSerializer) {
return determineTargetRedisTemplate().sort(query, bulkMapper, resultSerializer);
}
@Override
public Long sort(SortQuery<K> query, K storeKey) {
return determineTargetRedisTemplate().sort(query, storeKey);
}
@NonNull
@Override
public BoundValueOperations<K, V> boundValueOps(@NonNull K key) {
return determineTargetRedisTemplate().boundValueOps(key);
}
@NonNull
@Override
public ValueOperations<K, V> opsForValue() {
return determineTargetRedisTemplate().opsForValue();
}
@NonNull
@Override
public ListOperations<K, V> opsForList() {
return determineTargetRedisTemplate().opsForList();
}
@NonNull
@Override
public BoundListOperations<K, V> boundListOps(@NonNull K key) {
return determineTargetRedisTemplate().boundListOps(key);
}
@NonNull
@Override
public BoundSetOperations<K, V> boundSetOps(@NonNull K key) {
return determineTargetRedisTemplate().boundSetOps(key);
}
@NonNull
@Override
public SetOperations<K, V> opsForSet() {
return determineTargetRedisTemplate().opsForSet();
}
@NonNull
@Override
public BoundZSetOperations<K, V> boundZSetOps(@NonNull K key) {
return determineTargetRedisTemplate().boundZSetOps(key);
}
@NonNull
@Override
public ZSetOperations<K, V> opsForZSet() {
return determineTargetRedisTemplate().opsForZSet();
}
@NonNull
@Override
public GeoOperations<K, V> opsForGeo() {
return determineTargetRedisTemplate().opsForGeo();
}
@NonNull
@Override
public BoundGeoOperations<K, V> boundGeoOps(@NonNull K key) {
return determineTargetRedisTemplate().boundGeoOps(key);
}
@NonNull
@Override
public HyperLogLogOperations<K, V> opsForHyperLogLog() {
return determineTargetRedisTemplate().opsForHyperLogLog();
}
@NonNull
@Override
public <HK, HV> BoundHashOperations<K, HK, HV> boundHashOps(@NonNull K key) {
return determineTargetRedisTemplate().boundHashOps(key);
}
@NonNull
@Override
public <HK, HV> HashOperations<K, HK, HV> opsForHash() {
return determineTargetRedisTemplate().opsForHash();
}
@NonNull
@Override
public ClusterOperations<K, V> opsForCluster() {
return determineTargetRedisTemplate().opsForCluster();
}
@Override
public void killClient(@NonNull String host, int port) {
determineTargetRedisTemplate().killClient(host, port);
}
@Override
public List<RedisClientInfo> getClientList() {
return determineTargetRedisTemplate().getClientList();
}
@Override
public void slaveOf(@NonNull String host, int port) {
determineTargetRedisTemplate().slaveOf(host, port);
}
@Override
public void slaveOfNoOne() {
determineTargetRedisTemplate().slaveOfNoOne();
}
@Override
public void setEnableTransactionSupport(boolean enableTransactionSupport) {
determineTargetRedisTemplate().setEnableTransactionSupport(enableTransactionSupport);
}
@Override
public void setBeanClassLoader(@NonNull ClassLoader classLoader) {
determineTargetRedisTemplate().setBeanClassLoader(classLoader);
}
@Override
public RedisConnectionFactory getConnectionFactory() {
return determineTargetRedisTemplate().getConnectionFactory();
}
@Override
public void setConnectionFactory(@NonNull RedisConnectionFactory connectionFactory) {
determineTargetRedisTemplate().setConnectionFactory(connectionFactory);
}
}
package com.yebuxiu.template;
import com.yebuxiu.config.DynamicRedisTemplateFactory;
import com.yebuxiu.helper.RedisDatabaseThreadLocalHelper;
import org.springframework.data.redis.core.RedisTemplate;
/**
* 动态 RedisTemplate ,以支持动态切换 redis database
*/
public class DynamicRedisTemplate<K, V> extends AbstractRoutingRedisTemplate<K, V> {
/**
* 动态RedisTemplate工厂,用于创建管理动态DynamicRedisTemplate
*/
private final DynamicRedisTemplateFactory<K, V> dynamicRedisTemplateFactory;
public DynamicRedisTemplate(DynamicRedisTemplateFactory<K, V> dynamicRedisTemplateFactory) {
this.dynamicRedisTemplateFactory = dynamicRedisTemplateFactory;
}
@Override
protected Object determineCurrentLookupKey() {
return RedisDatabaseThreadLocalHelper.get();
}
/**
* 通过制定的db创建RedisTemplate
*
* @param lookupKey db号
* @return org.springframework.data.redis.core.RedisTemplate
*/
@Override
public RedisTemplate<K, V> createRedisTemplateOnMissing(Object lookupKey) {
return dynamicRedisTemplateFactory.createRedisTemplate((Integer) lookupKey);
}
}
package com.yebuxiu.helper;
import org.springframework.util.CollectionUtils;
import java.util.ArrayDeque;
import java.util.Deque;
/**
* redis动态切换数据库帮助器
*/
public class RedisDatabaseThreadLocalHelper {
private static final ThreadLocal<Deque<Integer>> THREAD_DB = new ThreadLocal<>();
/**
* 更改当前线程 RedisTemplate db
*
* @param db set current redis db
*/
public static void set(int db) {
Deque<Integer> deque = THREAD_DB.get();
if (deque == null) {
deque = new ArrayDeque<>();
}
deque.addFirst(db);
THREAD_DB.set(deque);
}
/**
* @return get current redis db
*/
public static Integer get() {
Deque<Integer> deque = THREAD_DB.get();
if (CollectionUtils.isEmpty(deque)) {
return null;
}
return deque.getFirst();
}
/**
* 清理
*/
public static void clear() {
Deque<Integer> deque = THREAD_DB.get();
if (deque == null || deque.size() <= 1) {
THREAD_DB.remove();
return;
}
deque.removeFirst();
THREAD_DB.set(deque);
}
}
package com.yebuxiu.helper;
import com.yebuxiu.template.DynamicRedisTemplate;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.util.Assert;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Redis操作工具类 集成封装一些常用方法
*/
public class RedisHelper implements InitializingBean {
/**
* 不设置过期时长
*/
public static final long NOT_EXPIRE = -1;
private final DynamicRedisTemplate<String, String> redisTemplate;
public RedisHelper(DynamicRedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(redisTemplate, "redisTemplate must not be null.");
}
public RedisTemplate<String, String> getRedisTemplate() {
return redisTemplate;
}
public Map<Object, RedisTemplate<String, String>> getRedisTemplates() {
return redisTemplate.getRedisTemplates();
}
protected ValueOperations<String, String> getValueOperations() {
return redisTemplate.opsForValue();
}
/**
* 设置当前线程操作 redis database,同一个线程内操作多次redis,不同database,
* 需要调用 {@link RedisHelper#clearCurrentDatabase()} 清除当前线程redis database,从而使用默认的db.
* 如果静态RedisHelper进行db切换,这是不被允许的,需要抛出异常
*
* @param database redis database
*/
public void setCurrentDatabase(int database) {
// logger.warn("Use default RedisHelper, you'd better use a DynamicRedisHelper instead.");
// throw new RuntimeException("static redisHelper can't change db.");
RedisDatabaseThreadLocalHelper.set(database);
}
/**
* 清除当前线程 redis database.
*/
public void clearCurrentDatabase() {
// logger.warn("Use default RedisHelper, you'd better use a DynamicRedisHelper instead.");
RedisDatabaseThreadLocalHelper.clear();
}
/**
* String 设置值
*
* @param key key
* @param value value
* @param expire 过期时间
*/
public void strSet(String key, String value, long expire, TimeUnit timeUnit) {
getValueOperations().set(key, value);
if (expire != NOT_EXPIRE) {
setExpire(key, expire, timeUnit == null ? TimeUnit.SECONDS : timeUnit);
}
}
/**
* String 获取值
*
* @param key key
*/
public String strGet(String key) {
return getValueOperations().get(key);
}
/**
* 设置过期时间
*
* @param key key
* @param expire 存活时长
* @param timeUnit 时间单位
*/
public Boolean setExpire(String key, long expire, TimeUnit timeUnit) {
return redisTemplate.expire(key, expire, timeUnit == null ? TimeUnit.SECONDS : timeUnit);
}
public void strSetWithDb(int db, String key, String value, long expire, TimeUnit timeUnit) {
try {
setCurrentDatabase(db);
strSet(key,value,expire,timeUnit);
} finally {
clearCurrentDatabase();
}
}
public String strGetWithDb(int db, String key) {
try {
setCurrentDatabase(db);
return strGet(key);
} finally {
clearCurrentDatabase();
}
}
}
package com.yebuxiu.config;
import com.yebuxiu.config.properties.MyRedisProperties;
import com.yebuxiu.helper.RedisHelper;
import com.yebuxiu.template.DynamicRedisTemplate;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* stone-redis自动配置,必须要容器中有RedisConnectionFactory才启动该配置类
*/
@Configuration
@EnableConfigurationProperties(MyRedisProperties.class)
@ConditionalOnClass(name = {"org.springframework.data.redis.connection.RedisConnectionFactory"})
public class EnhanceDataRedisAutoConfiguration {
/**
* 注入RedisTemplate,key-value都使用string类型
* RedisConnectionFactory由对应的spring-boot-autoconfigure自动配置到容器
*/
@Bean
@ConditionalOnMissingBean(RedisTemplate.class)
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
buildRedisTemplate(redisTemplate, redisConnectionFactory);
return redisTemplate;
}
// /**
// * 注入StringRedisTemplate,key-value序列化器都使用String序列化器
// */
// @Bean
// @ConditionalOnMissingBean(StringRedisTemplate.class)
// public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
// StringRedisTemplate redisTemplate = new StringRedisTemplate();
// buildRedisTemplate(redisTemplate, redisConnectionFactory);
// return redisTemplate;
// }
/**
* 通过Redis连接工厂构建一个RedisTemplate
*/
private static void buildRedisTemplate(RedisTemplate<String, String> redisTemplate,
RedisConnectionFactory redisConnectionFactory) {
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setStringSerializer(stringRedisSerializer);
redisTemplate.setDefaultSerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(stringRedisSerializer);
redisTemplate.setConnectionFactory(redisConnectionFactory);
}
/**
* 默认数据源的动态redisHelper
* 这些参数由lettuce客户端帮我们自动配置并注入到容器
*/
@Primary
@Bean(name = {"redisHelper", "default", "default-helper"})
public RedisHelper dynamicRedisHelper(StringRedisTemplate redisTemplate,
MyRedisProperties myRedisProperties,
ObjectProvider<List<LettuceClientConfigurationBuilderCustomizer>> builderCustomizers) {
// 构建动态RedisTemplate工厂
DynamicRedisTemplateFactory<String, String> dynamicRedisTemplateFactory =
new DynamicRedisTemplateFactory<>(myRedisProperties,
builderCustomizers.getIfAvailable());
// ======================================================================================================
// 这里在注入的时候默认值注入一个默认的redisTemplate,以及将这个redisTemplate放入到map中,该redisTemplate
// 操作的是配置文件中使用spring.redis.database属性指定的db(若不显示指定,则使用的0号db)
// 注入的时候值放入这个默认的的redisTemplate到map中,在使用的时候如果需要动态切换库那么会通过连接工厂
// 重新去创建一个redisTemplate,并缓存到map中(懒加载模式),并不会一次性创建出多个redisTemplate然后缓存起来(连接很昂贵)
// ======================================================================================================
DynamicRedisTemplate<String, String> dynamicRedisTemplate = new DynamicRedisTemplate<>(dynamicRedisTemplateFactory);
// 当不指定库时,默认使用的RedisTemplate来操作Redis(直接获取容器中的)
dynamicRedisTemplate.setDefaultRedisTemplate(redisTemplate);
Map<Object, RedisTemplate<String, String>> map = new HashMap<>(8);
// 配置文件中指定使用几号db
map.put(myRedisProperties.getRedisProperties().getDatabase(), redisTemplate);
// 将redisTemplate缓存起来
dynamicRedisTemplate.setRedisTemplates(map);
return new RedisHelper(dynamicRedisTemplate);
}
}