欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

spring data redis repostory

程序员文章站 2022-06-02 20:09:24
...

转载自 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 repostory
如上图,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: 
spring data redis repostory

如上图,我们这里稍做修改,添加我们自己的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可以成功看到键值的存取: 
spring data redis repostory

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

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

常用API与Serializer(序列化)

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

如上图,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 : 
spring data redis repostory

首先看一下官方文档中对Redis Repositories的简介: 
spring data redis repostory

如上所示,正如官方文档的介绍,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", "aaa@qq.com");
        //repo.save(u);
        List<User> users = new ArrayList<User>();
        users.add(new User("shanshan", "123", "杉杉", "aaa@qq.com"));
        users.add(new User("xiaoming", "123", "小明", "aaa@qq.com"));
        users.add(new User("xiaohong", "123", "小红", "aaa@qq.com"));
        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的测试方法: 
spring data redis repostory

可以看到测试通过,接下来在redis控制台看一下数据: 
spring data redis repostory

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

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

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

回顾上一小节,Redis Repository支持通过hash类型持久化对象,将对象的每个属性都映射的很好: 
spring data redis repostory

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

如上图,分为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注解没有指定参数时,默认会用全类名做为前缀: 
spring data redis repostory

如上图,这个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前缀已经改变: 
spring data redis repostory

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。