Spring Data Redis 实践

版权声明:本文为博主原创文章,转载请标明出处(http://blog.csdn.net/wlwlwlwl015)Thanks. https://blog.csdn.net/wlwlwlwl015/article/details/52863821

前言

Spring Data Redis是Spring Data大家族的一部分,提供了基于spring应用的简易配置与redis服务访问,它为存储与交互提供了低级(low-level)和高级的(high-level)封装与抽象,使得用户不必再关注底层,正如其官网给出的定义:

Spring Data Redis, part of the larger Spring Data family, provides easy configuration and access to Redis from Spring applications. It offers both low-level and high-level abstractions for interacting with the store, freeing the user from infrastructural concerns.

本篇blog主要记录一下Spring Data Redis在基于spring的web项目中的应用与配置,版本为当前最新的GA(1.7.4),同时也重点记录了该版本的新特性之一:Redis Repositories。

快速入门

按照官方的Quick Start我们先快速进行一个简单的入门,首先第一步是引入spring-data-redis的maven依赖,当前最新的GA版本是1.7.4:

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.7.4.RELEASE</version>
</dependency>

同时需要注意官方文档给出了Requirements(必备环境):
这里写图片描述
如上图,Spring Data Redis 1.x需要JDK 6.0及以上版本,Spring需要4.2.8.RELEASE及以上版本,同时Redis也要保证在2.6.x或更高的版本。Spring Data Redis还依赖了commons-logging,commons-pool2以及jedis,所以接下来还需要引入jedis的依赖,此处我选择的版本是2.8.0(当前最新版为2.9.0 Jul, 2016):

<!-- jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.8.0</version>
</dependency>

依赖添加完毕后就可以开始配置编码了,依照官方的Quick Start,我们接下来应该做的是配置一个RedisTemplate:
这里写图片描述

如上图,我们这里稍做修改,添加我们自己的host-name、port、password等(如果有的话),还有别忘了在spring配置文件中添加schema(xmlns:p):

<?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:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    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/aop 
                        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 注解扫描 -->
    <context:component-scan base-package="com.wl.controller" />

    <!-- jedisConnectionFactory -->
    <bean id="jedisConnectionFactory"
        class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
        p:host-name="192.168.111.11" p:port="6379" />

    <!-- redisTemplate -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
        p:connection-factory-ref="jedisConnectionFactory" />

</beans>

如上所示,定义好了redisTemplate之后就可以在程序中注入测试了,看一个简单的Test Case:

package com.wl.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:applicationContext.xml" })
@WebAppConfiguration
public class SpringRedisTest {

    @Autowired
    private RedisTemplate<String, String> template;

    @Test
    public void testFirst() {
        // set username wlwlwlwl015
        template.opsForValue().set("username", "wlwlwlwl015");
        // get username
        System.out.println(template.opsForValue().get("username"));
    }

}

如上所示,16行通过@Autowired注入了RedisTemplate,22行和24行则是调用了redis最简单的两个字符串操作命令set key valueget key,运行junit test case可以成功看到键值的存取:
这里写图片描述

通过RedisTemplate存取完全没有问题,那么再看一下redis控制台下是否已成功添加了这对key-value:
这里写图片描述

如上图,仿佛有些不对劲,在key-value前面均加了一串字符串,这是由于RedisSerializer默认使用的是JdkSerializationRedisSerializer,这个具体后面再说,本小节仅仅是Quick Start,那么至此Quick Start就算成功完成了,接下来具体记录一下Spring Data Redis的常用API与项目的集成封装。

扫描二维码关注公众号,回复: 3374986 查看本文章

常用API与Serializer(序列化)

接下来看一下Spring Data Redis中的常用API与对象操作,毕竟这些才是我们在实际项目中会频繁用到的,依旧参考官方文档,可以看到一个Operational views的表格:
这里写图片描述

如上图,Spring Redis Data针对jedis客户端的api进行了重新归类与封装,将同一类型的操作封装为Operation接口,如上面的Key Type OperationsKey Bound Operations,其中Key Type Operations顾名思义也就是根据键类型给所有操作进行分类,类别如下:

  • ValueOperations:简单K-V操作
  • SetOperations:set类型数据操作
  • ZSetOperations:zset类型数据操作
  • HashOperations:map类型的数据操作
  • ListOperations:list类型的数据操作

正如在Quick Start中用到的template.opsForValue()也就是第一个ValueOperations了,而后面的Key Bound Operations则提供了对key的”bound”(绑定)便捷化操作的API,可以通过bound封装指定的key,然后进行一系列的操作而无须“显式”的再次指定Key,举个简单例子:

@Test
public void testBound(){
    BoundValueOperations<String, String> boundValueOps = template.boundValueOps("username");
    System.out.println(boundValueOps.get());
    boundValueOps.set("wlwlwlwl is good!");
    System.out.println(boundValueOps.get());
}

很好理解,接下来介绍重点内容——序列化/反序列化(RedisSerializer),Spring Data Redis提供了多种可选择策略。官方文档的5.7小节针对Serializers只有一小段概述,下面是我截取的一小段重点内容:

Multiple implementations are available out of the box, two of which have been already mentioned before in this documentation: the StringRedisSerializer and the JdkSerializationRedisSerializer. However one can use OxmSerializer for Object/XML mapping through Spring 3 OXM support or either JacksonJsonRedisSerializer, Jackson2JsonRedisSerializer or GenericJackson2JsonRedisSerializer for storing data in JSON format.

如上所示,提供了多种开箱即用的实现,官方文档中已经被提过两种,分别是:

  • StringRedisSerializer
  • JdkSerializationRedisSerializer

第二种在上面也提过了,它是RedisTemplate提供的默认序列化方式,在源码中可以看到:

private RedisSerializer<?> defaultSerializer;

private RedisSerializer keySerializer = null;
private RedisSerializer valueSerializer = null;
private RedisSerializer hashKeySerializer = null;
private RedisSerializer hashValueSerializer = null;

private RedisSerializer<String> stringSerializer = new StringRedisSerializer();

/**
 * Constructs a new <code>RedisTemplate</code> instance.
 */
public RedisTemplate() {}

public void afterPropertiesSet() {

    super.afterPropertiesSet();

    boolean defaultUsed = false;

    if (defaultSerializer == null) {

        defaultSerializer = new JdkSerializationRedisSerializer(
                classLoader != null ? classLoader : this.getClass().getClassLoader());
    }

    if (enableDefaultSerializer) {

        if (keySerializer == null) {
            keySerializer = defaultSerializer;
            defaultUsed = true;
        }
        if (valueSerializer == null) {
            valueSerializer = defaultSerializer;
            defaultUsed = true;
        }
        if (hashKeySerializer == null) {
            hashKeySerializer = defaultSerializer;
            defaultUsed = true;
        }
        if (hashValueSerializer == null) {
            hashValueSerializer = defaultSerializer;
            defaultUsed = true;
        }
    }

    if (enableDefaultSerializer && defaultUsed) {
        Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
    }

    if (scriptExecutor == null) {
        this.scriptExecutor = new DefaultScriptExecutor<K>(this);
    }

    initialized = true;
}

如上所示,23行指定默认defaultSerializer为JdkSerializationRedisSerializer,之所以上面通过RedisTemplate设置的key-value在redis控制台看加了一串字符串,是因为JdkSerializationRedisSerializer对key和value都进行了序列化,变成了字节序列(byte[]),然后再调用jedis进行存储的,而StringRedisSerializer是根据指定的charset对数据的字节序列编码成string,更适用于字符串场景,相当于new String(bytes, charset)string.getBytes(charset)的直接封装,也更加轻量与高效,所以此处将默认的JdkSerializationRedisSerializer替换成StringRedisSerializer就可以正常存取键值了,在我们的RedisTemplate中加入如下配置:

<!-- redisTemplate -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
    p:connection-factory-ref="jedisConnectionFactory">
    <!-- 序列化方式 建议key/hashKey采用StringRedisSerializer。 -->
    <property name="keySerializer">
        <bean
            class="org.springframework.data.redis.serializer.StringRedisSerializer" />
    </property>
    <property name="hashKeySerializer">
        <bean
            class="org.springframework.data.redis.serializer.StringRedisSerializer" />
    </property>
    <property name="valueSerializer">
        <bean
            class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
    </property>
    <property name="hashValueSerializer">
        <bean
            class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
    </property>
</bean>

再回头看一下官方文档中关于Serializer的那一小段介绍,还提到了OxmSerializer和JacksonJsonRedisSerializer这两种策略,顾名思义,前者提供了将javabean与xml之间的转换能力(xml格式存储),而后者提供了javabean与json之间的转换能力(json格式存储,且依赖jackson-json工具包),由于实际项目中最常用的还是针对序列化对象的存取,所以接下来就记录一下对象操作,但并考虑使用以上两种,因为无论是json还是xml,他们本身仍然是String,并且以上两种策略封装和编程都较为复杂,性能也存在一些问题,在当前版本中我们有更好的选择,Spring Data Redis在最新版1.7.x中为我们提供了Redis Repositories,可以无缝的转换并存储domain objects,使用的数据类型为哈希(hash),下面重点看一下Redis Repositories的应用。

Redis Repositories

在Spring Data Redis1.7的新特性中,除了支持Redis集群外,我们还可以看到另一条重要的新特性——Spring Data Repository abstractions :
这里写图片描述

首先看一下官方文档中对Redis Repositories的简介:
这里写图片描述

如上所示,正如官方文档的介绍,Redis Repositories允许通过redis的hash类型无缝的存储以及转换领域对象,应用了自定义的映射策略并且利用了二级索引。接下来具体看一下用法,还需要注意一下如果要使用Redis Repositories,那么redis服务器的版本不能低于2.8:

Redis Repositories requires at least Redis Server version 2.8.0.

接下来看一下如何使用。

基础用法(Usage)

根据官方文档,首先第一步是在我们的实体bean上添加注解@RedisHash,并且在标识属性(或主键)上添加@Id注解,这两个注解负责创建用于存储对象hash的key,例如:

package com.wl.bean;

import java.io.Serializable;

import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;

@RedisHash("users")
public class User implements Serializable{

    private static final long serialVersionUID = 1L;

    @Id
    private String username;
    private String password;
    private String nickname;
    private String email;
    private String filePath;

    public User() {}

}

第二步需要创建一个repository接口来存取数据,根据官方的Example改写后如下:

package com.wl.dao;

import org.springframework.data.repository.CrudRepository;

public interface BaseRepository<T> extends CrudRepository<T, String> {

}

用过spring data的话应该对这个接口比较熟悉了,CrudRepository为我们提供了一组基础的CRUD方法,有了这个基础接口之后,我们下面需要做的就通过Spring配置将bean和这个接口关联起来,正如官方文档的原话:

As our repository extends CrudRepository it provides basic CRUD and finder operations. The thing we need in between to glue things together is the according Spring configuration.

以User为例,我们再创建一个UserRepository接口:

package com.wl.dao;

import com.wl.bean.User;

public interface UserRepository extends BaseRepository<User>{

}

下面看一下这个配置类:

package com.wl.dao;

import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.stereotype.Component;

import com.wl.bean.User;

@Component("userDao")
@EnableRedisRepositories
public class UserDao {}

如上所示,关键点就是@EnableRedisRepositories,该注解表示启用Repositories,接下来我们就可以将UserRepository注入到我们的业务组件中使用了,下面是一个简单的测试方法:

package com.wl.test;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;

import com.wl.bean.User;
import com.wl.dao.UserRepository;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:applicationContext.xml" })
@WebAppConfiguration
public class SpringRedisTest {

    @Autowired 
    private UserRepository repo;

    @Test
    public void testRepositorySave(){
        //User u = new User("wangliang", "123", "raito", "[email protected]");
        //repo.save(u);
        List<User> users = new ArrayList<User>();
        users.add(new User("shanshan", "123", "杉杉", "[email protected]"));
        users.add(new User("xiaoming", "123", "小明", "[email protected]"));
        users.add(new User("xiaohong", "123", "小红", "[email protected]"));
        repo.save(users);
    }

    @Test
    public void testRepositoryGet(){
        //User u = repo.findOne("wangliang");
        //System.out.println(u);
        Iterator<User> iterator = repo.findAll().iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

}

如上所示,主要测试了List的存取,首先运行第一个存List的测试方法:
这里写图片描述

可以看到测试通过,接下来在redis控制台看一下数据:
这里写图片描述

如上图,可以看到所有hash的键格式均是@RedisHash("users")的参数值和@Id注解的属性名拼接而成的(keyspace:id),所以再次强调@Id务必要标记主键或者是数据库中的唯一标识符。save正常,再来看一下常用的findAll,运行第二个测试方法:
这里写图片描述
这里写图片描述

如上所示,测试通过,并且List中的数据可以成功返回并输出,Redis Repository的其它api就不再一一演示,可以发现用起来确实很爽,很强大,关于基础的使用方法(Usage)就暂且介绍到这里。

对象哈希映射(Object to Hash Mapping)

回顾上一小节,Redis Repository支持通过hash类型持久化对象,将对象的每个属性都映射的很好:
这里写图片描述

如上图,_class属性还映射了全类名,那么Redis Repository是如何正确映射对象的全部属性呢?根据官方文档的说明,实际上是通过一个RedisConverter(转换器)来实现的,这个转换器的默认实现就是org.springframework.core.convert.converter.Converter,在上图的映射列表中,所有的映射属性都属于Simple Type,官方文档中给出了一张默认的映射规则表:
这里写图片描述

如上图,分为5种类型,分别是:

  1. 简单类型(Simple Type)
  2. 符合类型(Complex Type)
  3. 简单列表类型(List of Simple Type)
  4. 简单K-V映射类型(Map of Simple Type)
  5. 符合列表类型(List of Complex Type)
  6. 符合K-V映射类型(Map of Complex Type)

每种映射类型都提供了对应的例子,当然这都是官方默认提供的,我们还可以通过程序自定义,由于我目前的项目只涉及简单类型,所以关于自定义映射就不做过多说明,有需求的朋友可以参考官方文档。

Keyspaces

keyspace用于创建hash键的前缀,默认的前缀是getClass().getName()即全类名,这里的默认意思就是说当我们的@RedisHash注解没有指定参数时,默认会用全类名做为前缀:
这里写图片描述

如上图,这个hash键的前缀就是默认生成的,除了通过修改@RedisHash注解,还可以使用编程的形式来指定前缀,此时就需要创建一个自定义的KeyspaceConfiguration:

package com.wl.util;

import java.util.Collections;

import org.springframework.data.redis.core.convert.KeyspaceConfiguration;

import com.wl.bean.User;

public class MyKeyspaceConfiguration extends KeyspaceConfiguration {
    @Override
    protected Iterable<KeyspaceSettings> initialConfiguration() {
      return Collections.singleton(new KeyspaceSettings(User.class, "userme"));
    }
  }

如上所示,很好理解,这段代码的作用就是将keyspace指定为字符串“userme”,定义好KeyspaceConfiguration之后在@EnableRedisRepositories注解中声明引用即可:

package com.wl.dao;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.stereotype.Component;

import com.wl.bean.User;
import com.wl.util.MyKeyspaceConfiguration;

@Component("userDao")
@EnableRedisRepositories(keyspaceConfiguration = MyKeyspaceConfiguration.class)
public class UserDao {

    @Autowired 
    private UserRepository repo;

}   

再次运行junit测试插入1条数据,可以看到此时的key前缀已经改变:
这里写图片描述

Redis Repository还有更多高级内容,如二级索引(Secondary Indexes)、对象过期时间(Time To Live)、查询(Queries and Query Methods)等等,感兴趣的朋友可以参考Spring Data Redis官方文档:http://docs.spring.io/spring-data/redis/docs/1.7.4.RELEASE/reference/html/

总结

简单介绍一下Spring Data Redis的基础用法以及1.7新特性——Redis Repositories,希望对遇到同样问题的朋友有所帮助,The End。

猜你喜欢

转载自blog.csdn.net/wlwlwlwl015/article/details/52863821