前段時間寫了一篇SSM三大架構的整合教程,
今天就順便在這個基礎上繼續整合下redis吧。
第一步,加入相關依賴包,pom.xml:
<!--redis-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.6.1.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.3</version>
</dependency>
第二步,Redis的 相關配置參數redis.properties:
redis.host=127.0.0.1
redis.port=6379
redis.password=12345
redis.maxIdle=100
redis.maxWait=1000
redis.testOnBorrow=true
redis.timeout=100000
defaultCacheExpireTime=3600
第三步,applicationContext-Redis.xml:
這裡說明下,如果你不分開單獨一個redis的配置檔案也可以。我目前單獨分開了,是以我是web.xml裡面加載讀取的是spring-root-context.xml檔案,而spring-root-context.xml檔案内再去加載讀取其他的配置檔案,

好,繼續看我們的applicationContext-Redis.xml,
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--引入Redis配置檔案-->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath*:redis.properties</value>
</list>
</property>
<property name="ignoreUnresolvablePlaceholders" value="true" />
</bean>
<!-- jedis 連接配接池配置 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}"/>
<property name="maxWaitMillis" value="${redis.maxWait}"/>
<property name="testOnBorrow" value="${redis.testOnBorrow}"/>
</bean>
<!-- redis連接配接工廠 -->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="poolConfig" ref="poolConfig"/>
<property name="port" value="${redis.port}"/>
<property name="hostName" value="${redis.host}"/>
<property name="password" value="${redis.password}"/>
<property name="timeout" value="${redis.timeout}"></property>
</bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
</property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
</property>
<!--開啟事務 -->
<property name="enableTransactionSupport" value="true"></property>
</bean>
<!-- 緩存攔截器配置 -->
<bean id="methodCacheInterceptor" class="com.springmvc.interceptor.MethodCacheInterceptor">
<property name="redisUtil" ref="redisUtil"/>
<property name="defaultCacheExpireTime" value="${defaultCacheExpireTime}"/>
<!-- 禁用緩存的類名清單 -->
<property name="targetNamesList">
<list>
<value></value>
</list>
</property>
<!-- 禁用緩存的方法名清單 -->
<property name="methodNamesList">
<list>
<value></value>
</list>
</property>
</bean>
<bean id="redisUtil" class="com.springmvc.util.RedisUtil">
<property name="redisTemplate" ref="redisTemplate"/>
</bean>
<!--配置切面攔截方法 -->
<aop:config proxy-target-class="true">
<aop:pointcut id="controllerMethodPointcut"
expression="execution(* com.springmvc.service.impl.*.select*(..))"/>
<aop:advisor advice-ref="methodCacheInterceptor" pointcut-ref="controllerMethodPointcut"/>
</aop:config>
</beans>
OK,到了這裡配置的XML基本完畢,這時候你可以看到配置檔案裡面有些地方顯示紅色了,不慌,是因為缺少了
RedisUtil.java (對應redis的資料相關存儲讀取操作工具類 )
MethodCacheInterceptor.java(緩存過濾器,可以看到配置檔案裡面,我們采取了AOP方式配置切面進行攔截)
那麼接下來加入RedisUtil.java:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
// ============================Common=============================
/**
* 功能描述:根據key 設定過期時間
* @param key
* @param time
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 功能描述:根據key 擷取過期時間
*
* @param key 鍵,不能為null
* @return 時間(s) 傳回0 代表永久有效
* @author Jesson
* @date 2018/8/27 10:10
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 功能描述:判斷key是否存在
*
* @param key
* @return true 存在 false不存在
* @author Jesson
* @date 2018/8/27 10:12
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 功能描述:删除緩存
*
* @param key 可為多個
* @return
* @author Jesson
* @date 2018/8/27 10:13
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通緩存擷取
*
* @param key 鍵
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通緩存放入
*
* @param key 鍵
* @param value 值
* @return true成功 false失敗
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通緩存放入并設定時間
*
* @param key 鍵
* @param value 值
* @param time 時間(秒) time要大于0 如果time小于等于0 将設定無限期
* @return true成功 false 失敗
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 遞增
*
* @param key 鍵
* @param delta 要增加幾(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("遞增因子必須大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 遞減
*
* @param key 鍵
* @param delta 要減少幾(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("遞減因子必須大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
*
* @param key 鍵 不能為null
* @param item 項 不能為null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 擷取hashKey對應的所有鍵值
*
* @param key 鍵
* @return 對應的多個鍵值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key 鍵
* @param map 對應多個鍵值
* @return true 成功 false 失敗
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并設定時間
*
* @param key 鍵
* @param map 對應多個鍵值
* @param time 時間(秒)
* @return true成功 false失敗
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一張hash表中放入資料,如果不存在将建立
*
* @param key 鍵
* @param item 項
* @param value 值
* @return true 成功 false失敗
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一張hash表中放入資料,如果不存在将建立
*
* @param key 鍵
* @param item 項
* @param value 值
* @param time 時間(秒) 注意:如果已存在的hash表有時間,這裡将會替換原有的時間
* @return true 成功 false失敗
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 鍵 不能為null
* @param item 項 可以使多個 不能為null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判斷hash表中是否有該項的值
*
* @param key 鍵 不能為null
* @param item 項 不能為null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash遞增 如果不存在,就會建立一個 并把新增後的值傳回
*
* @param key 鍵
* @param item 項
* @param by 要增加幾(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash遞減
*
* @param key 鍵
* @param item 項
* @param by 要減少記(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根據key擷取Set中的所有值
*
* @param key 鍵
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根據value從一個set中查詢,是否存在
*
* @param key 鍵
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将資料放入set緩存
*
* @param key 鍵
* @param values 值 可以是多個
* @return 成功個數
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set資料放入緩存
*
* @param key 鍵
* @param time 時間(秒)
* @param values 值 可以是多個
* @return 成功個數
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 擷取set緩存的長度
*
* @param key 鍵
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值為value的
*
* @param key 鍵
* @param values 值 可以是多個
* @return 移除的個數
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 擷取list緩存的内容
*
* @param key 鍵
* @param start 開始
* @param end 結束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 擷取list緩存的長度
*
* @param key 鍵
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通過索引 擷取list中的值
*
* @param key 鍵
* @param index 索引 index>=0時, 0 表頭,1 第二個元素,依次類推;index<0時,-1,表尾,-2倒數第二個元素,依次類推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入緩存
*
* @param key 鍵
* @param value 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入緩存
*
* @param key 鍵
* @param value 值
* @param time 時間(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入緩存
*
* @param key 鍵
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入緩存
*
* @param key 鍵
* @param value 值
* @param time 時間(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根據索引修改list中的某條資料
*
* @param key 鍵
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N個值為value
*
* @param key 鍵
* @param count 移除多少個
* @param value 值
* @return 移除的個數
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 功能描述:擷取所有鍵值
*
*/
public Set<String> getKeys(String pattern) {
return redisTemplate.keys(pattern);
}
public void setRedisTemplate(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public RedisTemplate getRedisTemplate() {
return redisTemplate;
}
}
然後是, MethodCacheInterceptor.java:
import com.springmvc.util.RedisUtil;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.util.List;
/**
* @Author : JCccc
* @CreateTime : 2019/8/1
* @Description :
**/
/**
* Redis緩存過濾器
*/
public class MethodCacheInterceptor implements MethodInterceptor {
private RedisUtil redisUtil;
private List<String> targetNamesList; // 禁用緩存的類名清單
private List<String> methodNamesList; // 禁用緩存的方法清單
private String defaultCacheExpireTime; // 緩存預設的過期時間
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object value = null;
String targetName = invocation.getThis().getClass().getName();
String methodName = invocation.getMethod().getName();
if (!isAddCache(targetName, methodName)) {
// 跳過緩存傳回結果
return invocation.proceed();
}
Object[] arguments = invocation.getArguments();
String key = getCacheKey(targetName, methodName, arguments);
try {
// 判斷是否有緩存
if (redisUtil.hasKey(key)) {
return redisUtil.get(key);
}
// 寫入緩存
value = invocation.proceed();
if (value != null) {
final String tkey = key;
final Object tvalue = value;
new Thread(new Runnable() {
@Override
public void run() {
redisUtil.set(tkey, tvalue, Long.parseLong(defaultCacheExpireTime));
}
}).start();
}
} catch (Exception e) {
e.printStackTrace();
if (value == null) {
return invocation.proceed();
}
}
return value;
}
/**
* 是否加入緩存
*
* @return
*/
private boolean isAddCache(String targetName, String methodName) {
boolean flag = true;
if (targetNamesList.contains(targetName)
|| methodNamesList.contains(methodName) || targetName.contains("$$EnhancerBySpringCGLIB$$")) {
flag = false;
}
return flag;
}
/**
* 建立緩存key
*
* @param targetName
* @param methodName
* @param arguments
*/
private String getCacheKey(String targetName, String methodName,
Object[] arguments) {
StringBuffer sbu = new StringBuffer();
sbu.append(targetName).append("_").append(methodName);
if ((arguments != null) && (arguments.length != 0)) {
for (int i = 0; i < arguments.length; i++) {
sbu.append("_").append(arguments[i]);
}
}
return sbu.toString();
}
public void setRedisUtil(RedisUtil redisUtil) {
this.redisUtil = redisUtil;
}
public void setTargetNamesList(List<String> targetNamesList) {
this.targetNamesList = targetNamesList;
}
public void setMethodNamesList(List<String> methodNamesList) {
this.methodNamesList = methodNamesList;
}
public void setDefaultCacheExpireTime(String defaultCacheExpireTime) {
this.defaultCacheExpireTime = defaultCacheExpireTime;
}
}
OK,整合全部完成,接下來可以進行測試下,看看是什麼效果。
在進行測試之前,先慣例說下流程:
我們是想将某些方法從mysql資料庫查詢資料,然後又想理由redis緩存技術。那麼就是,在查資料之前,先要去redis資料庫裡面看下,是否對應的緩存資料存在,如果存在就沒必要去mysql資料庫查詢了,直接取出資料傳回即可;如果不存在,則需要去mysql資料查詢出相關資料,然後順便也存入redis緩存中(這樣下次就能直接從緩存讀取)。
正因為我們想實作上述的流程邏輯,是以我們利用AOP進行了緩存過濾器,也就是在過濾器配置的相關方法,我們都會進行AOP切面攔截,目前我在代碼裡面配置的是我的service層裡面的實作類impl裡面帶有‘select’的方法,
好了,一下子沒忍住又啰嗦了很多,我們來開始測試,
随便在一個controller裡面寫個測試接口,調用一個查詢的方法:
@Autowired
RedisUtil redisUtil;
@RequestMapping(value = "/getMessageBoardData", produces = "application/json; charset=utf-8")
public String getMessageBoardData() {
Messageboard messageboard = messageboardServiceImpl.selectByPrimaryKey(5);
return messageboard.toString();
}
OK,我們項目跑起來,然後先看看我們的Redis資料庫(沒有任何值):
然後我們調用接口, 可以看到已經查出相關資料了,而且看看控制台,是執行了MYSQL語句查詢的,因為我們原先的redis裡面啥資料都沒緩存:
然後緊接着,我們去看看本地的redis資料庫,發現多了一個存入的值:
沒錯,這個正是我們的緩存過濾器存進去的(TTL是指緩存資料的存在時長,機關秒,使我們自己設定的時長,可以回過頭看看代碼)。
是以我們繼續調用剛剛的接口,再查一次,發現資料一樣是查詢出來了,但是控制台沒有新的執行SQL語句日志列印:
沒錯,這就是因為每次調用查詢方法的時候,緩存過濾器都在啟作用,第一次已經把資料存進redis了,是以第二次查詢的時候,就直接從redis取出緩存資料了,就是緩存過濾器的這段代碼:
OK,測試完畢。
PS:以下補充一些調用Redis.java對資料的存入,查詢,删除等接口測試,畢竟可能有些小夥伴整合redis就是單純想存一些資料和查詢一下。
@RequestMapping(value = "/operationRedisData", produces = "application/json; charset=utf-8")
public void operationRedisData() {
//簡單舉例調用下 redisUtil 裡面的一些方法,其餘都可以自己調用
redisUtil.set("test1","JCccc");
redisUtil.set("test2",123456);
Map map =new HashMap();
map.put("A","a");
map.put("B","b");
redisUtil.hmset("TEST-MAP",map);
System.out.println(redisUtil.get("test2")+" \n "+redisUtil.hget("TEST-MAP","A"));
}
調用接口,看看redis資料庫,存入了相關的值,控制台也列印出了相關的查詢值: