Redis整合Spring綜合使用緩存實例

文章轉載自:http://blog.csdn.net/evankaka  

摘要:本文介紹瞭如何在spring中配置Redis,並通過Spring中AOP的思想,將緩存的方法切入到有需要進入緩存的類或方法前面。

一、Redis介紹

什麼是Redis?

      redis是一個key-value存儲系統。和Memcached類似,它支持存儲的value類型相對更多,包括string(字符串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。這些數據類型都支持push/pop、add/remove及取交集並集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎上,redis支持各種不同方式的排序。與memcached一樣,爲了保證效率,數據都是緩存在內存中。區別的是redis會週期性的把更新的數據寫入磁盤或者把修改操作寫入追加的記錄文件,並且在此基礎上實現了master-slave(主從)同步。

它有什麼特點?

(1)Redis數據庫完全在內存中,使用磁盤僅用於持久性。
(2)相比許多鍵值數據存儲,Redis擁有一套較爲豐富的數據類型。
(3)Redis可以將數據複製到任意數量的從服務器。

Redis 優勢?
 (1)異常快速:Redis的速度非常快,每秒能執行約11萬集合,每秒約81000+條記錄。
 (2)支持豐富的數據類型:Redis支持最大多數開發人員已經知道像列表,集合,有序集合,散列數據類型。這使得它非常容易解決各種各樣的問題,因爲我們知道哪些問題是可以處理通過它的數據類型更好。
(3)操作都是原子性:所有Redis操作是原子的,這保證瞭如果兩個客戶端同時訪問的Redis服務器將獲得更新後的值。
(4)多功能實用工具:Redis是一個多實用的工具,可以在多個用例如緩存,消息,隊列使用(Redis原生支持發佈/訂閱),任何短暫的數據,應用程序,如Web應用程序會話,網頁命中計數等。

Redis 缺點?

(1)單線程

(2)耗內存

二、使用實例

本文使用maven+eclipse+sping

1、引入jar包

[html] view plain copy
  1.     <!--Redis start -->  
  2. <dependency>  
  3.     <groupId>org.springframework.data</groupId>  
  4.     <artifactId>spring-data-redis</artifactId>  
  5.     <version>1.6.1.RELEASE</version>  
  6. </dependency>  
  7. <dependency>  
  8.     <groupId>redis.clients</groupId>  
  9.     <artifactId>jedis</artifactId>  
  10.     <version>2.7.3</version>  
  11. </dependency>  
  12.    <!--Redis end -->  

2、配置bean

在application.xml加入如下配置

[html] view plain copy
  1. <!-- jedis 配置 -->  
  2.    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig" >  
  3.          <property name="maxIdle" value="${redis.maxIdle}" />  
  4.          <property name="maxWaitMillis" value="${redis.maxWait}" />  
  5.          <property name="testOnBorrow" value="${redis.testOnBorrow}" />  
  6.    </bean >  
  7.   <!-- redis服務器中心 -->  
  8.    <bean id="connectionFactory"  class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >  
  9.          <property name="poolConfig" ref="poolConfig" />  
  10.          <property name="port" value="${redis.port}" />  
  11.          <property name="hostName" value="${redis.host}" />  
  12.          <property name="password" value="${redis.password}" />  
  13.          <property name="timeout" value="${redis.timeout}" ></property>  
  14.    </bean >  
  15.    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" >  
  16.          <property name="connectionFactory" ref="connectionFactory" />  
  17.          <property name="keySerializer" >  
  18.              <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />  
  19.          </property>  
  20.          <property name="valueSerializer" >  
  21.              <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />  
  22.          </property>  
  23.    </bean >  
  24.      
  25.     <!-- cache配置 -->  
  26.    <bean id="methodCacheInterceptor" class="com.mucfc.msm.common.MethodCacheInterceptor" >  
  27.          <property name="redisUtil" ref="redisUtil" />  
  28.    </bean >  
  29.    <bean id="redisUtil" class="com.mucfc.msm.common.RedisUtil" >  
  30.          <property name="redisTemplate" ref="redisTemplate" />  
  31.    </bean >  

其中配置文件redis一些配置數據redis.properties如下:

[plain] view plain copy
  1. #redis中心  
  2. redis.host=10.75.202.11  
  3. redis.port=6379  
  4. redis.password=123456  
  5. redis.maxIdle=100  
  6. redis.maxActive=300  
  7. redis.maxWait=1000  
  8. redis.testOnBorrow=true  
  9. redis.timeout=100000  
  10.   
  11. # 不需要加入緩存的類  
  12. targetNames=xxxRecordManager,xxxSetRecordManager,xxxStatisticsIdentificationManager  
  13. # 不需要緩存的方法  
  14. methodNames=  
  15.   
  16. #設置緩存失效時間  
  17. com.service.impl.xxxRecordManager= 60  
  18. com.service.impl.xxxSetRecordManager= 60  
  19. defaultCacheExpireTime=3600  
  20.   
  21. fep.local.cache.capacity =10000  

要掃這些properties文件,在application.xml加入如下配置

[plain] view plain copy
  1.  <!-- 引入properties配置文件 -->    
  2.  <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
  3.     <property name="locations">  
  4.         <list>  
  5.            <value>classpath:properties/*.properties</value>  
  6.             <!--要是有多個配置文件,只需在這裏繼續添加即可 -->  
  7.         </list>  
  8.     </property>  
  9. </bean>  

3、一些工具類

(1)RedisUtil

上面的bean中,RedisUtil是用來緩存和去除數據的實例

[java] view plain copy
  1. package com.mucfc.msm.common;  
  2.   
  3. import java.io.Serializable;  
  4. import java.util.Set;  
  5. import java.util.concurrent.TimeUnit;  
  6.   
  7. import org.apache.log4j.Logger;  
  8. import org.springframework.data.redis.core.RedisTemplate;  
  9. import org.springframework.data.redis.core.ValueOperations;  
  10.   
  11. /** 
  12.  * redis cache 工具類 
  13.  *  
  14.  */  
  15. public final class RedisUtil {  
  16.     private Logger logger = Logger.getLogger(RedisUtil.class);  
  17.     private RedisTemplate<Serializable, Object> redisTemplate;  
  18.   
  19.     /** 
  20.      * 批量刪除對應的value 
  21.      *  
  22.      * @param keys 
  23.      */  
  24.     public void remove(final String... keys) {  
  25.         for (String key : keys) {  
  26.             remove(key);  
  27.         }  
  28.     }  
  29.   
  30.     /** 
  31.      * 批量刪除key 
  32.      *  
  33.      * @param pattern 
  34.      */  
  35.     public void removePattern(final String pattern) {  
  36.         Set<Serializable> keys = redisTemplate.keys(pattern);  
  37.         if (keys.size() > 0)  
  38.             redisTemplate.delete(keys);  
  39.     }  
  40.   
  41.     /** 
  42.      * 刪除對應的value 
  43.      *  
  44.      * @param key 
  45.      */  
  46.     public void remove(final String key) {  
  47.         if (exists(key)) {  
  48.             redisTemplate.delete(key);  
  49.         }  
  50.     }  
  51.   
  52.     /** 
  53.      * 判斷緩存中是否有對應的value 
  54.      *  
  55.      * @param key 
  56.      * @return 
  57.      */  
  58.     public boolean exists(final String key) {  
  59.         return redisTemplate.hasKey(key);  
  60.     }  
  61.   
  62.     /** 
  63.      * 讀取緩存 
  64.      *  
  65.      * @param key 
  66.      * @return 
  67.      */  
  68.     public Object get(final String key) {  
  69.         Object result = null;  
  70.         ValueOperations<Serializable, Object> operations = redisTemplate  
  71.                 .opsForValue();  
  72.         result = operations.get(key);  
  73.         return result;  
  74.     }  
  75.   
  76.     /** 
  77.      * 寫入緩存 
  78.      *  
  79.      * @param key 
  80.      * @param value 
  81.      * @return 
  82.      */  
  83.     public boolean set(final String key, Object value) {  
  84.         boolean result = false;  
  85.         try {  
  86.             ValueOperations<Serializable, Object> operations = redisTemplate  
  87.                     .opsForValue();  
  88.             operations.set(key, value);  
  89.             result = true;  
  90.         } catch (Exception e) {  
  91.             e.printStackTrace();  
  92.         }  
  93.         return result;  
  94.     }  
  95.   
  96.     /** 
  97.      * 寫入緩存 
  98.      *  
  99.      * @param key 
  100.      * @param value 
  101.      * @return 
  102.      */  
  103.     public boolean set(final String key, Object value, Long expireTime) {  
  104.         boolean result = false;  
  105.         try {  
  106.             ValueOperations<Serializable, Object> operations = redisTemplate  
  107.                     .opsForValue();  
  108.             operations.set(key, value);  
  109.             redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);  
  110.             result = true;  
  111.         } catch (Exception e) {  
  112.             e.printStackTrace();  
  113.         }  
  114.         return result;  
  115.     }  
  116.   
  117.     public void setRedisTemplate(  
  118.             RedisTemplate<Serializable, Object> redisTemplate) {  
  119.         this.redisTemplate = redisTemplate;  
  120.     }  
  121. }  
(2)MethodCacheInterceptor

切面MethodCacheInterceptor,這是用來給不同的方法來加入判斷如果緩存存在數據,從緩存取數據。否則第一次從數據庫取,並將結果保存到緩存 中去。

[java] view plain copy
  1. package com.mucfc.msm.common;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileInputStream;  
  5. import java.io.InputStream;  
  6. import java.util.ArrayList;  
  7. import java.util.List;  
  8. import java.util.Properties;  
  9.   
  10. import org.aopalliance.intercept.MethodInterceptor;  
  11. import org.aopalliance.intercept.MethodInvocation;  
  12. import org.apache.log4j.Logger;  
  13.   
  14.   
  15. public class MethodCacheInterceptor implements MethodInterceptor {  
  16.     private Logger logger = Logger.getLogger(MethodCacheInterceptor.class);  
  17.     private RedisUtil redisUtil;  
  18.     private List<String> targetNamesList; // 不加入緩存的service名稱  
  19.     private List<String> methodNamesList; // 不加入緩存的方法名稱  
  20.     private Long defaultCacheExpireTime; // 緩存默認的過期時間  
  21.     private Long xxxRecordManagerTime; //  
  22.     private Long xxxSetRecordManagerTime; //  
  23.   
  24.     /** 
  25.      * 初始化讀取不需要加入緩存的類名和方法名稱 
  26.      */  
  27.     public MethodCacheInterceptor() {  
  28.         try {  
  29.              File f = new File("D:\\lunaJee-workspace\\msm\\msm_core\\src\\main\\java\\com\\mucfc\\msm\\common\\cacheConf.properties");   
  30.              //配置文件位置直接被寫死,有需要自己修改下  
  31.              InputStream in = new FileInputStream(f);   
  32. //          InputStream in = getClass().getClassLoader().getResourceAsStream(  
  33. //                  "D:\\lunaJee-workspace\\msm\\msm_core\\src\\main\\java\\com\\mucfc\\msm\\common\\cacheConf.properties");  
  34.             Properties p = new Properties();  
  35.             p.load(in);  
  36.             // 分割字符串  
  37.             String[] targetNames = p.getProperty("targetNames").split(",");  
  38.             String[] methodNames = p.getProperty("methodNames").split(",");  
  39.   
  40.             // 加載過期時間設置  
  41.             defaultCacheExpireTime = Long.valueOf(p.getProperty("defaultCacheExpireTime"));  
  42.             xxxRecordManagerTime = Long.valueOf(p.getProperty("com.service.impl.xxxRecordManager"));  
  43.             xxxSetRecordManagerTime = Long.valueOf(p.getProperty("com.service.impl.xxxSetRecordManager"));  
  44.             // 創建list  
  45.             targetNamesList = new ArrayList<String>(targetNames.length);  
  46.             methodNamesList = new ArrayList<String>(methodNames.length);  
  47.             Integer maxLen = targetNames.length > methodNames.length ? targetNames.length  
  48.                     : methodNames.length;  
  49.             // 將不需要緩存的類名和方法名添加到list中  
  50.             for (int i = 0; i < maxLen; i++) {  
  51.                 if (i < targetNames.length) {  
  52.                     targetNamesList.add(targetNames[i]);  
  53.                 }  
  54.                 if (i < methodNames.length) {  
  55.                     methodNamesList.add(methodNames[i]);  
  56.                 }  
  57.             }  
  58.         } catch (Exception e) {  
  59.             e.printStackTrace();  
  60.         }  
  61.     }  
  62.   
  63.     @Override  
  64.     public Object invoke(MethodInvocation invocation) throws Throwable {  
  65.         Object value = null;  
  66.   
  67.         String targetName = invocation.getThis().getClass().getName();  
  68.         String methodName = invocation.getMethod().getName();  
  69.         // 不需要緩存的內容  
  70.         //if (!isAddCache(StringUtil.subStrForLastDot(targetName), methodName)) {  
  71.         if (!isAddCache(targetName, methodName)) {  
  72.             // 執行方法返回結果  
  73.             return invocation.proceed();  
  74.         }  
  75.         Object[] arguments = invocation.getArguments();  
  76.         String key = getCacheKey(targetName, methodName, arguments);  
  77.         System.out.println(key);  
  78.   
  79.         try {  
  80.             // 判斷是否有緩存  
  81.             if (redisUtil.exists(key)) {  
  82.                 return redisUtil.get(key);  
  83.             }  
  84.             // 寫入緩存  
  85.             value = invocation.proceed();  
  86.             if (value != null) {  
  87.                 final String tkey = key;  
  88.                 final Object tvalue = value;  
  89.                 new Thread(new Runnable() {  
  90.                     @Override  
  91.                     public void run() {  
  92.                         if (tkey.startsWith("com.service.impl.xxxRecordManager")) {  
  93.                             redisUtil.set(tkey, tvalue, xxxRecordManagerTime);  
  94.                         } else if (tkey.startsWith("com.service.impl.xxxSetRecordManager")) {  
  95.                             redisUtil.set(tkey, tvalue, xxxSetRecordManagerTime);  
  96.                         } else {  
  97.                             redisUtil.set(tkey, tvalue, defaultCacheExpireTime);  
  98.                         }  
  99.                     }  
  100.                 }).start();  
  101.             }  
  102.         } catch (Exception e) {  
  103.             e.printStackTrace();  
  104.             if (value == null) {  
  105.                 return invocation.proceed();  
  106.             }  
  107.         }  
  108.         return value;  
  109.     }  
  110.   
  111.     /** 
  112.      * 是否加入緩存 
  113.      *  
  114.      * @return 
  115.      */  
  116.     private boolean isAddCache(String targetName, String methodName) {  
  117.         boolean flag = true;  
  118.         if (targetNamesList.contains(targetName)  
  119.                 || methodNamesList.contains(methodName)) {  
  120.             flag = false;  
  121.         }  
  122.         return flag;  
  123.     }  
  124.   
  125.     /** 
  126.      * 創建緩存key 
  127.      * 
  128.      * @param targetName 
  129.      * @param methodName 
  130.      * @param arguments 
  131.      */  
  132.     private String getCacheKey(String targetName, String methodName,  
  133.             Object[] arguments) {  
  134.         StringBuffer sbu = new StringBuffer();  
  135.         sbu.append(targetName).append("_").append(methodName);  
  136.         if ((arguments != null) && (arguments.length != 0)) {  
  137.             for (int i = 0; i < arguments.length; i++) {  
  138.                 sbu.append("_").append(arguments[i]);  
  139.             }  
  140.         }  
  141.         return sbu.toString();  
  142.     }  
  143.   
  144.     public void setRedisUtil(RedisUtil redisUtil) {  
  145.         this.redisUtil = redisUtil;  
  146.     }  
  147. }  

4、配置需要緩存的類或方法

在application.xml加入如下配置,有多個類或方法可以配置多個

[html] view plain copy
  1. <!-- 需要加入緩存的類或方法 -->  
  2. <bean id="methodCachePointCut"  class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" >  
  3.       <property name="advice" >  
  4.           <ref local="methodCacheInterceptor" />  
  5.       </property>  
  6.       <property name="patterns" >  
  7.           <list>  
  8.            <!-- 確定正則表達式列表 -->  
  9.              <value>com\.mucfc\.msm\.service\.impl\...*ServiceImpl.*</value >  
  10.           </list>  
  11.       </property>  
  12. </bean >  

5、執行結果:

寫了一個簡單的單元測試如下:

[java] view plain copy
  1. @Test  
  2. public void getSettUnitBySettUnitIdTest() {  
  3.     String systemId = "CES";  
  4.     String merchantId = "133";  
  5.     SettUnit configSettUnit = settUnitService.getSettUnitBySettUnitId(systemId, merchantId, "ESP");  
  6.     SettUnit configSettUnit1 = settUnitService.getSettUnitBySettUnitId(systemId, merchantId, "ESP");  
  7.     boolean flag= (configSettUnit == configSettUnit1);  
  8.     System.out.println(configSettUnit);  
  9.     logger.info("查找結果" + configSettUnit.getBusinessType());  
  10.     
  11.   //  localSecondFIFOCache.put("configSettUnit", configSettUnit.getBusinessType());  
  12.  //  String string = localSecondFIFOCache.get("configSettUnit");  
  13.       logger.info("查找結果" + string);  
  14. }  
這是第一次執行單元測試的過程:

MethodCacheInterceptor這個類中打了斷點,然後每次查詢前都會先進入這個方法


依次運行,發現沒有緩存,所以會直接去查數據庫

打印了出來的SQL語句:


第二次執行:

因爲第一次執行時,已經寫入緩存了。所以第二次直接從緩存中取數據


3、取兩次的結果進行地址的對比:

發現兩個不是同一個對象,沒錯,是對的。如果是使用ehcache的話,那麼二者的內存地址會是一樣的。那是因爲redis和ehcache使用的緩存機制是不一樣的。ehcache是基於本地電腦的內存使用緩存,所以使用緩存取數據時直接在本地電腦上取。轉換成Java對象就會是同一個內存地址,而redis它是在裝有redis服務的電腦上(一般是另一臺電腦),所以取數據時經過傳輸到本地,會對應到不同的內存地址,所以用==來比較會返回false。但是它確實是從緩存中去取的,這點我們從上面的斷點可以看到。


10
 
0
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章