使用Spring Data Redis實現數據緩存

引言
目前很多系統為了解決數據讀寫的性能瓶頸,在系統架構設計中使用Redis實現緩存,Spring框架為了讓開發人員更加方便快捷的使用Redis實現緩存,對Redis的操作進行了包裝。

0.緩存
個人理解的緩存是指用于存儲頻繁使用的數據的空間,關注點是存儲數據的空間和使用頻繁的數據。緩存技術,簡單的說就是先從緩存中查詢數據是否存在,存在則直接返回,不存在再執行相應的操作獲取數據,并將獲取的數據存儲到緩存中,它是一種提升系統性能的重要方法。

1.Redis
Redis是一個開源的、內存存儲key-value類型的數據結構服務器,可用作數據庫、高速緩存和消息隊列代理。它支持的數據類型有字符串、哈希表、列表、集合、有序集合等,同時通過Redis Sentinel提供高可用,通過Redis Cluster提供分區功能。

2.jedis
jedis是Redis的Java版客戶端實現,也是官方推薦的Java版客戶端。它封裝了對Redis的各種操作,并且支持事務、管道及有jedis自身實現的分布式。

3.Spring Data Redis
Spring Data是Spring框架中的一個主要項目,目的是為了簡化構建基于Spring框架應用的數據訪問,包括非關系數據庫、Map-Reduce框架、云數據服務等,另外也包含對關系數據庫的訪問支持。
Spring Data Redis是Spring Data項目中的一個主要模塊,實現了對jedis客戶端API的高度封裝,使對Redis的操作更加便捷。

4.關系圖
Redis、jedis、Spring Data Redis三者之間的關系圖如下所示。

Paste_Image.png

5.Spring Cache
從Spring3.1開始,Spring框架提供了對Cache的支持,提供了一個對緩存使用的抽象,通過在既有代碼中添加少量它定義的各種 annotation,即能夠達到緩存方法的返回對象的作用。提供的主要注解有@Cacheable、@CachePut、@CacheEvict和@Caching,具體見表1。

Paste_Image.png

@Cacheable的常用屬性及說明如表2所示。

Paste_Image.png
Paste_Image.png

@CacheEvict的常用屬性見表4。@CachePut的常用屬性同@Cacheable。

Paste_Image.png

當需要在類上或方法上同時使用多個注解時,可以使用@Caching,如@Caching(cacheable = @Cacheable("User"), evict = {@CacheEvict("Member"), @CacheEvict(value = "Customer", allEntries = true)})

6.使用示例
下面使用Spring Data Reds、Redis和jedis實現一個簡單的數據緩存。
1)依賴配置
示例使用了gradle,所以需要在build.gradle中加入如下依賴配置來管理所需要的jar。

compile "org.springframework.data:spring-data-redis:1.7.2.RELEASE"
compile "redis.clients:jedis:2.7.2"
testCompile "junit:junit:4.12"

2)Redis配置
示例連接的是本地的Redis,redis.properties配置如下。

# Redis settings
redis.host=127.0.0.1
redis.port=6379
redis.pass=
redis.dbIndex=0
redis.expiration=3000
redis.maxIdle=300
redis.maxActive=600
redis.maxWait=1000
redis.testOnBorrow=true

3)Spring配置
Spring的配置文件如下。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
    <context:component-scan base-package="redis.cache"/>
    <context:annotation-config/>
    <cache:annotation-driven cache-manager="redisCacheManager"/>
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:redis.properties</value>
            </list>
        </property>
    </bean>
    <!-- 配置JedisPoolConfig實例 -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxIdle" value="${redis.maxIdle}"/>
        <property name="maxTotal" value="${redis.maxActive}"/>
        <property name="maxWaitMillis" value="${redis.maxWait}"/>
        <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
    </bean>
    <!-- 配置JedisConnectionFactory -->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.host}"/>
        <property name="port" value="${redis.port}"/>
        <property name="password" value="${redis.pass}"/>
        <property name="database" value="${redis.dbIndex}"/>
        <property name="poolConfig" ref="poolConfig"/>
    </bean>
    <!-- 配置RedisTemplate -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory"/>
    </bean>
    <!-- 配置RedisCacheManager -->
    <bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
        <constructor-arg name="redisOperations" ref="redisTemplate"/>
        <property name="defaultExpiration" value="${redis.expiration}"/>
    </bean>
</beans>

4)Service
示例代碼的Servicer如下。

@Service("userService")
public class UserService {
    @Cacheable(value = "User", key = "'UserId_' + #id",condition = "#id<=110")
    public String queryFullNameById(long id) {
        System.out.println("execute queryFullNameById method");
        return "ZhangSanFeng";
    }

    @CacheEvict(value = "User", key = "'UserId_' + #id")
    public void deleteById(long id) {
        System.out.println("execute deleteById method");
    }

    @CachePut(value = "User", key = "'UserId_' + #id")
    public String modifyFullNameById(long id, String newName) {
        System.out.println("execute modifyFullNameById method");
        return newName;
    }
}

5)測試

    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("redisCacheContext.xml");
        UserService userService = (UserService) context.getBean("userService");
        System.out.println("第一次執行查詢:" + userService.queryFullNameById(110L));
        System.out.println("----------------------------------");

        System.out.println("第二次執行查詢:" + userService.queryFullNameById(110L));
        System.out.println("----------------------------------");

        userService.deleteById(110L);
        System.out.println("----------------------------------");

        System.out.println("清除緩存后查詢:" + userService.queryFullNameById(110L));
        System.out.println("----------------------------------");

        System.out.println(userService.modifyFullNameById(110L, "ZhangJunBao"));
        System.out.println("----------------------------------");

        System.out.println("修改數據后查詢:" + userService.queryFullNameById(110L));
        System.out.println("----------------------------------");

        System.out.println("第一次執行查詢:" + userService.queryFullNameById(112L));
        System.out.println("----------------------------------");

        System.out.println("第二次執行查詢:" + userService.queryFullNameById(112L));
        System.out.println("----------------------------------");
    }

6)測試結果
輸出結果如下。

execute queryFullNameById method
第一次執行查詢:ZhangSanFeng
----------------------------------
第二次執行查詢:ZhangSanFeng
----------------------------------
execute deleteById method
----------------------------------
execute queryFullNameById method
清除緩存后查詢:ZhangSanFeng
----------------------------------
execute modifyFullNameById method
ZhangJunBao
----------------------------------
修改數據后查詢:ZhangJunBao
----------------------------------
execute queryFullNameById method
第一次執行查詢:ZhangSanFeng
----------------------------------
execute queryFullNameById method
第二次執行查詢:ZhangSanFeng
----------------------------------

從結果可以看到,使用緩存后,第二次查詢沒有執行查詢方法體,直接返回了緩存中的數據;清除緩存后,再次查詢就執行了查詢方法體;修改數據后,相應的緩存數據也被修改了;不符合緩存條件的數據沒有被緩存。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容