简易Redis工具集(待优化、扩充)

主要内容

使用注解配置的多库Redis:

  1. 基础配置;
  2. redis常用操作方法;

一个springboot的简单集成

主要maven依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
	<version>2.9.0</version>
</dependency>

yml配置文件

spring:
  redis:
    jedis:
      pool:
        # 连接池最大连接数(使用负值表示没有限制)
        max-active: 10
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1
        # 连接池中的最大空闲连接
        max-idle: 10
        # 连接池中的最小空闲连接
        min-idle: 0
    host: 127.0.0.1
    port: 6379
    # 使用数据库0,商城小程序使用的是5
    database: 0
    password: 123456
    # 其他配置项已在商城后端设定
  # 公共数据库,用于需要同步的数据
  redis-public:
    database: 5

配置项父类

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
public class BaseConfig {
    @Value("${spring.redis.jedis.pool.max-active}")
    private int redisPoolMaxActive;

    @Value("${spring.redis.jedis.pool.max-wait}")
    private int redisPoolMaxWait;

    @Value("${spring.redis.jedis.pool.max-idle}")
    private int redisPoolMaxIdle;

    @Value("${spring.redis.jedis.pool.min-idle}")
    private int redisPoolMinIdle;

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Value("${spring.redis.password}")
    private String password;

    public JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxIdle(redisPoolMaxIdle);
        poolConfig.setMinIdle(redisPoolMinIdle);
        poolConfig.setMaxTotal(redisPoolMaxActive);
        poolConfig.setMaxWaitMillis(redisPoolMaxWait);
        return poolConfig;
    }

    public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig poolConfig, int database) {
        // 单机版jedis
        RedisStandaloneConfiguration standa = new RedisStandaloneConfiguration();
        // 设置redis服务器的host或者ip地址
        standa.setHostName(host);
        standa.setPort(port);
        standa.setPassword(RedisPassword.of(password));
        standa.setDatabase(database);
        // 获得默认的连接池构造器
        // 指定jedisPoolConifig来修改默认的连接池构造器
        JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpcb = JedisClientConfiguration.builder().usePooling().poolConfig(poolConfig);
        // 通过构造器来构造jedis客户端配置
        JedisClientConfiguration jedisClientConfiguration = jpcb.build();
        // 单机配置 + 客户端配置 = jedis连接工厂
        return new JedisConnectionFactory(standa, jedisClientConfiguration);
    }
}

数据库1配置

import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
@EnableCaching
public class RedisConfig extends BaseConfig {
    @Value("${spring.redis.database}")
    private int database;

    @Bean(name = "baseJedisPoolconfig")
    public JedisPoolConfig jedisPoolConfig() {
        return super.jedisPoolConfig();
    }

    /**
     * 统一配置RedisTemplate的序列化方式,注入时应该使用@Autowrite。需要使用redis的实体类需要实现Serializable接口<br/>
     * 使用hash等集合类型存储对象时,对象内部如果存在自定义实体类,那么这些自定义实体类都需要实现Serializable接口<br/>
     * 由于String存储于redis的占用空间一般会比较大,建议都是用hash、list、set等形式存储
     *
     * @return
     */
    @Bean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory(this.jedisPoolConfig(), database));
        // 以{ "key": value }形式序列化Redis存储对象
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new FastJsonRedisSerializer<>(Object.class));
        // 开启事务支持
        template.setEnableTransactionSupport(true);
        return template;
    }
}

数据库5配置

import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
public class PublicRedisConfig extends BaseConfig {
    @Value("${spring.redis-public.database}")
    private int database;

    @Bean(name = "publicJedisPoolConfig")
    public JedisPoolConfig publicJedisPoolConfig() {
        return jedisPoolConfig();
    }

    /**
     * 公用数据源
     *
     * @return
     */
    @Bean(name = "publicRedisTemplate")
    public RedisTemplate<String, Object> publicRedisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory(publicJedisPoolConfig(), database));
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new FastJsonRedisSerializer<>(Object.class));
        template.setEnableTransactionSupport(true);
        return template;
    }
}

操作工作集接口

import com.alibaba.fastjson.JSONObject;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.List;
import java.util.Map;
import java.util.Set;

public interface BaseRedisService {
    RedisTemplate<String, Object> getRedisTemplate();

    /**
     * 是否存在key
     *
     * @param key
     * @return
     */
    boolean hasKey(String key);

    /**
     * 获取string值
     *
     * @param key
     * @return
     */
    Object getString(String key);

    /**
     * 保存string值
     *
     * @param key
     * @param value
     */
    void setString(String key, Object value);

    /**
     * 把实体类保存为hash记录
     *
     * @param key     hash关键字
     * @param t       实体类对象,必须实现java.io.Serializable接口或继承实现了此接口的父类
     * @param clear   是否需要删除原有hash键
     * @param excepts 需要忽略的字段名
     */
    <T> void saveEntityToHash(String key, T t, boolean clear, String... excepts);

    /**
     * 把异步请求结果保存为hash记录
     *
     * @param response 请求返回结果,统一使用com.alibaba.fastjson.JSONObject
     * @param key      保存的hash关键字
     * @param t        实体类对象,必须实现java.io.Serializable接口或继承实现了此接口的父类
     * @param clear    是否需要删除原有hash键
     */
    <T> void saveEntityToHash(JSONObject response, String key, T t, boolean clear);

    /**
     * 保存hash值
     *
     * @param hkey hash值的一级key
     * @param mkey hash值的内部key,这个方法只能保存内部key类型为string的数据
     * @param value 需要存储的值
     * @param <T>
     */
    <T> void saveHashValue(String hkey, String mkey, T value);

    /**
     * 查询整个hash并反射到实体类
     *
     * @param key
     */
    <T> void setHashToEntity(T t, String key, String... excepts);

    /**
     * 获取原始的hash数据,可作为数据请求接口返回值
     *
     * @param key
     * @return
     */
    Map<String, Object> getHashMapResult(String key);

    /**
     * 获取hash具体参数
     *
     * @param hkey hash值的一级key
     * @param mkey hash值的内部key,这个方法只能查询内部key类型为string的数据
     * @param <T>
     * @return
     */
    <T> T getHashValue(String hkey, String mkey);

    /**
     * 把hash的值转成com.alibaba.fastjson.JSONObject
     *
     * @param key
     * @return
     */
    JSONObject getJsonObjectHashResult(String key);

    /**
     * 获取list
     *
     * @param key
     * @return
     */
    <T> List<T> getList(String key);

    /**
     * 保存list
     *
     * @param list
     * @param key
     */
    <T> void saveList(List<T> list, String key);

    /**
     * 添加参数到指定list
     *
     * @param t
     * @param key
     */
    <T> void addObjToList(T t, String key);

    /**
     * 删除单个key
     *
     * @param key
     */
    void remove(String key);

    /**
     * 批量删除字符串形式的key,目前都使用String作为key的类型
     *
     * @param keys
     */
    void remove(Set<String> keys);

    /**
     * 模糊查找pattern匹配的key,返回所有匹配的key<br/>
     * 不使用redisTemplate.keys()做查找的原因是,keys命令会造成redis阻塞<br/>
     * 而scan的缺陷是查找结果有可能在cursor迭代过程中被修改,因此这个方法更适用于查找后删除keys
     *
     * @param pattern 需要匹配的key字符串
     * @param count   匹配多少个,默认1000,对目前的需求已满足
     * @param unmatch 反向匹配,即不包含,不传或false时默认是匹配返回字符串结果
     * @return
     */
    Set<String> scan(String pattern, Long count, Boolean unmatch);
}

操作工作集实现类,只展示其一,方法实现有待优化,不同实现类代码基本重复

import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;

/**
 * 手动控制事务,不使用Spring的注解
 */
@Service
public class RedisServiceImpl implements BaseRedisService {
	// 唯一区别是选择不同的redis配置
    @Resource(name = "redisTemplate")
    private RedisTemplate<String, Object> redisTemplate;

    public RedisTemplate<String, Object> getRedisTemplate() {
        return redisTemplate;
    }

    @Override
    public boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    private static boolean containns(String[] array, String key) {
        for (Object item : array) {
            if (item.equals(key)) return true;
        }
        return false;
    }

    @Override
    public Object getString(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    @Override
    public void setString(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    @Override
    public <T> void saveEntityToHash(String key, T t, boolean clear, String... excepts) {
        redisTemplate.multi();
        if (clear)
            remove(key);
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(t.getClass());
            PropertyDescriptor[] properties = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor property : properties) {
                String fieldname = property.getName();
                // 跳过class属性和自定义的属性
                if (fieldname.equals("class") || containns(excepts, fieldname))
                    continue;
                Object value = property.getReadMethod().invoke(t);
//                System.out.println(fieldname + ": " + value);
                // 当前属性的值不为空时,保存到redis
                if (null != value)
                    redisTemplate.opsForHash().put(key, fieldname, value);
            }
            redisTemplate.exec();
            // 开启事务支持后,需要手动释放redis连接
            RedisConnectionUtils.unbindConnection(Objects.requireNonNull(redisTemplate.getConnectionFactory()));
        } catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    @Override
    public <T> void saveEntityToHash(JSONObject response, String key, T t, boolean clear) {
        redisTemplate.multi();
        if (clear)
            remove(key);
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(t.getClass());
            PropertyDescriptor[] properties = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor property : properties) {
                String fieldname = property.getName();
                // 跳过class属性和自定义的属性
                if (fieldname.equals("class"))
                    continue;
                Object value = response.get(fieldname);
                // 当前属性的值不为空时,保存到redis
                if (null != value)
                    redisTemplate.opsForHash().put(key, fieldname, value);
            }
            redisTemplate.exec();
            // 开启事务支持后,需要手动释放redis连接
            RedisConnectionUtils.unbindConnection(Objects.requireNonNull(redisTemplate.getConnectionFactory()));
        } catch (IntrospectionException e) {
            e.printStackTrace();
        }
    }

    @Override
    public <T> void saveHashValue(String hkey, String mkey, T value) {
        HashOperations<String, String, Object> hashOperations = redisTemplate.opsForHash();
        hashOperations.put(hkey, mkey, value);
    }

    @Override
    public <T> void setHashToEntity(T t, String key, String... excepts) {
        Map<Object, Object> res = redisTemplate.opsForHash().entries(key);
        BeanInfo beanInfo;
        try {
            beanInfo = Introspector.getBeanInfo(t.getClass());
            PropertyDescriptor[] properties = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor property : properties) {
                String fieldname = property.getName();
                // 跳过自定义属性
                if (containns(excepts, fieldname))
                    continue;
                if (res.containsKey(fieldname)) {
                    Method setter = property.getWriteMethod();
                    setter.invoke(t, res.get(fieldname));
                }
            }
        } catch (IntrospectionException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Map<String, Object> getHashMapResult(String key) {
        HashOperations<String, String, Object> hashOperations = redisTemplate.opsForHash();
        return hashOperations.entries(key);
    }

    @Override
    public <T> T getHashValue(String hkey, String mkey) {
        HashOperations<String, String, T> hashOperations = redisTemplate.opsForHash();
        return hashOperations.get(hkey, mkey);
    }

    @Override
    public JSONObject getJsonObjectHashResult(String key) {
        Map<String, Object> current = getHashMapResult(key);
        if (null != current && !current.isEmpty())
            return new JSONObject(current);
        return null;
    }

    @Override
    public <T> List<T> getList(String key) {
        if (!hasKey(key))
            return null;
        long size = redisTemplate.opsForList().size(key);
        return (List<T>) redisTemplate.opsForList().range(key, 0, size);
    }

    @Override
    public <T> void saveList(List<T> list, String key) {
        for (T t : list) {
            redisTemplate.opsForList().rightPush(key, t);
        }
    }

    @Override
    public <T> void addObjToList(T t, String key) {
        redisTemplate.opsForList().rightPush(key, t);
    }

    @Override
    public void remove(String key) {
        redisTemplate.delete(key);
    }

    @Override
    public void remove(Set<String> keys) {
        redisTemplate.delete(keys);
    }

    @Override
    public Set<String> scan(String pattern, Long count, Boolean unmatch) {
        Set<String> keys = redisTemplate.execute((RedisCallback<Set<String>>) conn -> {
            Set<String> temps = new HashSet<>();
            String match = "*" + pattern + "*";
            if (null != unmatch && unmatch)
                match = "*";
            Cursor<byte[]> cursor = conn.scan(new ScanOptions.ScanOptionsBuilder().match(match).count(null != count ? count : 1000).build());
            while (cursor.hasNext()) {
                String key = new String(cursor.next());
                if (null != unmatch && unmatch) {
                    if (key.indexOf(pattern) < 0) {
                        temps.add(key);
                    }
                } else {
                    temps.add(key);
                }
            }
            return temps;
        });
        return keys;
    }
}