Redis七:SpringMVC项目集成Redis

记录传统SpringMVC项目集成Redis,总结一下遇到的坑和异常原因。
如果有疑问或者感觉哪里有问题欢迎指点,一起探讨。

一:选择合适的jar包

选择合适的jar包,而且如果spring和redis这两个jar包版本不对应的话运行中会报错。

以下是我使用的版本。

<jedis.version>2.8.1</jedis.version>
<commons.pool2.version>2.4.2</commons.pool2.version>
<spring.redis.version>1.7.5.RELEASE</spring.redis.version>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>${spring.redis.version}</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>${commons.pool2.version}</version>
</dependency>

查看spring对应的redis信息可以去 Maven Repository 官方查询

 选择版本点进去在页面下方可以看到对应的redis版本信息:

 因为本人比较习惯使用spring低版本中的配置文件里的 JedisConnectionFactory 所以并没有选择使用高版本的 jar。

二:redis 配置文件信息

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
                     http://www.springframework.org/schema/beans
                     http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <!--  properties参数文件路径  -->
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:config/*.properties</value>
            </list>
        </property>
    </bean>

    <!-- redis数据源 -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!-- 最大空闲数 -->
        <property name="maxIdle" value="${redis.pool.maxIdle}" />
        <!-- 最大空连接数 -->
        <property name="maxTotal" value="${redis.pool.maxTotal}" />
        <!-- 最大等待时间 -->
        <property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}" />
        <!-- 返回连接时,检测连接是否成功 -->
        <property name="testOnBorrow" value="${redis.pool.testOnBorrow}" />
        <!-- 返回数据时,检测连接是否成功 -->
        <property name="testOnReturn" value="${redis.pool.testOnReturn}" />
    </bean>

    <!-- Spring-redis连接池管理工厂 -->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <!-- IP地址 -->
        <property name="hostName" value="${redis.master.ip}" />
        <!-- 端口号 -->
        <property name="port" value="${redis.master.port}" />
        <property name="password" value="${redis.master.password}" />
        <!-- 超时时间 默认2000-->
        <property name="timeout" value="${redis.master.timeout}" />
        <!-- 连接池配置引用 -->
        <property name="poolConfig" ref="poolConfig" />
        <!-- usePool:是否使用连接池 -->
        <property name="usePool" value="true"/>
    </bean>

    <!-- redis template definition -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory" />
        <property name="keySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
        </property>
        <property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
        </property>
        <property name="hashKeySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
        </property>
        <property name="hashValueSerializer">
            <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
        </property>
        <!--开启事务  -->
     <!--<property name="enableTransactionSupport" value="true"></property>-->
    </bean>

    <!--自定义redis工具类,在需要缓存的地方注入此类  -->
    <bean id="redisService" class=" com.xxx.xxx.xxx.xxx.RedisUtils">
        <constructor-arg name="redisTemplate" ref="redisTemplate" />
    </bean>
</beans>

三:redis.properties 数据文件参数

这里的参数是我们线上系统的使用参数,数值比较大,如果只是小系统使用可以设置的小一点,如果太小的话运行会报异常,按需调整就行。

#redis.master.ip=127.0.0.1
#redis.master.port=6379
#redis.master.password=123456

# JEDIS_BorrowPool:maxActive
redis.pool.maxActive=1024
# JEDIS_BorrowPool:maxIdle
redis.pool.maxIdle=200
# JEDIS_BorrowPool:maxTotal
redis.pool.maxTotal=200
# JEDIS_BorrowPool:maxWaitMillis
redis.pool.maxWaitMillis=7200
# JEDIS_BorrowPool:testOnBorrow
redis.pool.testOnBorrow=false
# JEDIS_BorrowPool:testOnReturn
redis.pool.testOnReturn=false
# JEDIS_Connection:timeout
redis.master.timeout=10000

四:RedisUtils工具类

(其中指定 RedisSerializer 序列化,这个序列化程序序列化出的数据比较直观,不会乱码,在Redis Desktop Manager 软件上可以直接查看数据详情)。

package com.xxx.xxx.service.impl;

import com.xxx.xxx.xxx.utils.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.Jedis;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author ShouSi
 * @Title: Redis工具类Service,使用Spring整合,redisTemplate:事务操作,匿名内部类操作
 * @date 2021/12/03 17:51
 */
public class RedisUtils {

    private static final Logger logger = LogManager.getLogger(RedisUtils.class);

    private static RedisTemplate<String, Object> redisTemplate;

    public RedisUtils(RedisTemplate redisTemplate) {
        RedisSerializer stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(stringSerializer);
        this.redisTemplate = redisTemplate;
    }

    /**
     * 获取所有key,支持正则表达式
     * @param key
     * @return Set<String> key
     */
    public static Set<String> keys(String key) {
        Set<String> keys = null;
        if(StringUtils.isBlank(key)){
            key = "*";
        }
        try {
            keys = redisTemplate.keys(key);
        } catch (Exception e) {
            logger.error("keys:获取所有key异常,key:{}", key,e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
        return keys;
    }

    /**
     * 向key指向的set集合中插入若干条数据
     * @param key
     * @param members
     */
    public static void sadd(String key, String... members) {
        //redis操作发生异常时要把异常捕获,不要响应正常的业务逻辑
        try {
            Long add = redisTemplate.boundSetOps(key).add(members);
        } catch (Exception e) {
            logger.error("sadd:向缓存中添加数据时出现异常,key:{} value:{}", key, members,e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }

    /**
     * 从key指向的set集合中取出所有数据并删除此key指向的set集合
     * @param key
     * @return
     */
    @SuppressWarnings("unchecked")
    public static Set<String> smembersAndDel(String key) {
        Set<String> set = null;
        try {
            Object o = redisTemplate.execute(new SessionCallback<Object>() {
                @Override
                public Object  execute(RedisOperations redisOperations) throws DataAccessException {
                    redisOperations.multi();
                    //1、获取数据
                    redisOperations.opsForSet().members(key);
                    //2、删除数据
                    redisOperations.delete(key);

                    final List<Object> result = redisOperations.exec();
                    //返回vKey的值
                    return result.get(1).toString();
                }
            });
            set = (Set<String>) o ;
        } catch (Exception e) {
            logger.error("smembersAndDel:从缓存中取出数据或者删除数据是出现异常", e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
        return set;
    }

//    /**
//     * 添加一对key:value,并设置过期时间
//     * @param key 键
//     * @param expire 过期时间
//     * @param value 值
//     */
//    public static Boolean setex(String key, int expire, String value) {
//        Boolean boo = false;
//        try {
//            boo = redisTemplate.opsForValue().setIfAbsent(key, value, expire);
//
//        } catch (Exception e) {
//            logger.error("setex:向缓存中添加数据时出现异常,key:{},value:{},过期时间:{}秒", key, value, expire, e);
//        } finally {
//            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
//        }
//        return boo;
//    }

    /**
     * 获得key指向的value
     * @param key
     * @return
     */
    public static String get(String key) {
        String value = null;
        try {
            Object o = redisTemplate.opsForValue().get(key);
            value = o.toString();
        } catch (Exception e) {
            logger.error("get:从缓存中获取数据时出现异常,key:{},value:{}", key, value, e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
        return value;
    }

    /**
     * 设置值,如果key存在就覆盖原有的值
     * @param key
     * @param value
     */
    public static void set(String key, String value){
        String resp = null;
        try {
            redisTemplate.opsForValue().set(key,value);
        } catch (Exception e) {
            logger.error("set:向缓存中添加数据时出现异常,key:{},value:{}", key, value, e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }

    /**
     * 查看某个key的剩余生存时间,单位【秒】.
     * @param key
     * @return  永久生存返回-1 没有该值返回-2
     */
    public static Long ttl(String key){
        Long expire = null;
        try {
            expire = redisTemplate.opsForValue().getOperations().getExpire(key);
        } catch (Exception e) {
            logger.error("ttl:查询key剩余过期时间出现异常,key:{}", key,e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
        return expire;
    }

    /**
     * 查询键是否存在
     * @param key
     * @return boolean
     */
    public static boolean exists (String key){
        Boolean exist = null;
        try {
            exist = redisTemplate.hasKey(key);
            if(exist){
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            logger.error("exists:从缓存中判断key是否存在时出现异常,key:{}", key,e);
            throw new RuntimeException(e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }

    /**
     * 根据key的前缀删除所有相关的key
     * @param keyPattern 参数支持正则表达式
     */
    public static void del(String keyPattern) {
        try {
            Set<String> keys = keys(keyPattern);
            if (keys != null && keys.size() > 0) {
                for (String key : keys) {
                    redisTemplate.delete(key);
                }
            }
        } catch (Exception e) {
            logger.error("del:从缓存中删除数据时出现异常,keyPattern:{}", keyPattern,e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }

    /**
     * 给指定key设置过期时间,秒或毫秒
     * @param keyPattern key
     * @param seconds 过期时间
     * @param timeUnit TimeUtil
     */
    public static Boolean expire(String keyPattern,int seconds,TimeUnit timeUnit) {
        Boolean expire = false;
        try {
            expire = redisTemplate.expire(keyPattern, seconds, timeUnit);
        } catch (Exception e) {
            logger.error("expire:设置到期时间异常,keyPattern:{},seconds:{}", keyPattern,seconds,e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
        return expire;
    }

    /**
     * 给指定key设置过期时间
     * @param keyPattern key
     * @param date 时间
     */
    public static Boolean expireAt(String keyPattern, Date date) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Boolean expire = false;
        try {
            expire = redisTemplate.expireAt(keyPattern, date);
        } catch (Exception e) {
            logger.error("expireAt:设置到期时间异常,keyPattern:{},date:{}", keyPattern,sdf.format(date),e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
        return expire;
    }

    /** Hash散列操作 **/

    /** Hash 获取所有值 **/
    public static List<Object> hvals(String keyPattern) {
        List<Object> hvals = null;
        try {
            hvals = redisTemplate.opsForHash().values(keyPattern);
        } catch (Exception e) {
            logger.error("hvals:从缓存中查询has异常,keyPattern:{}", keyPattern,e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
        return hvals;
    }

    /** Hash单个写入 **/
    public static void hset(String key,String field,String value){
        try {
            redisTemplate.opsForHash().put(key,field,value);
        }catch (Exception e){
            logger.error("hset:写入缓存Hash异常,key:{}",key,e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }

    /** Hash多个写入 **/
    public static Boolean hmset(String key, Map<String, String> valMap){
        Boolean resp = false;
        try {
            redisTemplate.opsForHash().putAll(key,valMap);
        }catch (Exception e){
            logger.error("hmset:写入缓存Hash异常,key:{}",key,e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
        return resp;
    }

    /** Hash不存在则写入 **/
    public static Boolean hsetnx(String key,String field,String value){
        Boolean resp = false;
        try {
            resp = redisTemplate.opsForHash().putIfAbsent(key, field, value);
        }catch (Exception e){
            logger.error("hsetnx:Hash写入缓存异常,key:{}",key,e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
        return resp;
    }

    /** Hash判断字段是否存在 **/
    public static boolean hexists(String key,String field){
        Boolean resp = false;
        try {
            resp = redisTemplate.opsForHash().hasKey(key, field);
        }catch (Exception e){
            logger.error("hexists:查询缓存Hash异常,key:{}",key,e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
        return resp;
    }

    /**
     * Hash:指定key,field获取对应单条数据
     * @param key
     * @param field
     * @return
     */
    public static String hget(String key,String field){
        String hget = null;
        try {
            hget = (String)redisTemplate.opsForHash().get(key, field);
        }catch (Exception e){
            logger.error("hget:获取单个字段值Hash异常,key:{},field:{}",key,field,e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
        return hget;
    }

    /**
     * 返回哈希表中,一个或多个给定字段的值。
     * 如果指定的字段不存在于哈希表,那么返回一个 nil 值。
     * @param key
     * @param field
     * @return
     */
    public static List<Object> hmget(String key,List<Object> field){
        List<Object> hmget = null;
        try {
            hmget = redisTemplate.opsForHash().multiGet(key, field);
        }catch (Exception e){
            logger.error("hmget:Hash获取指定key,field数据缓存异常,key:{},field:{}",key,field,e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
        return hmget;
    }

    /**
     * Hash:获取指定key数据所有键值
     * @param key
     * @return
     */
    public static  Map<Object, Object> hgetall(String key){
        Jedis jedis = null;
        Map<Object, Object> resultMap = new HashMap<>();
        try {
            resultMap = redisTemplate.opsForHash().entries(key);
        }catch (Exception e){
            logger.error("hgetAll:Hash获取指定key数据所有键值失败,key:{}",key,e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
        return resultMap;
    }

    /**
     * 删除HASH缓存对应key下的单个field数据
     * @param key
     * @param field
     */
    public static Boolean hdel(String key,String field){
        Boolean result = false;
        try {
            Long hdel = redisTemplate.opsForHash().delete(key, field);
            if(hdel.equals(1)){
                result = true;
            }
        }catch (Exception e){
            logger.error("hdel:Hash删除缓存失败,key:{},field:{}",key,field,e);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
        return result;
    }

}

为了方便使用,我这里专门做了一个Hask操作的key值参数类,这样可以使数据更好的按照指定的维度进行分类,比如店铺维度,后缀加MySql店铺表id,一个店铺一个Hash表。

五:RedisKeyAndField 类

package com.xxx.xxx.domain.jedis;

/**
* @description: RedisKeyAndField
* @author: ShouSi
* @createDate: 2021/10/21
*/
public class RedisKeyAndField {
public static final String Order_Info_ = "Order_Info_";   // redis 订单 店铺维度 _后缀加店铺ID
public static final String OrderStock_Info_ = "OrderStock_Info_"; // redis 订单商品明细 店铺维度 _后缀加店铺ID
public static final String OrderGift_Info_ = "OrderGift_Info_"; // redis 订单赠品明细 店铺维度 _后缀加店铺ID
public static final String Shop_Info = "Shop_Info"; // redis 店铺
public static final String ShopCross_Info = "ShopCross_Info"; // redis 跨境店铺
public static final String Tenant_Info = "Tenant_Info"; // redis 租户
public static final String Goods_Info = "Goods_Info"; // redis 商品资料
public static final String Stock_Info = "Stock_Info"; // redis 库存
public static final String StockBatch_Info = "StockBatch_Info"; // redis 库存批次
public static final String Pretreat_Info_ = "Pretreat_Info"; // redis 预设置策略 货主维度 _后缀加tenantid
public static final String Paramset_Info = "Paramset_Info"; // redis 参数配置
public static final String Activity_Info_ = "Activity_Info_"; // redis 赠送策略 货主维度 _后缀加tenantid
public static final String StockPolicy1_ = "StockPolicy1_"; // redis 自动发货策略
public static final String StockPolicy2_ = "StockPolicy2_"; // redis 自动驳回策略
public static final String StockPolicy3_ = "StockPolicy3_"; // redis 自动合并策略
public static final String StockPolicy4_ = "StockPolicy4_"; // redis 自动拆分策略
}

注意

店铺信息,账号信息这种不经常更改的信息如果要放到redis上,如果系统有修改或者新增的操作,要同步更新redis,并且在redis上尽量给这种数据一个过期时间,过期后在查询的时候如果没有数据,去数据库查,并且同步一份到redis。

 

posted @ 2021-12-09 17:53  凉年技术  阅读(1811)  评论(0编辑  收藏  举报