一、spring整合redis
Redis作為一個時下非常流行的NOSQL語言,不學一下有點過意不去。
背景:學習Redis用到的框架是maven+spring+mybatis(框架如何搭建這邊就不敘述了)
首先在pom里面添加當前所需要的jar包,有下面幾個:
………………
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.4.2</version> </dependency> <!--mybaitis 緩存--> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-redis</artifactId> <version>1.0.0-beta2</version> </dependency>
<!-- spring data redis -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.1.RELEASE</version>
</dependency>
……………………
第一個是支持Redis的語言——Jedis包
第二個是依賴包
第三個是mybaitis自己做的一個 mybatis與redis整合的一個jar包
第四個是spring與redis整合的需要的jar包
先看spring與redis整合的配置文件,如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 5 6 7 <!--redis哨兵 --> 8 <!--<bean id="redisSentinelConfiguration" 9 class="org.springframework.data.redis.connection.RedisSentinelConfiguration"> 10 <property name="master"> 11 <bean class="org.springframework.data.redis.connection.RedisNode"> 12 <property name="name" value="mymaster"></property> 13 </bean> 14 </property> 15 <property name="sentinels"> 16 <set> 17 <bean class="org.springframework.data.redis.connection.RedisNode"> 18 <constructor-arg index="0" value="${redis.host.slave}"/> 19 <constructor-arg index="1" value="${redis.port}"/> 20 </bean> 21 <bean class="org.springframework.data.redis.connection.RedisNode"> 22 <constructor-arg index="0" value="${redis.host.master}"/> 23 <constructor-arg index="1" value="${redis.port}"/> 24 </bean> 25 </set> 26 </property> 27 </bean>--> 28 29 <bean id="jedisConnFactory" 30 class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> 31 <property name="hostName" value="${redis.host}"/> 32 <property name="port" value="${redis.port}"/> 33 <property name="password" value="${redis.password}"/> 34 <property name="usePool" value="false"/> 35 <!--<property name="poolConfig" ref="poolConfig"/>--> 36 <!--<constructor-arg ref="redisSentinelConfiguration"/>--> 37 <property name="timeout" value="10000"/> 38 </bean> 39 40 <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"> 41 <property name="connectionFactory" ref="jedisConnFactory"/> 42 </bean> 43 44 <!-- 使用中間類解決RedisCache.jedisConnectionFactory的靜態注入,從而使MyBatis實現第三方緩存 --> 45 <bean id="redisCacheTransfer" class="com.anhoo.util.RedisCacheTransfer"> 46 <property name="jedisConnectionFactory" ref="jedisConnFactory"/> 47 </bean> 48 49 </beans>
其中所涉及到的config.properties文件內容為:
redis.host=127.0.0.1
redis.port=7777
redis.password=eoooxy
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/anhoo
username=root
password=root
8-27行是配置redis集群需要的哨兵,這邊暫且不講,下次再說。
29-38是通過JedisConnectionFactory來配置redis的初始化配置。這里可以配置poolConfig,即jedis的進一步性能的配置,以及sentinel哨兵的配置,這里用到,下次再說
40-42通過jedis的配置,注入到StringRedisTemplate中,得到stringRedisTemplate bean
45-47是redis做mybatis的緩存的時候,需要的用到的,下面講到的時候具體再說。
這樣一來測試下,(這里在獲取string-redis.xml中的stringRedisTemplate bean的時候,需要把對應的配置文件引入的配置放到當前文件中,或者把hostName、post、password,直接替換成具體的值)
@Test public void redis() { Map<String, List<Map<String, String>>> hashMap = new HashMap<String, List<Map<String, String>>>(); Map<String, String> map = new HashMap<>(); map.put("name", "xueyuan"); List<Map<String, String>> list = new ArrayList<Map<String, String>>(); list.add(map); hashMap.put("hashName", list); ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-redis.xml"); StringRedisTemplate template = ctx.getBean(StringRedisTemplate.class); template.opsForValue().set("name", "eoooxy"); template.opsForHash().put("hash", "name", hashMap.toString()); System.out.println(template.opsForValue().get("name")); System.out.println(template.opsForHash().get("hash", "name")); }
打印結果為:
在查看一下redis中的key,以及結果:
如果這些都成功的話,那么你的redis基本配置完成,下面只要在你的spring-content.xml 引入spring-redis.xml。每個人的這個文件名不一樣,這個文件就是在web.xml的一個配置,如下:
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-context.xml</param-value> </context-param>
這樣一來,如果在springmvc框架的service層需要用到redis的話,只需要自動注入bean就可以了,如下:
@Autowired
StringRedisTemplate stringRedisTemplate;
二、redis做mybatis的第三方緩存
mybatis自己已經做了一個兼容redis的第三方緩存的jar包,具體項目git連接: https://github.com/mybatis/redis-cache
官方描述使用jar的方法是:首先要在pom.xml中引入mybatis-redis 的jar包,前面我們已經引入了。之后只要在mybatis的Mapper中引入:
<mapper namespace="org.acme.FooMapper"> <cache type="org.mybatis.caches.redis.RedisCache" /> ... </mapper>
做好上面的操作,還需要我們在classpath 的resource下面放一個redis的配置文件:/redis.properties
下面就簡單介紹下 mybatis-redis 的工作順序,其主要有以下幾個jar包:
mybatis-redis
|----DummyReadWriteLock
|----RedisCache
|----RedisCallback
|----RedisConfig
|----RedisConfigurationBuilder
|----SerializeUtil
我們在使用的時候,先后步驟為:
- 當調用了RedisCache類,RedisCache通過他的構造函數,使得RedisConfigurationBuilder通過 ./redis.properties 來創建redis的配置文件類RedisConfig
- 這個時候RedisCache根據得到的RedisConfig,創建了PoolConfig
- RedisCache的execute方法又根據PoolConfig得到了Jedis
- 根據RedisCallBack回調Jedis做對應的get、put等等操作
但是在使用redis-cache的時候他有三個不足之處:
- 默認情況下,mybatis會為每一個mapper創建一個RedisCache,而JedisPool是在RedisCache的構造方法中創建的,這就意味着會為每一個mapper創建一個JedisPool,使用意圖和開銷上面都會有問題;
- 在很多情況下,我們的應用中也會獨立使用到redis,這樣也無法讓RedisCache直接使用我們項目中可能已經存在的JedisPool;並且會造成兩個配置文件(除非我們應用也使用redis.properties);
- RedisCache是使用hash來緩存一個Mapper中的查詢,所以我們只能通過mybatis的cache配置來控制對象的生存時間,空閑時間等屬性;而無法獨立的去配置每一個緩存區域(即每一個hash);
下面針對上面的三個不足之處進行了進一步的改進:
需要用到mybatis-redis 中SerializeUtil類(這個工具類用起來還是很好用的 :) )以及 RedisCacheTransfer類,這個類的目的是根據其 setJedisConnectionFactory 方法自動注入方式得到spring-redis.xml中的 jedisConnFactory bean,再通過的set注入方式,注入到到自定義RedisCache中的 jedisConnectionFactory(這個也就是上面spring-redis.xml 45-47行的作用)
public class RedisCacheTransfer { @Autowired public void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) { RedisCache.setJedisConnectionFactory(jedisConnectionFactory); } }
這樣一來我們就解決了 上面第二不足之處,對於第一與第三個不足之處,因為redis-cache用的是hash來緩存Mapper中的查詢,這里我們采用string數據結構來緩存。下面就根絕redis-chahe我們自己自定義一個RedisCache,又因自定義RedisCache中的jedisConnectionFactory是一個工廠模式,需要的時候就直接創建一個連接,這樣一來就不存在創建多個poolConfig了。
1 import com.anhoo.common.BaseBean; 2 import org.apache.ibatis.cache.Cache; 3 import org.springframework.data.redis.connection.jedis.JedisConnection; 4 import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; 5 import redis.clients.jedis.exceptions.JedisConnectionException; 6 7 import java.util.concurrent.locks.ReadWriteLock; 8 import java.util.concurrent.locks.ReentrantReadWriteLock; 9 10 /** 11 * Author XueYuan 12 * Data 2017/05/16 13 * Time 16:04 14 */ 15 16 public class RedisCache extends BaseBean implements Cache { 17 18 private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); 19 private String id; 20 private static JedisConnectionFactory jedisConnectionFactory; 21 22 public RedisCache(String id) { 23 if (id == null) { 24 throw new IllegalArgumentException("Cache instances require an ID"); 25 } 26 logger.debug("---------------------Mybatis RedisCache:id=" + id + "---------------------"); 27 this.id = id; 28 } 29 30 /** 31 * 清除所有數據 32 */ 33 @Override 34 public void clear() { 35 JedisConnection connection = null; 36 try { 37 connection = (JedisConnection) jedisConnectionFactory.getConnection(); 38 connection.flushAll(); 39 } catch (JedisConnectionException e) { 40 e.printStackTrace(); 41 } finally { 42 if (connection != null) { 43 connection.close(); 44 } 45 } 46 } 47 48 /** 49 * @return 50 */ 51 @Override 52 public String getId() { 53 return this.id; 54 } 55 56 /** 57 * 得到指定key的 value 58 * @param key 59 * @return object 60 */ 61 @Override 62 public Object getObject(Object key) { 63 Object result = null; 64 JedisConnection connection = null; 65 try { 66 connection = (JedisConnection) jedisConnectionFactory.getConnection(); 67 result = SerializeUtil.unserialize(connection.get(SerializeUtil.serialize(key))); 68 //result = SerializeUtil.unserialize(connection.hGet(RedisCache.this.id.toString().getBytes(), key.toString().getBytes())); 69 } catch (JedisConnectionException e) { 70 e.printStackTrace(); 71 } finally { 72 if (connection != null) { 73 connection.close(); 74 } 75 } 76 return result; 77 } 78 79 /** 80 * 得到當前db的key值 81 * @return int 82 */ 83 @Override 84 public int getSize() { 85 int result = 0; 86 JedisConnection connection = null; 87 try { 88 connection = (JedisConnection) jedisConnectionFactory.getConnection(); 89 result = Integer.valueOf(connection.dbSize().toString()); 90 // result = Integer.valueOf(connection.hGetAll(RedisCache.this.id.toString().getBytes()).size()); 91 } catch (JedisConnectionException e) { 92 e.printStackTrace(); 93 } finally { 94 if (connection != null) { 95 connection.close(); 96 } 97 } 98 return result; 99 } 100 101 /** 102 * 寫入 key-value 103 * @param key 104 * @param value 105 */ 106 @Override 107 public void putObject(Object key, Object value) { 108 JedisConnection connection = null; 109 try { 110 logger.debug("------------------Redis Put Object:" + key.toString() + ":" + value.toString() + "-------------------"); 111 connection = (JedisConnection) jedisConnectionFactory.getConnection(); 112 connection.set(SerializeUtil.serialize(key), SerializeUtil.serialize(value)); 113 // connection.hSet(RedisCache.this.id.toString().getBytes(),key.toString().getBytes(),SerializeUtil.serialize(value)); 114 } catch (JedisConnectionException e) { 115 e.printStackTrace(); 116 } finally { 117 if (connection != null) { 118 connection.close(); 119 } 120 } 121 } 122 123 /** 124 * 刪除指定key的值 125 * @param key 126 * @return 127 */ 128 @Override 129 public Object removeObject(Object key) { 130 JedisConnection connection = null; 131 Object result = null; 132 try { 133 connection = (JedisConnection) jedisConnectionFactory.getConnection(); 134 result = connection.expire(SerializeUtil.serialize(key), 0); 135 //或者 result = connection.del(SerializeUtil.serialize(key)); 136 // result = connection.hDel(RedisCache.this.id.toString().getBytes(),key.toString().getBytes()); 137 } catch (JedisConnectionException e) { 138 e.printStackTrace(); 139 } finally { 140 if (connection != null) { 141 connection.close(); 142 } 143 } 144 return result; 145 } 146 147 @Override 148 public ReadWriteLock getReadWriteLock() { 149 return this.readWriteLock; 150 } 151 152 /** 153 * 注入jedisConnectionFactory 154 * @param jedisConnectionFactory 155 */ 156 public static void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) { 157 RedisCache.jedisConnectionFactory = jedisConnectionFactory; 158 } 159 }
其中每個方法下面都有被注釋的存儲到hash結構的方法,我們這里才用的是string,所以存儲到redis中的是key-value對應的,這樣一來我們就可以解決key的生存時間了。
這里面主要通過set注入的方法注入 jedisConnectionFactory 之后通過其做一系列的 get、put等等操作。
當然使用string結構來存儲也有不足之處:會生成大量的key
上面都配置完畢之后,使用方法跟mybatis-redis一致,需要開啟mybatis的緩存:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <!--<setting name="logImpl" value="LOG4J2" />--> <!-- 全局映射器啟用緩存--> <setting name="cacheEnabled" value="true"/> </settings> </configuration>
下面就做一個小小的測試 sql查詢如下,其他具體的文件就不一一列出來了,如果需要的話,可以在git上面下載。
<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.anhoo.mapper.UserEntityMapper"> <cache type="com.anhoo.util.RedisCache"/> <!--<cache type="org.mybatis.caches.redis.RedisCache"/>--> <select id="selectByUserName" parameterType="java.lang.String" resultType="com.anhoo.entity.UserEntity"> SELECT * FROM user WHERE username = #{userName,jdbcType=VARCHAR} </select> </mapper>
當我們第一次查詢的時候,控制台顯示:
在service中打斷點得到查詢的值為:
第一個紅框表示查詢到了一條語句,第二個紅框是把查詢的key-value放到redis中,我們可以通過redis-cli來驗證下,如下所示:
之后我們清除控制台信息,再次查詢:斷點的值與上面一致,但是控制台中不是從數據庫中獲取的,而是從緩存中獲取,控制台信息如下:
本文涉及到的配置以及框架內容下載連接:https://github.com/eoooxy/anhoo 如有錯誤,謝謝指出。
參考:
http://www.jianshu.com/p/648c10420df1
http://www.cnblogs.com/springlight/p/6374372.html
http://www.cnblogs.com/yjmyzz/p/4105731.html