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。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM