sping整合redis,以及做mybatis的第三方緩存


一、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

我們在使用的時候,先后步驟為:

  1. 當調用了RedisCache類,RedisCache通過他的構造函數,使得RedisConfigurationBuilder通過 ./redis.properties 來創建redis的配置文件類RedisConfig
  2. 這個時候RedisCache根據得到的RedisConfig,創建了PoolConfig
  3. RedisCache的execute方法又根據PoolConfig得到了Jedis
  4. 根據RedisCallBack回調Jedis做對應的get、put等等操作

但是在使用redis-cache的時候他有三個不足之處:

  1. 默認情況下,mybatis會為每一個mapper創建一個RedisCache,而JedisPool是在RedisCache的構造方法中創建的,這就意味着會為每一個mapper創建一個JedisPool,使用意圖和開銷上面都會有問題;
  2. 在很多情況下,我們的應用中也會獨立使用到redis,這樣也無法讓RedisCache直接使用我們項目中可能已經存在的JedisPool;並且會造成兩個配置文件(除非我們應用也使用redis.properties);
  3. 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

 


免責聲明!

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



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