原文:https://www.docs4dev.com/docs/zh/spring-data-redis/2.1.5.RELEASE/reference/all.html
Spring Data Redis项目通过使用键值样式数据存储将核心Spring概念应用于解决方案的开发。我们提供“模板”作为发送和接收消息的高级抽象。您可能会注意到Spring Framework中与JDBC支持的相似之处。
本节简要介绍最新版本中新增和值得注意的项目。
使用 Lettuce 的Unix域套接字连接。
Write to Master, read from Replica 支持使用 Lettuce 。
Query by Example 整合。
@TypeAlias
支持Redis存储库。
群集范围 SCAN
在两个驱动程序支持的选定节点上使用Lettuce和 SCAN
执行。
Reactive Pub/Sub 发送和接收消息流。
BITFIELD
, BITPOS
和 OBJECT
命令支持。
将 BoundZSetOperations
的返回类型与 ZSetOperations
对齐。
Reactive SCAN
, HSCAN
, SSCAN
和 ZSCAN
支持。
在存储库查询方法中使用 IsTrue
和 IsFalse
关键字。
升级到Java 8。
升级到 Lettuce 5.0。
删除了对SRP和JRedis驱动程序的支持。
为 RedisConnection
引入Redis功能特定的接口。
使用 JedisClientConfiguration
和 LettuceClientConfiguration
改进了 RedisConnectionFactory
配置。
修订 RedisCache
实施。
为Redis 3.2添加带有count命令的 SPOP
。
升级到Jedis 2.9。
升级到 Lettuce
4.2(注意: Lettuce 4.2需要Java 8)。
支持Redis GEO 命令。
使用Spring Data Repository抽象支持地理空间索引(参见 Geospatial Index )。
MappingRedisConverter
-基于 HashMapper
实施(见 Hash mapping )。
在存储库中支持 PartialUpdate
(请参阅 Persisting Partial Updates )。
SSL支持与Redis群集的连接。
使用Jedis时支持 client name
到 ConnectionFactory
。
支持 RedisCluster 。
支持Spring Data Repository抽象(参见 Redis Repositories )。
Lettuce
Redis驱动程序从 wg/lettuce 切换到 mp911de/lettuce 。
支持 ZRANGEBYLEX
。
ZSET
的增强范围操作,包括 +inf
/ -inf
。
RedisCache
中的性能改进,现在更早发布连接。
Generic Jackson2 RedisSerializer
利用Jackson的多态反序列化。
添加对Redis HyperLogLog命令的支持: PFADD
, PFCOUNT
和 PFMERGE
。
可配置 JavaType
查找基于Jackson的 RedisSerializers
。
基于
PropertySource
的连接到Redis Sentinel的配置(请参阅: Redis Sentinel Support )。本文档是Spring Data Redis(SDR)支持的参考指南。它解释了键值模块的概念和语义以及各种 Store 命名空间的语法。
有关键值存储,Spring或Spring Data示例的介绍,请参阅 Getting Started 。本文档仅涉及Spring Data Redis支持,并假定用户熟悉键值存储和Spring概念。
Spring Framework是领先的全栈Java / JEE应用程序框架。它提供了一个轻量级容器和一个非侵入式编程模型,它通过使用依赖注入,AOP和便携式服务抽象来实现。
NoSQL 存储系统为经典RDBMS提供了水平可扩展性和速度的替代方案。在实现方面,键值存储代表NoSQL空间中最大(和最老)的成员之一。
Spring Data Redis(SDR)框架通过Spring的优秀基础架构支持消除了与 Store 交互所需的冗余任务和样板代码,可以轻松编写使用Redis键值存储的Spring应用程序。
Spring Data Redis 2.x二进制文件需要JDK级别8.0及以上版本以及 Spring Framework 5.1.5.RELEASE及以上版本。
在键值存储方面,需要 Redis 2.6.x或更高。 Spring Data Redis目前已针对最新的4.0版本进行了测试。
本节提供了一个易于遵循的Spring Data Redis模块入门指南。
正如 Why Spring Data Redis? 中所解释的,Spring Data Redis(SDR)提供了Spring框架和Redis键值存储之间的集成。因此,您应该熟悉这两个框架。在整个SDR文档中,每个部分都提供了相关资源的链接。但是,在阅读本指南之前,您应该熟悉这些主题。
Spring Data使用Spring框架的 core 功能,例如 IoC 容器, resource 摘要和 AOP 基础结构。虽然了解Spring API并不重要,但了解它们背后的概念非常重要。至少,IoC背后的想法应该是熟悉的。话虽这么说,你对Spring的了解越多,你获得Spring Data Redis的速度就越快。除了Spring Framework的综合文档之外,还有很多关于此事的文章,博客文章和书籍。 Spring 指南 home page 提供了一个好的起点。一般来说,这应该是想要尝试Spring Data Redis的开发人员的起点。
NoSQL Store 风靡了存储世界。这是一个涉及众多解决方案,术语和模式的庞大领域(更糟糕的是,甚至术语本身也有多个 meanings )。虽然一些原则很常见,但在某种程度上熟悉SDR支持的 Store 至关重要。熟悉这些解决方案的最佳方式是阅读他们的文档并按照他们的示例进行操作。它通常不需要花费五到十分钟来完成它们,如果你来自RDMBS背景,很多时候这些练习可以让人大开眼界。
可以在 http://github.com/spring-projects/spring-data-keyvalue-examples 的专用Spring Data示例仓库中找到键值存储的各种示例。对于Spring Data Redis,您应该特别注意 retwisj
示例,这是一个构建在Redis之上的Twitter克隆,可以在本地运行或部署到 Cloud 中。有关详细信息,请参阅 documentation ,以下博客 entry 。
如果您遇到问题或者您只是在寻找建议,请使用以下链接之一:
Stack Overflow 上的Spring Data标记是所有Spring Data(不仅仅是Redis)用户共享信息并互相帮助的留言板。请注意,需要注册 only 才能发布。
专业的,源头支持,保证响应时间,可从 Pivotal Software, Inc. ,Spring Data和Spring背后的公司获得。
有关Spring Data源代码存储库,夜间构建和快照构件的信息,请参阅Spring Data主页 page 。
通过在 spring-data 或 spring-data-redis 上与Stack Overflow上的开发人员交互,您可以帮助Spring Data最好地满足Spring社区的需求。
如果您遇到错误或想要建议改进(包括本文档),请在Spring Data问题 tracker 上创建一个票证。
要及时了解Spring eco系统中的最新新闻和公告,请订阅Spring社区 Portal 。
最后,您可以在Twitter上关注Spring blog 或项目团队( @SpringData )。
这部分参考文档解释了Spring Data Redis提供的核心功能。
Redis support 介绍了Redis模块功能集。
Spring Data支持的其中一个键值存储是 Redis 。引用Redis项目主页:
Redis是一个高级键值存储。它与memcached类似,但数据集不是易失性的,值可以是字符串,与memcached完全相同,但也可以是列表,集合和有序集。所有这些数据类型都可以通过原子操作来操作,以推送/弹出元素,添加/删除元素,执行服务器端并集,交集,集合之间的差异等等。 Redis支持不同类型的排序功能。
Spring Data Redis提供了从Spring应用程序轻松配置和访问Redis的功能。它提供了与 Store 交互的低级和高级抽象,使用户免于基础设施问题。
Spring Redis需要Redis 2.6或更高版本,Spring Data Redis与 Lettuce 和 Jedis 集成,这是Redis的两个流行的开源Java库。
Redis支持提供了几个组件。对于大多数任务,高级抽象和支持服务是最佳选择。请注意,在任何时候,您都可以在图层之间移动。例如,您可以获得与Redis直接通信的低级连接(甚至是本机库)。
使用Redis和Spring时的首要任务之一是通过IoC容器连接到 Store 。为此,需要Java连接器(或绑定)。无论您选择哪个库,都只需要使用一组Spring Data Redis API(在所有连接器中表现一致): org.springframework.data.redis.connection
包及其 RedisConnection
和 RedisConnectionFactory
接口,用于处理和检索活动连接Redis的。
RedisConnection
为Redis通信提供核心构建块,因为它处理与Redis后端的通信。它还会自动将底层连接库异常转换为Spring的一致DAO异常 hierarchy ,这样您就可以在没有任何代码更改的情况下切换连接器,因为操作语义保持不变。
对于需要本机库API的极端情况,
RedisConnection
提供了一个专用方法(getNativeConnection
),它返回用于通信的原始底层对象。
通过 RedisConnectionFactory
创建活动 RedisConnection
对象。此外,工厂充当 PersistenceExceptionTranslator
对象,这意味着,一旦声明,它们就会让您进行透明的异常转换。例如,您可以通过使用 @Repository
注释和AOP进行异常转换。有关更多信息,请参阅Spring Framework文档中的专用 section 。
根据基础配置,工厂可以返回新连接或现有连接(使用池或共享本机连接时)。
使用 RedisConnectionFactory
的最简单方法是通过IoC容器配置相应的连接器并将其注入using类。
遗憾的是,目前并非所有连接器都支持所有Redis功能。在基础库不支持的Connection API上调用方法时,会抛出
UnsupportedOperationException
。
Lettuce 是一个基于 Netty 的开源连接器,由Spring Data Redis通过 org.springframework.data.redis.connection.lettuce
包支持。以下示例显示如何创建新的Lettuce连接工厂:
@Configuration
class AppConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("server", 6379));
}
}
还有一些可以调整的特定 Lettuce 特定连接参数。默认情况下, LettuceConnectionFactory
创建的所有 LettuceConnection
实例共享所有非阻塞和非事务操作的相同线程安全本机连接。要每次使用专用连接,请将 shareNativeConnection
设置为 false
。如果 shareNativeConnection
设置为 false
, LettuceConnectionFactory
也可以配置为使用 LettucePool
汇集阻塞和事务连接或所有连接。
Lettuce与Netty的 native transports 集成,让你使用Unix域套接字与Redis进行通信。确保包含与运行时环境匹配的相应本机传输依赖项。以下示例显示如何在 /var/run/redis.sock
为Unix域套接字创建Lettuce连接工厂:
@Configuration
class AppConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisSocketConfiguration("/var/run/redis.sock"));
}
}
Netty目前支持用于OS本地传输的epoll(Linux)和kqueue(BSD / macOS)接口。
Jedis 是一个社区驱动的连接器,由Spring Data Redis模块通过 org.springframework.data.redis.connection.jedis
包支持。在最简单的形式中,Jedis配置如下所示:
@Configuration
class AppConfig {
@Bean
public JedisConnectionFactory redisConnectionFactory() {
return new JedisConnectionFactory();
}
}
但是,对于 生产环境 用途,您可能需要调整主机或密码等设置,如以下示例所示:
@Configuration
class RedisConfiguration {
@Bean
public JedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("server", 6379);
return new JedisConnectionFactory(config);
}
}
Redis主站/副本设置 - 没有自动故障转移(用于自动故障转移,请参阅: Sentinel ) - 不仅允许将数据安全地存储在更多节点上。它还允许通过使用 Lettuce 从副本中读取数据,同时将写入推送到主服务器。您可以使用 LettuceClientConfiguration
设置要使用的读/写策略,如以下示例所示:
@Configuration
class WriteToMasterReadFromReplicaConfiguration {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.readFrom(SLAVE_PREFERRED)
.build();
RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration("server", 6379);
return new LettuceConnectionFactory(serverConfig, clientConfig);
}
}
对于通过
INFO
命令报告非公共地址的环境(例如,使用AWS时),请使用RedisStaticMasterReplicaConfiguration
而不是RedisStandaloneConfiguration
。
为了处理高可用性Redis,Spring Data Redis使用 RedisSentinelConfiguration
支持 Redis Sentinel ,如以下示例所示:
/**
* Jedis
*/
@Bean
public RedisConnectionFactory jedisConnectionFactory() {
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
.master("mymaster")
.sentinel("127.0.0.1", 26379)
.sentinel("127.0.0.1", 26380);
return new JedisConnectionFactory(sentinelConfig);
}
/**
* Lettuce
*/
@Bean
public RedisConnectionFactory lettuceConnectionFactory() {
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
.master("mymaster")
.sentinel("127.0.0.1", 26379)
.sentinel("127.0.0.1", 26380);
return new LettuceConnectionFactory(sentinelConfig);
}
RedisSentinelConfiguration
也可以使用PropertySource
定义,它允许您设置以下属性:
配置属性
spring.redis.sentinel.master
:主节点的名称。
spring.redis.sentinel.nodes
:以逗号分隔的主机:端口对列表。
有时,需要与其中一个Sentinels直接交互。使用 RedisConnectionFactory.getSentinelConnection()
或 RedisConnection.getSentinelCommands()
可以访问配置的第一个活动Sentinel。
大多数用户可能会使用 RedisTemplate
及其相应的包 org.springframework.data.redis.core
。事实上,该模板是Redis模块的核心类,因为它具有丰富的功能集。该模板为Redis交互提供了高级抽象。虽然 RedisConnection
提供了接受和返回二进制值( byte
数组)的低级方法,但该模板负责序列化和连接管理,使用户无需处理此类详细信息。
此外,模板提供操作视图(在Redis命令 reference 分组之后),提供丰富的,通用的接口,用于处理特定类型或某些键(通过 KeyBound
接口)如下表所述:
接口 | 说明 |
---|---|
键类型操作 | |
GeoOperations | Redis地理空间操作,例如 GEOADD , GEORADIUS ,... |
HashOperations | Redis哈希操作 |
HyperLogLogOperations | Redis HyperLogLog操作,例如 PFADD , PFCOUNT ,...... |
ListOperations | Redis列表操作 |
SetOperations | Redis设置操作 |
ValueOperations | Redis字符串(或值)操作 |
ZSetOperations | Redis zset(或有序集)操作 |
键绑定操作 | |
BoundGeoOperations | Redis键绑定地理空间操作 |
BoundHashOperations | Redis哈希键绑定操作 |
BoundKeyOperations | Redis键绑定操作 |
BoundListOperations | Redis列表键绑定操作 |
BoundSetOperations | Redis设置键绑定操作 |
BoundValueOperations | Redis字符串(或值)键绑定操作 |
BoundZSetOperations | Redis zset(或有序集)键绑定操作 |
配置完成后,模板是线程安全的,可以跨多个实例重用。
RedisTemplate
在其大多数操作中使用基于Java的序列化程序。这意味着模板编写或读取的任何对象都通过Java进行序列化和反序列化。您可以更改模板上的序列化机制,Redis模块提供了多个实现,这些实现可在 org.springframework.data.redis.serializer
包中找到。有关更多信息,请参见 Serializers 。您还可以将任何序列化程序设置为null,并通过将 enableDefaultSerializer
属性设置为 false
将RedisTemplate与原始字节数组一起使用。请注意,模板要求所有键都为非null。但是,只要底层序列化程序接受它们,值就可以为null。阅读每个序列化程序的Javadoc以获取更多信息。
对于需要特定模板视图的情况,请将视图声明为依赖项并注入模板。容器自动执行转换,消除 opsFor[X]
调用,如以下示例所示:
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use-pool="true"/>
<!-- redis template definition -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="jedisConnectionFactory"/>
...
</beans>
public class Example {
// inject the actual template
@Autowired
private RedisTemplate<String, String> template;
// inject the template as ListOperations
@Resource(name="redisTemplate")
private ListOperations<String, String> listOps;
public void addLink(String userId, URL url) {
listOps.leftPush(userId, url.toExternalForm());
}
}
由于Redis中存储的键和值通常是 java.lang.String
,因此Redis模块为 RedisConnection
和 RedisTemplate
提供了两个扩展,分别为 StringRedisConnection
(及其 DefaultStringRedisConnection
实现)和 StringRedisTemplate
,作为密集String操作的便捷一站式解决方案。除了绑定到 String
键之外,模板和连接使用下面的 StringRedisSerializer
,这意味着存储的键和值是人类可读的(假设在Redis和您的代码中使用相同的编码)。以下列表显示了一个示例:
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use-pool="true"/>
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" p:connection-factory-ref="jedisConnectionFactory"/>
...
</beans>
public class Example {
@Autowired
private StringRedisTemplate redisTemplate;
public void addLink(String userId, URL url) {
redisTemplate.opsForList().leftPush(userId, url.toExternalForm());
}
}
与其他Spring模板一样, RedisTemplate
和 StringRedisTemplate
允许您通过 RedisCallback
接口直接与Redis对话。此功能为您提供完全控制,因为它直接与 RedisConnection
对话。请注意,当使用 StringRedisTemplate
时,回调会收到 StringRedisConnection
的实例。以下示例显示如何使用 RedisCallback
接口:
public void useCallback() {
redisTemplate.execute(new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection) throws DataAccessException {
Long size = connection.dbSize();
// Can cast to StringRedisConnection if using a StringRedisTemplate
((StringRedisConnection)connection).set("key", "value");
}
});
}
从框架的角度来看,存储在Redis中的数据只是字节数。虽然Redis本身支持各种类型,但在大多数情况下,这些类型指的是数据的存储方式而不是它所代表的方式。由用户决定信息是否被翻译成字符串或任何其他对象。
在Spring Data中,用户(自定义)类型和原始数据之间的转换(反之亦然)在 org.springframework.data.redis.serializer
包中处理Redis。
该软件包包含两种类型的序列化程序,顾名思义,它们负责序列化过程:
基于 RedisSerializer
的双向序列化程序。
使用 RedisElementReader
和 RedisElementWriter
的元素读者和作者。
这些变体之间的主要区别在于 RedisSerializer
主要序列化为 byte[]
而读者和作者使用 ByteBuffer
。
可以使用多种实现(包括本文档中已经提到的两种实现):
JdkSerializationRedisSerializer
,默认用于 RedisCache
和 RedisTemplate
。
StringRedisSerializer
。
但是,可以通过Spring OXM 支持使用 OxmSerializer
进行对象/ XML映射,或者使用 Jackson2JsonRedisSerializer
或 GenericJackson2JsonRedisSerializer
以 JSON 格式存储数据。
请注意,存储格式不仅限于值。它可以用于键,值或哈希,没有任何限制。
默认情况下,
RedisCache
和RedisTemplate
配置为使用Java本机序列化。众所周知,Java本机序列化允许由利用易受攻击的库和类注入未经验证的字节码的有效负载引起的远程代码执行。在反序列化期间,操作输入可能导致应用程序中不需要的代码执行步。因此,请勿在不受信任的环境中使用序列化。通常,我们强烈建议使用任何其他消息格式(例如JSON)。
如果您担心Java序列化导致的安全漏洞,请考虑核心JVM级别的通用序列化过滤机制,最初为JDK 9开发但向后移植到JDK 8,7和6:
可以使用Redis中的各种数据结构存储数据。 Jackson2JsonRedisSerializer
可以转换 JSON 格式的对象。理想情况下,可以使用普通键将JSON存储为值。您可以使用Redis哈希实现更复杂的结构化对象映射。 Spring Data Redis提供了各种将数据映射到哈希的策略(取决于用例):
直接映射,使用 HashOperations
和 serializer
使用 HashMapper
和 HashOperations
哈希映射器是将 Map 对象转换为 Map<K, V>
并返回。 HashMapper
适用于Redis Hashes。
有多种实现方式:
BeanUtilsHashMapper
使用Spring的 BeanUtils 。
ObjectHashMapper
使用 Object-to-Hash Mapping 。
以下示例显示了实现哈希映射的一种方法:
public class Person {
String firstname;
String lastname;
// …
}
public class HashMapping {
@Autowired
HashOperations<String, byte[], byte[]> hashOperations;
HashMapper<Object, byte[], byte[]> mapper = new ObjectHashMapper();
public void writeHash(String key, Person person) {
Map<byte[], byte[]> mappedHash = mapper.toHash(person);
hashOperations.putAll(key, mappedHash);
}
public Person loadHash(String key) {
Map<byte[], byte[]> loadedHash = hashOperations.entries("key");
return (Person) mapper.fromHash(loadedHash);
}
}
Jackson2HashMapper
使用 FasterXML Jackson 为域对象提供Redis Hash映射。 Jackson2HashMapper
可以将顶级属性映射为哈希字段名称,并可选择展平结构。简单类型映射到简单值。复杂类型(嵌套对象,集合,映射等)表示为嵌套JSON。
展平为所有嵌套属性创建单独的哈希条目,并尽可能将复杂类型解析为简单类型。
考虑以下类及其包含的数据结构:
public class Person {
String firstname;
String lastname;
Address address;
}
public class Address {
String city;
String country;
}
下表显示了前一类中的数据如何在法线贴图中显示:
哈希字段 | 值 |
---|---|
名字 | Jon |
姓氏 | Snow |
地址 | { "city" : "Castle Black", "country" : "The North" } |
下表显示了前一类中的数据如何在平面映射中显示:
哈希字段 | 值 |
---|---|
名字 | Jon |
姓氏 | Snow |
address.city | Castle Black |
address.country | The North |
展平要求所有属性名称不会干扰JSON路径。使用展平时,不支持在 Map 键中使用点或括号或作为属性名称。生成的哈希不能映射回Object。
Spring Data为Redis提供了专用的消息传递集成,功能类似,并命名为Spring Framework中的JMS集成。
Redis消息传递大致可分为两个功能区域:
发布或制作消息
订阅或消费消息
这是通常称为Publish / Subscribe(简称Pub / Sub)的模式示例。 RedisTemplate
类用于生成消息。对于类似于Java EE的消息驱动bean样式的异步接收,Spring Data提供了一个专用的消息监听器容器,用于创建消息驱动的POJO(MDP),以及用于同步接收的 RedisConnection
Contract 。
org.springframework.data.redis.connection
和 org.springframework.data.redis.listener
包为Redis消息传递提供核心功能。
要发布消息,您可以像使用其他操作一样使用低级 RedisConnection
或高级 RedisTemplate
。两个实体都提供 publish
方法,该方法接受消息和目标通道作为参数。虽然 RedisConnection
需要原始数据(字节数组),但 RedisTemplate
允许任意对象作为消息传入,如以下示例所示:
// send message through connection RedisConnection con = ...
byte[] msg = ...
byte[] channel = ...
con.publish(msg, channel); // send message through RedisTemplate
RedisTemplate template = ...
template.convertAndSend("hello!", "world");
在接收方,可以通过直接命名或使用模式匹配来订阅一个或多个通道。后一种方法非常有用,因为它不仅可以使用一个命令创建多个订阅,还可以监听尚未在订阅时创建的通道(只要它们与模式匹配)。
在低级别, RedisConnection
提供 subscribe
和 pSubscribe
方法,分别映射Redis命令以按通道或按模式进行订阅。请注意,可以使用多个通道或模式作为参数。要更改连接的订阅或查询是否正在侦听, RedisConnection
提供了 getSubscription
和 isSubscribed
方法。
Spring Data Redis中的订阅命令正在阻止。也就是说,在连接上调用subscribe会导致当前线程被阻塞因为它开始等待消息。只有在取消订阅时才会释放该线程,这在另一个线程在 same 连接上调用
unsubscribe
或pUnsubscribe
时发生。有关此问题的解决方案,请参阅“ Message Listener Containers ”(本文档后面部分)。
如前所述,一旦订阅,连接就开始等待消息。仅允许添加新订阅,修改现有订阅和取消现有订阅的命令。调用 subscribe
, pSubscribe
, unsubscribe
或 pUnsubscribe
以外的任何内容都会引发异常。
为了订阅消息,需要实现 MessageListener
回调。每次新消息到达时,都会调用回调,并且 onMessage
方法会运行用户代码。该接口不仅可以访问实际消息,还可以访问通过它接收的通道以及订阅用于匹配通道的模式(如果有)。此信息使被叫方不仅可以通过内容区分各种消息,还可以检查其他详细信息。
由于其阻塞性质,低级订阅不具吸引力,因为它需要每个单个侦听器的连接和线程管理。为了缓解这个问题,Spring Data提供了 RedisMessageListenerContainer
,它完成了所有繁重的工作。如果您熟悉EJB和JMS,那么您应该找到熟悉的概念,因为它的设计尽可能接近Spring Framework及其消息驱动的POJO(MDP)中的支持。
RedisMessageListenerContainer
充当消息侦听器容器。它用于从Redis通道接收消息并驱动注入其中的 MessageListener
实例。侦听器容器负责消息接收的所有线程并将其分派到侦听器中进行处理。消息监听器容器是MDP和消息传递提供者之间的中介,并负责注册以接收消息,资源获取和释放,异常转换等。这使您作为应用程序开发人员可以编写与接收消息(并对其做出反应)相关联的(可能是复杂的)业务逻辑,并将样板Redis基础结构关注委托给框架。
此外,为了最小化应用程序占用空间, RedisMessageListenerContainer
允许多个侦听器共享一个连接和一个线程,即使它们不共享订阅。因此,无论应用程序跟踪多少个侦听器或通道,运行时成本在其整个生命周期内保持不变。此外,容器允许更改运行时配置,以便您可以在应用程序运行时添加或删除侦听器,而无需重新启动。此外,容器使用延迟订阅方法,仅在需要时使用 RedisConnection
。如果所有侦听器都已取消订阅,则会自动执行清理,并释放该线程。
为了帮助消息的异步性,容器需要 java.util.concurrent.Executor
(或Spring的 TaskExecutor
)来分派消息。根据负载,侦听器数量或运行时环境,您应该更改或调整执行程序以更好地满足您的需求。特别是,在托管环境(例如应用程序服务器)中,强烈建议选择适当的 TaskExecutor
以利用其运行时。
MessageListenerAdapter
类是Spring异步消息传递支持的最后一个组件。简而言之,它允许您将几乎 any 类暴露为MDP(尽管存在一些约束)。
请考虑以下接口定义:
public interface MessageDelegate {
void handleMessage(String message);
void handleMessage(Map message); void handleMessage(byte[] message);
void handleMessage(Serializable message);
// pass the channel/pattern as well
void handleMessage(Serializable message, String channel);
}
请注意,虽然接口不扩展 MessageListener
接口,但仍可以使用 MessageListenerAdapter
类将其用作MDP。还要注意各种消息处理方法是如何根据它们可以接收和处理的各种 Message
类型的 contents 强类型化的。此外,发送消息的通道或模式可以作为 String
类型的第二个参数传递给方法:
public class DefaultMessageDelegate implements MessageDelegate {
// implementation elided for clarity...
}
注意 MessageDelegate
接口(上面的 DefaultMessageDelegate
类)的上述实现如何具有 no Redis依赖性。它确实是我们在MDP中使用以下配置制作的POJO:
<?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:redis="http://www.springframework.org/schema/redis"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/redis http://www.springframework.org/schema/redis/spring-redis.xsd">
<!-- the default ConnectionFactory -->
<redis:listener-container>
<!-- the method attribute can be skipped as the default method name is "handleMessage" -->
<redis:listener ref="listener" method="handleMessage" topic="chatroom" />
</redis:listener-container>
<bean id="listener" class="redisexample.DefaultMessageDelegate"/>
...
<beans>
侦听器主题可以是通道(例如,
topic="chatroom"
)或模式(例如,topic="*room"
)
前面的示例使用Redis命名空间声明消息侦听器容器并自动将POJO注册为侦听器。完整的bean定义如下:
<bean id="messageListener" class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter">
<constructor-arg>
<bean class="redisexample.DefaultMessageDelegate"/>
</constructor-arg>
</bean>
<bean id="redisContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="messageListeners">
<map>
<entry key-ref="messageListener">
<bean class="org.springframework.data.redis.listener.ChannelTopic">
<constructor-arg value="chatroom">
</bean>
</entry>
</map>
</property>
</bean>
每次收到消息时,适配器都会自动且透明地在低级格式和所需对象类型之间执行转换(使用已配置的 RedisSerializer
)。由方法调用引起的任何异常都由容器捕获并处理(默认情况下,会记录异常)。
Redis通过 multi
, exec
和 discard
命令为 transactions 提供支持。这些操作可在 RedisTemplate
上找到。但是, RedisTemplate
不保证在具有相同连接的事务中执行所有操作。
Spring Data Redis提供 SessionCallback
接口,以便在需要使用相同的 connection
执行多个操作时使用,例如使用Redis事务时。以下示例使用 multi
方法:
//execute a transaction
List<Object> txResults = redisTemplate.execute(new SessionCallback<List<Object>>() {
public List<Object> execute(RedisOperations operations) throws DataAccessException {
operations.multi();
operations.opsForSet().add("key", "value1");
// This will contain the results of all operations in the transaction
return operations.exec();
}
});
System.out.println("Number of items added to set: " + txResults.get(0));
RedisTemplate
使用其值,散列键和散列值序列化程序在返回之前反序列化 exec
的所有结果。还有一个 exec
方法可以让您为事务结果传递自定义序列化程序。
从版本1.1开始,对
RedisConnection
和RedisTemplate
的exec
方法进行了重要更改。以前,这些方法直接从连接器返回事务的结果。这意味着数据类型通常与RedisConnection
方法返回的数据类型不同。例如,zAdd
返回一个布尔值,指示元素是否已添加到有序集。大多数连接器将此值作为long返回,Spring Data Redis执行转换。另一个常见的区别是大多数连接器返回状态回复(通常是字符串,OK
),用于set
等操作。 Spring Data Redis通常会丢弃这些回复。在1.1之前,这些转换未在exec
的结果上执行。此外,结果未在RedisTemplate
中反序列化,因此它们通常包含原始字节数组。如果此更改破坏了您的应用程序,请在RedisConnectionFactory
上将convertPipelineAndTxResults
设置为false
以禁用此行为。
默认情况下,事务支持已禁用,必须通过设置 setEnableTransactionSupport(true)
为每个正在使用的 RedisTemplate
显式启用。这样做会强制将当前 RedisConnection
绑定到触发 MULTI
的当前 Thread
。如果事务完成且没有错误,则调用 EXEC
。否则 DISCARD
被调用。进入 MULTI
后, RedisConnection
队列写入操作。所有 readonly
操作(例如 KEYS
)都通过管道传输到新的(非线程绑定的) RedisConnection
。
以下示例显示了如何配置事务管理:
示例1.启用事务管理的配置
@Configuration
@EnableTransactionManagement (1)
public class RedisTxContextConfiguration {
@Bean
public StringRedisTemplate redisTemplate() {
StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
// explicitly enable transaction support
template.setEnableTransactionSupport(true); (2)
return template;
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
// jedis || Lettuce
}
@Bean
public PlatformTransactionManager transactionManager() throws SQLException {
return new DataSourceTransactionManager(dataSource()); (3)
}
@Bean
public DataSource dataSource() throws SQLException {
// ...
}
}
1 | 配置Spring上下文以启用 declarative transaction management 。 |
---|---|
2 | 配置 RedisTemplate 通过绑定到当前线程的连接来参与事务。 |
3 | 事务管理需要 PlatformTransactionManager 。 Spring Data Redis未附带 PlatformTransactionManager 实现。假设您的应用程序使用JDBC,Spring Data Redis可以使用现有的事务管理器参与事务。 |
以下示例均演示了使用限制:
示例2.使用限制
// must be performed on thread-bound connection
template.opsForValue().set("thing1", "thing2");
// read operation must be executed on a free (not transaction-aware) connection
template.keys("*");
// returns null as values set within a transaction are not visible
template.opsForValue().get("thing1");
Redis为 pipelining 提供支持,它涉及向服务器发送多个命令而无需等待回复,然后在一个步骤中读取回复。当您需要连续发送多个命令时,流水线操作可以提高性能,例如向同一个List添加许多元素。
Spring Data Redis提供了几种用于在管道中执行命令的 RedisTemplate
方法。如果您不关心流水线操作的结果,可以使用标准 execute
方法,为 pipeline
参数传递 true
。 executePipelined
方法在管道中运行提供的 RedisCallback
或 SessionCallback
并返回结果,如以下示例所示:
//pop a specified number of items from a queue
List<Object> results = stringRedisTemplate.executePipelined(
new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisConnection stringRedisConn = (StringRedisConnection)connection;
for(int i=0; i< batchSize; i++) {
stringRedisConn.rPop("myqueue");
}
return null;
}
});
前面的示例从管道中的队列运行批量右侧弹出的项目。 results
List
包含所有弹出的项目。 RedisTemplate
使用其值,散列键和散列值序列化程序在返回之前反序列化所有结果,因此前面示例中返回的项是字符串。还有其他 executePipelined
方法可让您为流水线结果传递自定义序列化程序。
请注意,从 RedisCallback
返回的值必须为null,因为此值将被丢弃,以支持返回流水线命令的结果。
从版本1.1开始,对
RedisConnection
和RedisTemplate
的exec
方法进行了重要更改。以前,这些方法直接从连接器返回事务的结果。这意味着数据类型通常与RedisConnection
方法返回的数据类型不同。例如,zAdd
返回一个布尔值,指示元素是否已添加到有序集合中。大多数连接器将此值作为long返回,Spring Data Redis执行转换。另一个常见的区别是大多数连接器为set
等操作返回状态答复(通常是字符串,OK
)。这些回复是通常由Spring Data Redis丢弃。在1.1之前,这些转换未在exec
的结果上执行。此外,结果未在RedisTemplate
中反序列化,因此它们通常包含原始字节数组。如果此更改破坏了您的应用程序,请在RedisConnectionFactory
上将convertPipelineAndTxResults
设置为false
以禁用此行为。
Redis 2.6及更高版本通过 eval 和 evalsha 命令为执行Lua脚本提供支持。 Spring Data Redis为脚本执行提供高级抽象,处理序列化并自动使用Redis脚本缓存。
可以通过调用 RedisTemplate
和 ReactiveRedisTemplate
的 execute
方法来运行脚本。两者都使用可配置的 ScriptExecutor
(或 ReactiveScriptExecutor
)来运行提供的脚本。默认情况下, ScriptExecutor
(或 ReactiveScriptExecutor
)负责序列化提供的键和参数以及反序列化脚本结果。这是通过模板的键和值序列化程序完成的。还有一个额外的重载,允许您为脚本参数和结果传递自定义序列化程序。
默认 ScriptExecutor
通过检索脚本的SHA1并尝试首先运行 evalsha
来优化性能,如果Redis脚本缓存中尚不存在脚本,则返回 eval
。
以下示例使用Lua脚本运行常见的“检查和设置”方案。这是Redis脚本的理想用例,因为它要求以原子方式运行一组命令,并且一个命令的行为受另一个命令的结果的影响。
@Bean
public RedisScript<Boolean> script() {
ScriptSource scriptSource = new ResourceScriptSource(new ClassPathResource("META-INF/scripts/checkandset.lua");
return RedisScript.of(scriptSource, Boolean.class);
}
public class Example {
@Autowired
RedisScript<Boolean> script;
public boolean checkAndSet(String expectedValue, String newValue) {
return redisTemplate.execute(script, singletonList("key"), asList(expectedValue, newValue));
}
}
-- checkandset.lua
local current = redis.call('GET', KEYS[1])
if current == ARGV[1]
then redis.call('SET', KEYS[1], ARGV[2])
return true
end
return false
上面的代码配置 RedisScript
指向一个名为 checkandset.lua
的文件,该文件应该返回一个布尔值。脚本 resultType
应该是 Long
, Boolean
, List
或反序列化值类型之一。如果脚本返回一个丢弃状态(具体地说, OK
),它也可以是 null
。
最好在应用程序上下文中配置
DefaultRedisScript
的单个实例,以避免在每次执行脚本时重新计算脚本的SHA1。
然后上面的 checkAndSet
方法运行脚本。脚本可以作为事务或管道的一部分在 SessionCallback
内运行。有关详细信息,请参阅“ Redis Transactions ”和“ Pipelining ”。
Spring Data Redis提供的脚本支持还允许您使用Spring Task和Scheduler抽象来安排Redis脚本以定期执行。有关更多详细信息,请参阅 Spring Framework 文档。
包 org.springframework.data.redis.support
提供了各种可重用的组件,这些组件依赖Redis作为后备存储。目前,该软件包在Redis之上包含各种基于JDK的接口实现,例如 atomic 计数器和JDK Collections 。
原子计数器可以轻松地包装Redis密钥增量,而集合允许以最小的存储空间或API泄漏轻松管理Redis密钥。特别是, RedisSet
和 RedisZSet
接口可以轻松访问Redis支持的设置操作,例如 intersection
和 union
。 RedisList
在Redis之上实现 List
, Queue
和 Deque
Contract (及其等效的阻塞兄弟),将存储暴露为FIFO(先进先出),LIFO(后进先出)或上限最小配置的集合。以下示例显示了使用 RedisList
的bean的配置:
<?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" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="queue" class="org.springframework.data.redis.support.collections.DefaultRedisList">
<constructor-arg ref="redisTemplate"/>
<constructor-arg value="queue-key"/>
</bean>
</beans>
以下示例显示了 Deque
的Java配置示例:
public class AnotherExample {
// injected
private Deque<String> queue;
public void addTag(String tag) {
queue.push(tag);
}
}
如前面的示例所示,使用代码与实际存储实现分离。实际上,没有迹象表明Redis被用在了下面。这使得从开发环境转移到 生产环境 环境变得透明并大大提高了可测试性(Redis实现可以用内存中的实现替换)。
在2.0中更改
Spring Redis通过 org.springframework.data.redis.cache
包为Spring cache abstraction 提供了一个实现。要将Redis用作支持实现,请将 RedisCacheManager
添加到您的配置中,如下所示:
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
return RedisCacheManager.create(connectionFactory);
}
RedisCacheManager
行为可以使用 RedisCacheManagerBuilder
进行配置,允许您设置默认的 RedisCacheConfiguration
,事务行为和预定义的高速缓存。
RedisCacheManager cm = RedisCacheManager.builder(connectionFactory)
.cacheDefaults(defaultCacheConfig())
.withInitialCacheConfigurations(singletonMap("predefined", defaultCacheConfig().disableCachingNullValues()))
.transactionAware()
.build();
如前面的示例所示, RedisCacheManager
允许基于每个缓存定义配置。
使用 RedisCacheManager
创建的 RedisCache
的行为是使用 RedisCacheConfiguration
定义的。通过该配置,您可以设置密钥到期时间,前缀和 RedisSerializer
实现,以便与二进制存储格式进行转换,如以下示例所示:
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(1))
.disableCachingNullValues();
RedisCacheManager
默认为无锁 RedisCacheWriter
,用于读取和写入二进制值。无锁缓存可提高吞吐量。缺少条目锁定可能导致 putIfAbsent
和 clean
方法的重叠非原子命令,因为这些命令需要发送多个命令Redis的。锁定对应物通过设置显式锁定键并检查是否存在此键来防止命令重叠,这会导致其他请求和潜在的命令等待时间。
可以按如下方式选择锁定行为:
RedisCacheManager cm = RedisCacheManager.build(RedisCacheWriter.lockingRedisCacheWriter())
.cacheDefaults(defaultCacheConfig())
...
默认情况下,缓存条目的任何 key
都以实际缓存名称为后缀,后跟两个冒号。此行为可以更改为静态和计算前缀。
以下示例显示如何设置静态前缀:
// static key prefix
RedisCacheConfiguration.defaultCacheConfig().prefixKeysWith("( ͡° ᴥ ͡°)");
The following example shows how to set a computed prefix:
// computed key prefix
RedisCacheConfiguration.defaultCacheConfig().computePrefixWith(cacheName -> "¯\_(ツ)_/¯" + cacheName);
下表列出了 RedisCacheManager
的默认设置:
设置 | 值 |
---|---|
缓存写入器 | 非锁定 |
缓存配置 | RedisCacheConfiguration#defaultConfiguration |
初始缓存 | 无 |
Trasaction Aware | 否 |
下表列出了 RedisCacheConfiguration
的默认设置:
密钥有效期 | 无 |
---|---|
缓存 null | 是 |
前缀键 | 是 |
默认前缀 | 实际缓存名称 |
Key Serializer | StringRedisSerializer |
Value Serializer | JdkSerializationRedisSerializer |
转换服务 | DefaultFormattingConversionService ,带有默认缓存密钥转换器 |
本节介绍了Redis的反应支持以及如何入门。 Reactive Redis支持自然与 imperative Redis support 有某些重叠。
Spring Data Redis目前与 Lettuce 集成为唯一的反应式Java连接器。 Project Reactor 用作反应组合库。
使用Redis和Spring时的首要任务之一是通过IoC容器连接到 Store 。为此,需要Java连接器(或绑定)。无论您选择哪个库,都必须使用 org.springframework.data.redis.connection
包及其 ReactiveRedisConnection
和 ReactiveRedisConnectionFactory
接口来处理和检索活动 connections
到Redis。
Redis可以作为独立服务器运行,具有 Redis Sentinel 或 Redis Cluster 模式。 Lettuce 支持所有前面提到的连接类型。
ReactiveRedisConnection
是Redis通信的核心,因为它处理与Redis后端的通信。它还会自动将底层驱动程序异常转换为Spring的一致DAO异常 hierarchy ,因此您可以在不更改任何代码的情况下切换连接器,因为操作语义保持不变。
ReactiveRedisConnectionFactory
创建活动 ReactiveRedisConnection
实例。此外,工厂充当 PersistenceExceptionTranslator
实例,这意味着,一旦声明,它们就会让您进行透明异常转换 - 例如,通过使用 @Repository
注释和AOP进行异常转换。有关更多信息,请参阅Spring Framework文档中的专用 section 。
根据基础配置,工厂可以返回新连接或现有连接(如果使用池或共享本机连接)。
使用
ReactiveRedisConnectionFactory
的最简单方法是通过IoC容器配置相应的连接器并将其注入using类。
Spring Data Redis通过 org.springframework.data.redis.connection.lettuce
包支持 Lettuce 。
您可以按如下方式为Lettuce设置 ReactiveRedisConnectionFactory
:
@Bean
public ReactiveRedisConnectionFactory connectionFactory() {
return new LettuceConnectionFactory("localhost", 6379);
}
以下示例显示了使用 LettuceClientConfigurationBuilder
的更复杂的配置,包括SSL和超时:
@Bean
public ReactiveRedisConnectionFactory lettuceConnectionFactory() {
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.useSsl().and()
.commandTimeout(Duration.ofSeconds(2))
.shutdownTimeout(Duration.ZERO)
.build();
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("localhost", 6379), clientConfig);
}
有关更详细的客户端配置调整,请参阅 LettuceClientConfiguration 。
大多数用户可能会使用 ReactiveRedisTemplate
及其相应的包 org.springframework.data.redis.core
。由于其丰富的功能集,模板实际上是Redis模块的中心类。该模板为Redis交互提供了高级抽象。虽然 ReactiveRedisConnection
提供了接受和返回二进制值( ByteBuffer
)的低级方法,但该模板负责序列化和连接管理,使您无需处理此类详细信息。
此外,该模板提供操作视图(遵循Redis命令 reference 的分组),提供丰富的,通用的接口,用于处理特定类型,如下表所述:
接口 | 说明 |
---|---|
键类型操作 | |
ReactiveGeoOperations | Redis地理空间操作,例如 GEOADD , GEORADIUS 和其他) |
ReactiveHashOperations | Redis 哈希操作 |
ReactiveHyperLogLogOperations | Redis HyperLogLog操作,例如( PFADD , PFCOUNT 等) |
ReactiveListOperations | Redis list 操作 |
ReactiveSetOperations | Redis set 操作 |
ReactiveValueOperations | Redis string(或value)操作 |
ReactiveZSetOperations | Redis zset(或有序集)操作 |
配置完成后,模板是线程安全的,可以跨多个实例重用。
ReactiveRedisTemplate
在其大多数操作中使用基于Java的序列化程序。这意味着模板编写或读取的任何对象都通过 RedisElementWriter
或 RedisElementReader
序列化或反序列化。序列化上下文在构建时传递给模板,Redis模块提供 org.springframework.data.redis.serializer
包中的几个实现。有关更多信息,请参见 Serializers 。
以下示例显示 ReactiveRedisTemplate
用于返回 Mono
:
@Configuration
class RedisConfiguration {
@Bean
ReactiveRedisTemplate<String, String> reactiveRedisTemplate(ReactiveRedisConnectionFactory factory) {
return new ReactiveRedisTemplate<>(factory, RedisSerializationContext.string());
}
}
public class Example {
@Autowired
private ReactiveRedisTemplate<String, String> template;
public Mono<Long> addLink(String userId, URL url) {
return template.opsForList().leftPush(userId, url.toExternalForm());
}
}
由于存储在Redis中的键和值通常是 java.lang.String
,因此Redis模块为 ReactiveRedisTemplate
: ReactiveStringRedisTemplate
提供了基于字符串的扩展。对于密集的 String
操作,它是一种方便的一站式解决方案。除了绑定到 String
键之外,模板还使用基于字符串的 RedisSerializationContext
,这意味着存储的键和值是人类可读的(假设在Redis和代码中使用相同的编码)。以下示例显示正在使用的 ReactiveStringRedisTemplate
:
@Configuration
class RedisConfiguration {
@Bean
ReactiveStringRedisTemplate reactiveRedisTemplate(ReactiveRedisConnectionFactory factory) {
return new ReactiveStringRedisTemplate<>(factory);
}
}
public class Example {
@Autowired
private ReactiveStringRedisTemplate redisTemplate;
public Mono<Long> addLink(String userId, URL url) {
return redisTemplate.opsForList().leftPush(userId, url.toExternalForm());
}
}
Spring Data为Redis提供了专用的消息传递集成,在功能和命名方面与Spring Framework中的JMS集成非常相似;事实上,熟悉Spring中JMS支持的用户应该感到宾至如归。
Redis消息传递可以大致分为两个功能区域,即消息的生成或发布以及消费或订阅,因此快捷方式pubsub(发布/订阅)。 ReactiveRedisTemplate
类用于生成消息。对于异步接收,Spring Data提供了一个专用的消息监听器容器,用于使用消息流。出于订阅的目的 ReactiveRedisTemplate
提供了使用监听器容器的精简替代方案。
包 org.springframework.data.redis.connection
和 org.springframework.data.redis.listener
提供了使用Redis消息传递的核心功能。
要发布消息,可以像其他操作一样使用低级 ReactiveRedisConnection
或高级 ReactiveRedisTemplate
。两个实体都提供一种发布方法,该方法接受需要发送的消息以及目标通道作为参数。虽然 ReactiveRedisConnection
需要原始数据,但 ReactiveRedisTemplate
允许任意对象作为消息传入:
// send message through ReactiveRedisConnection
ByteBuffer msg = …
ByteBuffer channel = …
Mono<Long> publish = con.publish(msg, channel);
// send message through ReactiveRedisTemplate
ReactiveRedisTemplate template = …
Mono<Long> publish = template.convertAndSend("channel", "message");
在接收方,可以通过直接命名或使用模式匹配来订阅一个或多个通道。后一种方法非常有用,因为它不仅允许使用一个命令创建多个订阅,而且还可以监听尚未在订阅时创建的通道(只要它们与模式匹配)。
在低级别, ReactiveRedisConnection
提供了 subscribe
和 pSubscribe
方法,这些方法通过模式分别映射Redis命令以按通道订阅。请注意,可以使用多个通道或模式作为参数。要更改订阅,只需查询 ReactiveSubscription
的 Channels 和模式。
Spring Data Redis中的反向订阅命令是非阻塞的,可以在不发出元素的情况下终止。
如上所述,一旦订阅连接就开始等待消息。除了添加新订阅或修改/取消现有订阅外,不能在其上调用其他命令。 subscribe
, pSubscribe
, unsubscribe
或 pUnsubscribe
以外的命令是非法的,会导致异常。
为了接收消息,需要获取消息流。请注意,订阅仅发布针对该特定订阅注册的 Channels 和模式的消息。消息流本身是一个热门序列,可以在不考虑需求的情况下生成元素。确保注册足够的需求以避免耗尽消息缓冲区。
Spring Data提供 ReactiveRedisMessageListenerContainer
,它代表用户完成所有繁重的转换和订阅状态管理。
ReactiveRedisMessageListenerContainer
充当消息侦听器容器。它用于从Redis通道接收消息,并公开应用反序列化发出通道消息的消息流。它负责注册接收消息,资源获取和释放,异常转换等。这允许您作为应用程序开发人员编写与接收消息(并对其做出反应)相关联的(可能是复杂的)业务逻辑,并将样板Redis基础结构关注委托给框架。消息流在发布者订阅时在Redis中注册订阅,如果是,则取消注册订阅被取消。
此外,为了最小化应用程序占用空间, ReactiveRedisMessageListenerContainer
允许多个侦听器共享一个连接和一个线程,即使它们不共享订阅。因此,无论应用程序跟踪多少个侦听器或通道,运行时成本在其生命周期内将保持不变。此外,容器允许运行时配置更改,因此可以在应用程序运行时添加或删除侦听器,而无需重新启动。此外,容器使用延迟订阅方法,仅在需要时使用 ReactiveRedisConnection
- 如果所有侦听器都已取消订阅,则会自动执行清理。
消息侦听器容器本身不需要外部线程资源。它使用驱动程序线程来发布消息。
ReactiveRedisConnectionFactory factory = …
ReactiveRedisMessageListenerContainer container = new ReactiveRedisMessageListenerContainer(factory);
Flux<ChannelMessage<String, String>> stream = container.receive(ChannelTopic.of("my-chanel"));
如上所述,您可以直接使用 ReactiveRedisTemplate
订阅 Channels /模式。这种方法提供了一种直接的,虽然有限的解决方案,因为您放弃了在初始订阅后添加订阅的选项。尽管如此,您仍然可以通过返回的 Flux
使用例如控制消息流。 take(Duration)
。完成读取后,在出错或取消时,将再次释放所有绑定资源。
redisTemplate.listenToChannel("channel1", "channel2").doOnNext(msg -> {
// message processing ...
}).subscribe();
通过响应式基础架构执行Redis脚本可以通过 ReactiveRedisTemplate
使用 ReactiveScriptExecutor
访问最佳。
public class Example {
@Autowired
private ReactiveRedisTemplate<String, String> template;
public Flux<Long> theAnswerToLife() {
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setLocation(new ClassPathResource("META-INF/scripts/42.lua"));
script.setResultType(Long.class);
return reactiveTemplate.execute(script);
}
}
有关脚本命令的更多详细信息,请参阅 scripting section 。
使用 Redis Cluster 需要Redis Server 3.0版。有关更多信息,请参阅 Cluster Tutorial 。
群集支持基于与非群集通信相同的构建块。 RedisClusterConnection
, RedisConnection
的扩展,处理与Redis群集的通信,并将错误转换为Spring DAO异常层次结构。 RedisClusterConnection
实例是使用 RedisConnectionFactory
创建的,必须使用关联的 RedisClusterConfiguration
进行设置,如以下示例所示:
示例3. Redis群集的RedisConnectionFactory配置示例
@Component
@ConfigurationProperties(prefix = "spring.redis.cluster")
public class ClusterConfigurationProperties {
/*
* spring.redis.cluster.nodes[0] = 127.0.0.1:7379
* spring.redis.cluster.nodes[1] = 127.0.0.1:7380
* ...
*/
List<String> nodes;
/**
* Get initial collection of known cluster nodes in format {@code host:port}.
*
* @return
*/
public List<String> getNodes() {
return nodes;
}
public void setNodes(List<String> nodes) {
this.nodes = nodes;
}
}
@Configuration
public class AppConfig {
/**
* Type safe representation of application.properties
*/
@Autowired ClusterConfigurationProperties clusterProperties;
public @Bean RedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory(
new RedisClusterConfiguration(clusterProperties.getNodes()));
}
}
RedisClusterConfiguration
也可以通过PropertySource
定义,并具有以下属性:
配置属性
spring.redis.cluster.nodes
:以逗号分隔的主机:端口对列表。
spring.redis.cluster.max-redirects
:允许的群集重定向数。
初始配置将驱动程序库指向一组初始集群节点。实时群集重新配置导致的更改仅保留在本机驱动程序中,不会写回配置。
如前所述,Redis群集的行为与单节点Redis或甚至Sentinel监控的主副本环境不同。这是因为自动分片将密钥映射到16384个插槽之一,这些插槽分布在节点上。因此,涉及多个密钥的命令必须断言所有密钥映射到完全相同的插槽,以避免跨时隙执行错误。单个群集节点仅提供一组专用密钥。针对一个特定服务器发出的命令仅返回该服务器所服务的密钥的结果。举个简单的例子,考虑 KEYS
命令。当发布到群集环境中的服务器时,它仅返回请求发送到的节点所服务的密钥,而不一定返回群集中的所有密钥。因此,要获取群集环境中的所有密钥,必须从所有已知主节点读取密钥。
虽然驱动程序库会处理特定键到相应插槽服务节点的重定向,但更高级别的功能(例如跨节点收集信息或向集群中的所有节点发送命令)均由 RedisClusterConnection
涵盖。从前面提取密钥示例,这意味着 keys(pattern)
方法获取集群中的每个主节点,同时在每个主节点上执行 KEYS
命令,同时获取结果并返回累积的密钥集。要仅请求单个节点的密钥 RedisClusterConnection
为这些方法提供重载(例如, keys(node, pattern)
)。
RedisClusterNode
可以从 RedisClusterConnection.clusterGetNodes
获得,也可以使用主机和端口或节点Id构建。
以下示例显示了在集群中运行的一组命令:
示例4.跨群集运行命令的示例
[email protected]:7379 > cluster nodes
6b38bb... 127.0.0.1:7379 master - 0 0 25 connected 0-5460 (1)
7bb78c... 127.0.0.1:7380 master - 0 1449730618304 2 connected 5461-10922 (2)
164888... 127.0.0.1:7381 master - 0 1449730618304 3 connected 10923-16383 (3)
b8b5ee... 127.0.0.1:7382 slave 6b38bb... 0 1449730618304 25 connected (4)
RedisClusterConnection connection = connectionFactory.getClusterConnnection();
connection.set("thing1", value); (5)
connection.set("thing2", value); (6)
connection.keys("*"); (7)
connection.keys(NODE_7379, "*"); (8)
connection.keys(NODE_7380, "*"); (9)
connection.keys(NODE_7381, "*"); (10)
connection.keys(NODE_7382, "*"); (11)
1 | 主节点服务插槽0到5460复制到副本7382 | |
---|---|---|
2 | 主节点服务插槽5461至10922 | |
3 | 主节点服务槽10923至16383 | |
4 | 在7379 | 持有主人复制者的副本节点 |
5 | 请求路由到7381服务时隙的节点12182 | |
6 | 请求路由到节点7379服务插槽5061 | |
7 | 请求路由到节点7379,7380,7381→[thing1,thing2] | |
8 | 请求路由到节点7379→[thing2] | |
9 | 请求路由到节点7380→[] | |
10 | 请求路由到7381的节点→[thing1] | |
11 | 请求路由到节点7382→[thing2] |
当所有键映射到同一插槽时,本机驱动程序库会自动提供跨插槽请求,例如 MGET
。但是,如果不是这种情况, RedisClusterConnection
对插槽服务节点执行多个并行 GET
命令,并再次返回累积结果。这比单槽执行效率低,因此应谨慎使用。如果有疑问,请考虑通过在大括号中提供前缀(例如 {my-prefix}.thing1
和 {my-prefix}.thing2
)将密钥固定到同一个插槽,这两个前缀都将映射到相同的插槽号。以下示例显示了跨槽请求处理:
示例5.交叉槽请求处理的示例
[email protected]:7379 > cluster nodes
6b38bb... 127.0.0.1:7379 master - 0 0 25 connected 0-5460 (1)
7bb...
RedisClusterConnection connection = connectionFactory.getClusterConnnection();
connection.set("thing1", value); // slot: 12182
connection.set("{thing1}.thing2", value); // slot: 12182
connection.set("thing2", value); // slot: 5461
connection.mGet("thing1", "{thing1}.thing2"); (2)
connection.mGet("thing1", "thing2"); (3)
1 | 与之前的示例相同的配置。 |
---|---|
2 | 键映射到同一个插槽→127.0.0.1:7381 MGET thing1 .thing2 |
3 | 键映射到不同的插槽并分成单个插槽,路由到相应的节点→127.0.0.1:7379 GET thing2→127.0.0.1:7381 GET thing1 |
上述示例演示了Spring Data Redis遵循的一般策略。请注意,某些操作可能需要将大量数据加载到内存中以计算所需的命令。此外,并非所有交叉插槽请求都可以安全地移植到多个单插槽请求,如果误用,则会出错(例如,
PFCOUNT
)。
有关 RedisTemplate
的一般用途,配置和用法的信息,请参见 Working with Objects through RedisTemplate 部分。
使用任何JSON
RedisSerializers
设置RedisTemplate#keySerializer
时要小心,因为更改JSON结构会立即影响哈希槽计算。
RedisTemplate
通过 ClusterOperations
接口提供对特定于集群的操作的访问,该接口可以从 RedisTemplate.opsForCluster()
获得。这使您可以在集群中的单个节点上显式运行命令,同时保留为模板配置的序列化和反序列化功能。它还提供管理命令(例如 CLUSTER MEET
)或更多高级操作(例如,重新分片)。
以下示例显示如何使用 RedisTemplate
访问 RedisClusterConnection
:
示例6.使用 RedisTemplate
访问 RedisClusterConnection
ClusterOperations clusterOps = redisTemplate.opsForCluster();
clusterOps.shutdown(NODE_7379); (1)
1 | 在7379关闭节点并交叉手指有一个可以接管的复制品。 |
---|
使用Redis存储库可以在Redis Hashes中无缝转换和存储域对象,应用自定义映射策略以及使用二级索引。
Redis存储库至少需要Redis Server版本2.8.0,不适用于事务。确保使用
RedisTemplate
和 disabled transaction support 。
Spring Data Redis允许您轻松实现域实体,如以下示例所示:
示例7.示例人员实体
@RedisHash("people")
public class Person {
@Id String id;
String firstname;
String lastname;
Address address;
}
我们这里有一个非常简单的域对象。请注意,它的类型有一个 @RedisHash
注释,而 org.springframework.data.annotation.Id
注释了一个名为 id
的属性。这两个项目负责创建用于保持哈希的实际密钥。
使用
@Id
注释的属性以及名为id
的属性被视为标识符属性。那些带有注释的人比其他人更受青睐。
现在实际上有一个负责存储和检索的组件,我们需要定义一个存储库接口,如下例所示:
示例8.持久保存人员实体的基本存储库接口
public interface PersonRepository extends CrudRepository<Person, String> {
}
随着我们的存储库扩展 CrudRepository
,它提供了基本的CRUD和finder操作。我们需要将事物粘合在一起的是相应的Spring配置,如下例所示:
示例9. Redis存储库的JavaConfig
@Configuration
@EnableRedisRepositories
public class ApplicationConfig {
@Bean
public RedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory();
}
@Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
return template;
}
}
鉴于前面的设置,我们可以将 PersonRepository
注入到我们的组件中,如以下示例所示:
示例10.访问人员实体
@Autowired PersonRepository repo;
public void basicCrudOperations() {
Person rand = new Person("rand", "al'thor");
rand.setAddress(new Address("emond's field", "andor"));
repo.save(rand); (1)
repo.findOne(rand.getId()); (2)
repo.count(); (3)
repo.delete(rand); (4)
}
1 | 如果当前值为 null ,则生成新的 id 或重新使用已设置的 id 值,并使用模式为 keyspace:id 的密钥在Redis哈希中存储 Person 类型的属性 - 在这种情况下,它可能是 people:5d67b7e1-8640-4475-beeb-c666fab4c0e5 。 |
---|---|
2 | 使用提供的 id 检索存储在 keyspace:id 的对象。 |
3 | 计算可用实体的总数键空间 people ,由 @RedisHash 在 Person 上定义。 |
4 | 从Redis中删除给定对象的键。 |
本节介绍Spring Data对象映射,对象创建,字段和属性访问,可变性和不变性的基础知识。注意,本节仅适用于不使用底层数据存储的对象映射的Spring Data模块(如JPA)。另外,请务必查阅特定于 Store 的部分以了解特定于 Store 的对象映射,例如索引,自定义列或字段名称等。
Spring Data对象映射的核心职责是创建域对象的实例,并将存储本机数据结构映射到这些对象上。这意味着我们需要两个基本步骤:
使用其中一个公开的构造函数创建实例。
要实现所有公开属性的实例填充。
Spring Data会自动尝试检测持久化实体的构造函数,以用于实现该类型的对象。分辨率算法的工作原理如下:
如果有一个无参数的构造函数,它将被使用。其他构造函数将被忽略。
如果有一个构造函数接受参数,它将被使用。
如果有多个构造函数使用参数,则Spring Data要使用的构造函数必须使用 @PersistenceConstructor
进行注释。
值解析假定构造函数参数名称与实体的属性名称匹配,即将执行解析,就像要填充属性一样,包括映射中的所有自定义(不同的数据存储列或字段名称等)。这还需要类文件中可用的参数名称信息或构造函数上存在的 @ConstructorProperties
注释。
可以使用Spring Framework的 @Value
值注释使用特定于 Store 的SpEL表达式来自定义值解析。有关更多详细信息,请参阅有关 Store 特定映射的部分。
对象创建内部
为了避免反射的开销,Spring Data对象创建使用默认情况下在运行时生成的工厂类,它将直接调用域类构造函数。即对于此示例类型:
class Person {
Person(String firstname, String lastname) { … }
}
我们将在运行时创建一个在语义上等效于此工厂类的工厂类:
class PersonObjectInstantiator implements ObjectInstantiator {
Object newInstance(Object... args) {
return new Person((String) args[0], (String) args[1]);
}
}
这使得我们在反射方面的性能提升了10%。要使域类符合此类优化的条件,它需要遵守一组约束:
它不能是私人类
它不能是非静态内部类
它不能是CGLib代理类
Spring Data使用的构造函数不能是私有的
如果这些条件中的任何一个匹配,Spring Data将通过反射回退到实体实例化。
一旦创建了实体的实例,Spring Data就会填充该类的所有剩余持久属性。除非已经由实体的构造函数填充(即通过其构造函数参数列表使用),否则将首先填充identifier属性以允许循环对象引用的解析。之后,在实体实例上设置尚未由构造函数填充的所有非瞬态属性。为此,我们使用以下算法:
如果属性是不可变的但是暴露了一个wither方法(见下文),我们使用wither来创建一个具有新属性值的新实体实例。
如果定义了属性访问(即通过getter和setter访问),我们将调用setter方法。
默认情况下,我们直接设置字段值。
property 人口内部
与我们的 optimizations in object construction 类似,我们还使用Spring Data运行时生成的访问器类与实体实例进行交互。
class Person {
private final Long id;
private String firstname;
private @AccessType(Type.PROPERTY) String lastname;
Person() {
this.id = null;
}
Person(Long id, String firstname, String lastname) {
// Field assignments
}
Person withId(Long id) {
return new Person(id, this.firstname, this.lastame);
}
void setLastname(String lastname) {
this.lastname = lastname;
}
}
示例11.生成的Property Accessor
class PersonPropertyAccessor implements PersistentPropertyAccessor {
private static final MethodHandle firstname; (2)
private Person person; (1)
public void setProperty(PersistentProperty property, Object value) {
String name = property.getName();
if ("firstname".equals(name)) {
firstname.invoke(person, (String) value); (2)
} else if ("id".equals(name)) {
this.person = person.withId((Long) value); (3)
} else if ("lastname".equals(name)) {
this.person.setLastname((String) value); (4)
}
}
}
1 | PropertyAccessor持有底层对象的可变实例。这是为了实现其他不可变属性的突变。 |
---|---|
2 | 默认情况下,Spring Data使用字段访问来读取和写入属性值。根据 private 字段的可见性规则, MethodHandles 用于与字段交互。 |
3 | 该类公开了一个用于设置标识符的 withId(…) 方法,例如将实例插入数据存储区并生成标识符时。调用 withId(…) 会创建一个新的 Person 对象。所有后续突变都将在新实例中发生,而前一个突变不会发生。 |
4 | 使用property-access允许直接方法调用而不使用 MethodHandles 。 |
这使我们在反射方面的性能提升了25%。要使域类符合此类优化条件,它需要遵循一组限制:
类型不得位于默认值或 java
包下。
类型及其构造函数必须为 public
内部类的类型必须是 static
。
使用过的Java Runtime必须允许在原始 ClassLoader
中声明类。 Java 9和更新版本施加了某些限制。
默认情况下,如果检测到限制,Spring Data会尝试使用生成的属性访问器并回退到基于反射的访问器。
我们来看看以下实体:
示例12.样本实体
class Person {
private final @Id Long id; (1)
private final String firstname, lastname; (2)
private final LocalDate birthday;
private final int age; (3)
private String comment; (4)
private @AccessType(Type.PROPERTY) String remarks; (5)
static Person of(String firstname, String lastname, LocalDate birthday) { (6)
return new Person(null, firstname, lastname, birthday,
Period.between(birthday, LocalDate.now()).getYears());
}
Person(Long id, String firstname, String lastname, LocalDate birthday, int age) { (6)
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.birthday = birthday;
this.age = age;
}
Person withId(Long id) { (1)
return new Person(id, this.firstname, this.lastname, this.birthday);
}
void setRemarks(String remarks) { (5)
this.remarks = remarks;
}
}
1 | 标识符属性为final,但在构造函数中设置为 null 。该类公开了一个用于设置标识符的 withId(…) 方法,例如将实例插入数据存储区并生成标识符时。原始 Person 实例在创建新实例时保持不变。相同的模式通常应用于存储管理的其他属性,但可能必须更改以进行持久性操作。 | |
---|---|---|
2 | firstname 和 lastname 属性是可能通过getter公开的普通不可变属性。 | |
3 | age 属性是 birthday 属性中的不可变但派生的属性。在显示设计的情况下,数据库值将胜过默认值,因为Spring Data使用唯一声明的构造函数。即使意图是计算应该是首选,重要的是这个构造函数也将 age 作为参数(可能忽略它),否则属性填充步骤将尝试设置age字段并因为它是不可变的而失败。枯萎的存在。 | |
4 | comment 属性是可变的,通过直接设置其字段来填充。 | |
5 | remarks 属性是可变的并通过直接设置 comment 字段或通过调用 | 的setter方法来填充 |
6 | 该类公开了一个工厂方法和一个用于创建对象的构造函数。这里的核心思想是使用工厂方法而不是其他构造函数来避免构造函数通过 @PersistenceConstructor 消除歧义。相反,在工厂方法中处理属性的默认值。 |
尝试坚持不可变对象 - 不可变对象很容易创建,因为实现对象只是调用它的构造函数。此外,这可以避免使用允许客户端代码操纵对象状态的setter方法来填充域对象。如果您需要这些,请更喜欢使它们受到保护,以便只能通过有限数量的共存类型来调用它们。仅构造函数的实现比属性总体快30%。
提供一个all-args构造函数 - 即使你不能或不想将你的实体建模为不可变值,提供一个构造函数仍然是有 Value 的,该构造函数将实体的所有属性作为参数,包括可变属性,因为这允许对象映射跳过属性填充以获得最佳性能。
使用工厂方法而不是重载的构造函数来避免 @PersistenceConstructor
- 为了获得最佳性能所需的全参数构造函数,我们通常希望公开更多的应用程序用例特定构造函数,这些构造函数省略了自动生成的标识符等内容。这是一种已 Build 的模式使用静态工厂方法来公开all-args构造函数的这些变体。
确保遵守允许使用生成的实例化器和属性访问器类的约束 -
对于要生成的标识符,仍将最终字段与枯萎方法结合使用 -
使用Lombok来避免样板代码 - 由于持久性操作通常需要构造函数接受所有参数,因此它们的声明变成了对字段赋值的样板参数的繁琐重复,使用Lombok的 @AllArgsConstructor
可以最好地避免这种重复。
Spring Data调整Kotlin的细节以允许对象创建和变异。
支持实例化Kotlin类,默认情况下所有类都是不可变的,并且需要显式属性声明来定义可变属性。考虑以下 data
class Person
:
data class Person(val id: String, val name: String)
上面的类使用显式构造函数编译为典型的类。我们可以通过添加另一个构造函数来自定义此类,并使用 @PersistenceConstructor
注释它以指示构造函数首选项:
data class Person(var id: String, val name: String) {
@PersistenceConstructor
constructor(id: String) : this(id, "unknown")
}
如果未提供参数,则允许使用默认值,Kotlin支持参数选项。当Spring Data检测到具有参数默认值的构造函数时,如果数据存储没有,则它将保留这些参数提供一个值(或简单地返回 null
),以便Kotlin可以应用参数默认值。考虑以下为 name
应用参数默认值的类
data class Person(var id: String, val name: String = "unknown")
每次 name
参数不是结果的一部分或其值是 null
时, name
默认为 unknown
。
在Kotlin中,默认情况下所有类都是不可变的,并且需要显式属性声明来定义可变属性。考虑以下 data
class Person
:
data class Person(val id: String, val name: String)
这个类实际上是不可变的。它允许创建新实例,因为Kotlin生成一个 copy(…)
方法,该方法创建新对象实例,从现有对象复制所有属性值,并将作为参数提供的属性值应用于方法。
Redis存储库支持将对象持久化为哈希。这需要一个由 RedisConverter
完成的Object-to-Hash转换。默认实现使用 Converter
将属性值映射到Redis本机 byte[]
。
给定前面部分中的 Person
类型,默认映射如下所示:
_class = org.example.Person (1)
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand (2)
lastname = al’thor
address.city = emond's field (3)
address.country = andor
1 | _class 属性包含在根级别以及任何嵌套接口或抽象类型上。 |
---|---|
2 | 简单属性值按路径映射。 |
3 | 复杂类型的属性由其点路径映射。 |
下表描述了默认的映射规则:
类型 | 示例 | 映射值 |
---|---|---|
简单类型(例如,字符串) | String firstname =“rand”; | firstname =“rand” |
复杂类型(例如,地址) | 地址地址=新地址(“emond的字段”); | address.city =“emond's field” |
简单类型列表 | List <String> nicknames = asList(“dragon reborn”,“lews therin”); | 昵称。[0] = "dragon reborn" ,昵称。[1] = "lews therin" |
简单类型的 Map | Map <String,String> atts = asMap({“eye-color”,“grey”},{“... | atts。[eye-color] = "grey" ,atts。[hair-color] = “...... |
复杂类型列表 | 列表<地址>地址= asList(新地址(“em ... | 地址。[0] .city = "emond’s field" ,地址。[1] .city =”... |
复杂类型的 Map | Map <String,Address> addresses = asMap({“home”,new Address(“em ... | addresses。[home] .city = "emond’s field" ,addresses。[work] .city =”... |
由于平面表示结构,Map键必须是简单类型,例如
String
或Number
。
通过在 RedisCustomConversions
中注册相应的 Converter
,可以自定义映射行为。这些转换器可以负责转换单个 byte[]
以及 Map<String,byte[]>
。第一个适用于(例如)将复杂类型转换为(例如)仍使用默认映射哈希结构的二进制JSON表示。第二个选项提供对结果哈希的完全控制。
将对象写入Redis哈希会从哈希中删除内容并重新创建整个哈希,因此未映射的数据将丢失。
以下示例显示了两个示例字节数组转换器:
示例13.示例byte []转换器
@WritingConverter
public class AddressToBytesConverter implements Converter<Address, byte[]> {
private final Jackson2JsonRedisSerializer<Address> serializer;
public AddressToBytesConverter() {
serializer = new Jackson2JsonRedisSerializer<Address>(Address.class);
serializer.setObjectMapper(new ObjectMapper());
}
@Override
public byte[] convert(Address value) {
return serializer.serialize(value);
}
}
@ReadingConverter
public class BytesToAddressConverter implements Converter<byte[], Address> {
private final Jackson2JsonRedisSerializer<Address> serializer;
public BytesToAddressConverter() {
serializer = new Jackson2JsonRedisSerializer<Address>(Address.class);
serializer.setObjectMapper(new ObjectMapper());
}
@Override
public Address convert(byte[] value) {
return serializer.deserialize(value);
}
}
使用前面的字节数组 Converter
产生类似于以下内容的输出:
_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al’thor
address = { city : "emond's field", country : "andor" }
以下示例显示了 Map
转换器的两个示例:
示例14.示例Map <String,byte []> Converters
@WritingConverter
public class AddressToMapConverter implements Converter<Address, Map<String,byte[]>> {
@Override
public Map<String,byte[]> convert(Address source) {
return singletonMap("ciudad", source.getCity().getBytes());
}
}
@ReadingConverter
public class MapToAddressConverter implements Converter<Address, Map<String, byte[]>> {
@Override
public Address convert(Map<String,byte[]> source) {
return new Address(new String(source.get("ciudad")));
}
}
使用前面的Map Converter
产生类似于以下内容的输出:
_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al’thor
ciudad = "emond's field"
自定义转化对索引解析没有影响。即使是自定义转换类型,仍会创建 Secondary Indexes 。
如果要避免将整个Java类名称写为类型信息并且更愿意使用键,则可以对要保留的实体类使用 @TypeAlias
注释。如果您需要更多地自定义映射,请查看 TypeInformationMapper 界面。可以在 DefaultRedisTypeMapper
配置该接口的实例,该实例可以在 MappingRedisConverter
上配置。
以下示例显示如何为实体定义类型别名:
示例15.为实体定义 @TypeAlias
@TypeAlias("pers")
class Person {
}
生成的文档包含 pers
作为 _class
字段中的值。
以下示例演示如何在 MappingRedisConverter
中配置自定义 RedisTypeMapper
:
示例16.通过Spring Java Config配置自定义 RedisTypeMapper
class CustomRedisTypeMapper extends DefaultRedisTypeMapper {
//implement custom type mapping here
}
@Configuration
class SampleRedisConfiguration {
@Bean
public MappingRedisConverter redisConverter(RedisMappingContext mappingContext,
RedisCustomConversions customConversions, ReferenceResolver referenceResolver) {
MappingRedisConverter mappingRedisConverter = new MappingRedisConverter(mappingContext, null, referenceResolver,
customTypeMapper());
mappingRedisConverter.setCustomConversions(customConversions);
return mappingRedisConverter;
}
@Bean
public RedisTypeMapper customTypeMapper() {
return new CustomRedisTypeMapper();
}
}
Keyspaces定义用于为Redis Hash创建实际密钥的前缀。默认情况下,前缀设置为 getClass().getName()
。您可以通过在聚合根级别设置 @RedisHash
来更改此默认值通过设置编程配置。但是,带注释的键空间将取代任何其他配置。
以下示例显示如何使用 @EnableRedisRepositories
注释设置键空间配置:
示例17.通过 @EnableRedisRepositories
设置Keyspace
@Configuration
@EnableRedisRepositories(keyspaceConfiguration = MyKeyspaceConfiguration.class)
public class ApplicationConfig {
//... RedisConnectionFactory and RedisTemplate Bean definitions omitted
public static class MyKeyspaceConfiguration extends KeyspaceConfiguration {
@Override
protected Iterable<KeyspaceSettings> initialConfiguration() {
return Collections.singleton(new KeyspaceSettings(Person.class, "people"));
}
}
}
以下示例显示如何以编程方式设置键空间:
示例18.程序化Keyspace设置
@Configuration
@EnableRedisRepositories
public class ApplicationConfig {
//... RedisConnectionFactory and RedisTemplate Bean definitions omitted
@Bean
public RedisMappingContext keyValueMappingContext() {
return new RedisMappingContext(
new MappingConfiguration(
new MyKeyspaceConfiguration(), new IndexConfiguration()));
}
public static class MyKeyspaceConfiguration extends KeyspaceConfiguration {
@Override
protected Iterable<KeyspaceSettings> initialConfiguration() {
return Collections.singleton(new KeyspaceSettings(Person.class, "people"));
}
}
}
Secondary indexes 用于启用基于本机Redis结构的查找操作。值将在每次保存时写入相应的索引,并在删除对象或 expire 时删除。
给定前面显示的示例 Person
实体,我们可以通过使用 @Indexed
注释属性来为 firstname
创建索引,如以下示例所示:
示例19.注释驱动的索引
@RedisHash("people")
public class Person {
@Id String id;
@Indexed String firstname;
String lastname;
Address address;
}
索引是为实际属性值构建的。保存两个人(例如,“rand”和“aviendha”)会导致设置类似于以下内容的索引:
SADD people:firstname:rand e2c7dcee-b8cd-4424-883e-736ce564363e
SADD people:firstname:aviendha a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56
也可以在嵌套元素上使用索引。假设 Address
有一个 city
属性,用 @Indexed
注释。在这种情况下,一旦 person.address.city
不是 null
,我们就为每个城市设置了集合,如下例所示:
SADD people:address.city:tear e2c7dcee-b8cd-4424-883e-736ce564363e
此外,程序设置允许您在 Map 键和列表属性上定义索引,如以下示例所示:
@RedisHash("people")
public class Person {
// ... other properties omitted
Map<String,String> attributes; (1)
Map<String Person> relatives; (2)
List<Address> addresses; (3)
}
1 | SADD people:attributes.map-key:map-value e2c7dcee-b8cd-4424-883e-736ce564363e |
---|---|
2 | SADD people:relatives.map-key.firstname:tam e2c7dcee-b8cd-4424-883e-736ce564363e |
3 | SADD people:addresses.city:tear e2c7dcee-b8cd-4424-883e-736ce564363e |
无法在 References 上解析索引。
与键空间一样,您可以配置索引而无需注释实际的域类型,如以下示例所示:
示例20.使用@EnableRedisRepositories进行索引设置
@Configuration
@EnableRedisRepositories(indexConfiguration = MyIndexConfiguration.class)
public class ApplicationConfig {
//... RedisConnectionFactory and RedisTemplate Bean definitions omitted
public static class MyIndexConfiguration extends IndexConfiguration {
@Override
protected Iterable<IndexDefinition> initialConfiguration() {
return Collections.singleton(new SimpleIndexDefinition("people", "firstname"));
}
}
}
同样,与键空间一样,您可以以编程方式配置索引,如以下示例所示:
示例21.程序化索引设置
@Configuration
@EnableRedisRepositories
public class ApplicationConfig {
//... RedisConnectionFactory and RedisTemplate Bean definitions omitted
@Bean
public RedisMappingContext keyValueMappingContext() {
return new RedisMappingContext(
new MappingConfiguration(
new KeyspaceConfiguration(), new MyIndexConfiguration()));
}
public static class MyIndexConfiguration extends IndexConfiguration {
@Override
protected Iterable<IndexDefinition> initialConfiguration() {
return Collections.singleton(new SimpleIndexDefinition("people", "firstname"));
}
}
}
假设 Address
类型包含 Point
类型的 location
属性,该属性保存特定地址的地理坐标。通过使用 @GeoIndexed
对属性进行批注,Spring Data Redis使用Redis GEO
命令添加这些值,如以下示例所示:
@RedisHash("people")
public class Person {
Address address;
// ... other properties omitted
}
public class Address {
@GeoIndexed Point location;
// ... other properties omitted
}
public interface PersonRepository extends CrudRepository<Person, String> {
List<Person> findByAddressLocationNear(Point point, Distance distance); (1)
List<Person> findByAddressLocationWithin(Circle circle); (2)
}
Person rand = new Person("rand", "al'thor");
rand.setAddress(new Address(new Point(13.361389D, 38.115556D)));
repository.save(rand); (3)
repository.findByAddressLocationNear(new Point(15D, 37D), new Distance(200)); (4)
1 | 使用 Point 和 Distance 对嵌套属性进行查询方法声明。 |
---|---|
2 | 在嵌套属性上查询方法声明,使用 Circle 进行搜索。 |
3 | GEOADD people:address:location 13.361389 38.115556 e2c7dcee-b8cd-4424-883e-736ce564363e |
4 | GEORADIUS people:address:location 15.0 37.0 200.0 km |
在前面的示例中,使用 GEOADD
存储经度和纬度值,该值使用对象的 id
作为成员的名称。 finder方法允许使用 Circle
或 Point, Distance
组合来查询这些值。
可以将
near
和within
与其他条件结合使用 not 。
本章介绍Query by Example并说明如何使用它。
按示例查询(QBE)是一种用户友好的查询技术,具有简单的界面。它允许动态创建查询,并且不需要您编写包含字段名称的查询。实际上,Query by Example不要求您使用特定于 Store 的查询语言来编写查询。
Query by Example API由三部分组成:
探测器:具有填充字段的域对象的实际示例。
ExampleMatcher
: ExampleMatcher
包含有关如何匹配特定字段的详细信息。它可以在多个示例中重用。
Example
: Example
由探针和 ExampleMatcher
组成。它用于创建查询。
按示例查询非常适合几种用例:
使用一组静态或动态约束查询数据存储。
频繁重构域对象,而不必担心破坏现有查询。
独立于底层数据存储API工作。
按示例查询也有几个限制:
不支持嵌套或分组的属性约束,例如 firstname = ?0 or (firstname = ?1 and lastname = ?2)
。
仅支持字符串的开始/包含/结束/正则表达式匹配以及其他属性类型的精确匹配。
在开始使用Query by Example之前,您需要拥有一个域对象。首先,为存储库创建一个接口,如以下示例所示:
示例22.示例Person对象
public class Person {
@Id
private String id;
private String firstname;
private String lastname;
private Address address;
// … getters and setters omitted
}
前面的示例显示了一个简单的域对象。您可以使用它来创建 Example
。默认情况下,将忽略具有 null
值的字段,并使用特定于 Store 的默认值匹配字符串。可以使用 of
工厂方法或使用 ExampleMatcher 构建示例。 Example
是不可变的。该以下列表显示了一个简单的示例
例23.简单的例子
Person person = new Person(); (1)
person.setFirstname("Dave"); (2)
Example<Person> example = Example.of(person); (3)
1 | 创建域对象的新实例。 |
---|---|
2 | 设置要查询的属性。 |
3 | 创建 Example 。 |
理想情况下,可以使用存储库执行示例。为此,请让您的存储库接口扩展 QueryByExampleExecutor<T>
。以下清单显示了 QueryByExampleExecutor
界面的摘录:
例24. QueryByExampleExecutor
public interface QueryByExampleExecutor<T> {
<S extends T> S findOne(Example<S> example);
<S extends T> Iterable<S> findAll(Example<S> example);
// … more functionality omitted.
}
示例不限于默认设置。您可以使用 ExampleMatcher
为字符串匹配,空值处理和特定于属性的设置指定自己的默认值,如以下示例所示:
示例25.具有定制匹配的示例匹配器
Person person = new Person(); (1)
person.setFirstname("Dave"); (2)
ExampleMatcher matcher = ExampleMatcher.matching() (3)
.withIgnorePaths("lastname") (4)
.withIncludeNullValues() (5)
.withStringMatcherEnding(); (6)
Example<Person> example = Example.of(person, matcher); (7)
1 | 创建域对象的新实例。 |
---|---|
2 | 设置属性。 |
3 | 创建 ExampleMatcher 以期望所有值匹配。即使没有进一步配置,它也可以在这个阶段使用。 |
4 | 构造一个新的 ExampleMatcher 以忽略 lastname 属性路径。 |
5 | 构造一个新的 ExampleMatcher 以忽略 lastname 属性路径并包含空值。 |
6 | 构造一个新的 ExampleMatcher 以忽略 lastname 属性路径,包含空值,并执行后缀字符串匹配。 |
7 | 根据域对象和配置的 ExampleMatcher 创建新的 Example 。 |
默认情况下, ExampleMatcher
期望探针上设置的所有值都匹配。如果要获得与隐式定义的任何谓词匹配的结果,请使用 ExampleMatcher.matchingAny()
。
您可以指定单个属性的行为(例如“firstname”和“lastname”,或者对于嵌套属性,“address.city”)。您可以使用匹配选项和区分大小写来调整它,如以下示例所示:
示例26.配置匹配器选项
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstname", endsWith())
.withMatcher("lastname", startsWith().ignoreCase());
}
配置匹配器选项的另一种方法是使用lambdas(在Java 8中引入)。此方法创建一个回调,要求实现者修改匹配器。您无需返回匹配器,因为配置选项保存在匹配器实例中。以下示例显示了使用lambdas的匹配器:
示例27.使用lambdas配置matcher选项
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstname", match -> match.endsWith())
.withMatcher("firstname", match -> match.startsWith());
}
由 Example
创建的查询使用配置的合并视图。默认匹配设置可以在 ExampleMatcher
级别设置,而个别设置可以应用于特定属性路径。除非明确定义,否则 ExampleMatcher
上设置的设置将由属性路径设置继承。属性修补程序上的设置优先于默认设置。下表描述了各种 ExampleMatcher
设置的范围:
设置 | 范围 |
---|---|
无效处理 | ExampleMatcher |
字符串匹配 | ExampleMatcher 和属性路径 |
忽略属性 | 属性路径 |
区分大小写 | ExampleMatcher 和属性路径 |
值转换 | 属性路径 |
以下示例针对存储库使用Query by Example:
示例28.使用存储库按示例查询
interface PersonRepository extends QueryByExampleExecutor<Person> {
}
class PersonService {
@Autowired PersonRepository personRepository;
List<Person> findPeople(Person probe) {
return personRepository.findAll(Example.of(probe));
}
}
Redis Repositories通过其二级索引支持Spring Data的Query by Example功能的子集。特别是,只使用精确,区分大小写和非空值来构造查询。
辅助索引使用基于集合的操作(Set intersection,Set union)来确定匹配的键。将属性添加到未编制索引的查询不会返回任何结果,因为不存在索引。 “按示例查询”支持检查索引配置,以仅包括索引所涵盖的查询中的属性。这是为了防止意外包含非索引属性。
不区分大小写的查询和不受支持的 StringMatcher
实例在运行时被拒绝。
以下列表显示了受支持的Query by Example选项:
区分大小写和嵌套属性的区分大小写
任意/所有匹配模式
标准值的值转换
从条件中排除 null
值
以下列表显示Query by Example不支持的属性:
不区分大小写的匹配
正则表达式,前缀/包含/后缀字符串匹配
查询关联,集合和类似 Map 的属性
从条件中包含 null
值
findAll
与排序
存储在Redis中的对象可能仅在一定时间内有效。这对于在Redis中持久存在短期对象尤其有用,而无需手动删除它们当他们达到他们的生命结束。以秒为单位的到期时间可以使用 @RedisHash(timeToLive=…)
设置,也可以使用 KeyspaceSettings
设置(请参阅 Keyspaces )。
通过在数字属性或方法上使用 @TimeToLive
注释,可以设置更灵活的到期时间。但是,不要对同一类中的方法和属性应用 @TimeToLive
。以下示例显示属性和方法上的 @TimeToLive
注释:
例29.过期
public class TimeToLiveOnProperty {
@Id
private String id;
@TimeToLive
private Long expiration;
}
public class TimeToLiveOnMethod {
@Id
private String id;
@TimeToLive
public long getTimeToLive() {
return new Random().nextLong();
}
}
使用
@TimeToLive
显式注释属性会从Redis读回实际的TTL
或PTTL
值。 -1表示该对象没有关联的到期日期。
存储库实现确保通过 RedisMessageListenerContainer
订阅 Redis keyspace notifications 。
当到期时间设置为正值时,将执行相应的 EXPIRE
命令。除了保留原始版本之外,还有一个幻像副本在Redis中保留,并在原始版本后五分钟到期。这样做是为了使存储库支持能够发布 RedisKeyExpiredEvent
,并在密钥到期时保持Spring的 ApplicationEventPublisher
中的过期值,即使原始值已被删除。使用Spring Data Redis存储库的所有连接应用程序都会收到到期事件。
默认情况下,在初始化应用程序时禁用密钥到期侦听器。可以在 @EnableRedisRepositories
或 RedisKeyValueAdapter
中调整启动模式以使用应用程序启动侦听器,或者在第一次插入具有TTL的实体时启动侦听器。有关可能的值,请参见 EnableKeyspaceEvents 。
RedisKeyExpiredEvent
包含过期域对象的副本以及密钥。
延迟或禁用到期事件侦听器启动会影响
RedisKeyExpiredEvent
publishing。已禁用的事件侦听器不会发布到期事件。由于延迟的侦听器初始化,延迟启动可能导致事件丢失。
键空间通知消息侦听器在Redis中更改
notify-keyspace-events
设置(如果尚未设置)。不会覆盖现有设置,因此您必须正确设置这些设置(或将其保留为空)。请注意,在AWS ElastiCache上禁用了CONFIG
,并且启用侦听器会导致错误。
Redis发布/订阅消息不是永久性的。如果密钥在应用程序关闭时到期,则不会处理到期事件,这可能导致包含对已过期对象的引用的二级索引。
使用 @Reference
标记属性允许存储简单的键引用,而不是将值复制到哈希本身。从Redis加载时,引用会自动解析并映射回对象,如以下示例所示:
示例30.示例属性参考
_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al’thor
mother = people:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56 (1)
1 | 引用存储引用对象的整个键( keyspace:id )。 |
---|
保存引用对象时,参考对象不会保留。您必须单独保留对引用对象的更改,因为只存储引用。未解析在引用类型的属性上设置的索引。
在某些情况下,您无需加载和重写整个实体,只需在其中设置新值即可。上次活动时间的会话时间戳可能是您想要更改一个属性的场景。 PartialUpdate
允许您在现有对象上定义 set
和 delete
操作,同时负责更新实体本身和索引结构的潜在过期时间。以下示例显示了部分更新:
示例31.部分更新示例
PartialUpdate<Person> update = new PartialUpdate<Person>("e2c7dcee", Person.class)
.set("firstname", "mat") (1)
.set("address.city", "emond's field") (2)
.del("age"); (3)
template.update(update);
update = new PartialUpdate<Person>("e2c7dcee", Person.class)
.set("address", new Address("caemlyn", "andor")) (4)
.set("attributes", singletonMap("eye-color", "grey")); (5)
template.update(update);
update = new PartialUpdate<Person>("e2c7dcee", Person.class)
.refreshTtl(true); (6)
.set("expiration", 1000);
template.update(update);
1 | 将简单的 firstname 属性设置为 mat 。 |
---|---|
2 | 将简单的'address.city'属性设置为'emond's field',而不必传入整个对象。注册自定义转换时,这不起作用。 |
3 | 删除 age 属性。 |
4 | 设置复杂的 address 属性。 |
5 | 设置值映射,删除以前存在的映射并用给定值替换值。 |
6 | 更改 Time To Live 时自动更新服务器到期时间。 |
更新复杂对象以及 Map (或其他集合)结构需要与Redis进一步交互以确定现有值,这意味着重写整个实体可能会更快。
查询方法允许从方法名称自动派生简单的查找程序查询,如以下示例所示:
示例32.示例存储库查找器方法
public interface PersonRepository extends CrudRepository<Person, String> {
List<Person> findByFirstname(String firstname);
}
请确保在finder方法中使用的属性已设置为索引。
Redis存储库的查询方法仅支持对具有分页的实体和实体集合的查询。使用派生查询方法可能并不总是足以模拟要执行的查询。
RedisCallback
提供了对索引结构甚至自定义索引的实际匹配的更多控制。为此,请提供RedisCallback
,它返回单个或Iterable
的id
值集,如以下示例所示:
示例33.使用RedisCallback的样本查找器
String user = //...
List<RedisSession> sessionsByUser = template.find(new RedisCallback<Set<byte[]>>() {
public Set<byte[]> doInRedis(RedisConnection connection) throws DataAccessException {
return connection
.sMembers("sessions:securityContext.authentication.principal.username:" + user);
}}, RedisSession.class);
下表概述了Redis支持的关键字以及包含该关键字的方法实质上转换为:
关键字 | 示例 | Redis代码段 |
---|---|---|
And | findByLastnameAndFirstname | SINTER …:firstname:rand …:lastname:al’thor |
Or | findByLastnameOrFirstname | SUNION …:firstname:rand …:lastname:al’thor |
Is, Equals | findByFirstname , findByFirstnameIs , findByFirstnameEquals | SINTER …:firstname:rand |
IsTrue | FindByAliveIsTrue | SINTER …:alive:1 |
IsFalse | findByAliveIsFalse | SINTER …:alive:0 |
Top,First | findFirst10ByFirstname , findTop5ByFirstname |
您可以在群集Redis环境中使用Redis存储库支持。有关 ConnectionFactory
配置详细信息,请参阅“ Redis Cluster ”部分。但是,必须进行一些其他配置,因为默认密钥分发会将实体和二级索引分散到整个群集及其插槽中。
下表显示了群集上数据的详细信息(基于前面的示例):
键 | 类型 | 插槽 | 节点 |
---|---|---|---|
人:e2c7dcee-b8cd-4424-883e-736ce564363e | id for hash | 15171 | 127.0.0.1:7381 |
人:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56 | id for hash | 7373 | 127.0.0.1:7380 |
人:名字:rand | index | 1700 | 127.0.0.1:7379 |
某些命令(例如 SINTER
和 SUNION
)只能在所有相关键映射到同一插槽时在服务器端进行处理。否则,必须在客户端进行计算。因此,将键空间固定到单个插槽是很有用的,这样可以立即使用Redis服务器端计算。下表显示了执行操作时发生的情况(请注意插槽列中的更改以及节点列中的端口值):
键 | 类型 | 插槽 | 节点 |
---|---|---|---|
:e2c7dcee-b8cd-4424-883e-736ce564363e | id for hash | 2399 | 127.0.0.1:7379 |
:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56 | id for hash | 2399 | 127.0.0.1:7379 |
:名字:rand | index | 2399 | 127.0.0.1:7379 |
使用Redis群集时,使用
@RedisHash("{yourkeyspace}")
定义和固定密钥空间到特定的插槽。
存储库接口的实例通常由容器创建,在使用Spring Data时,Spring是最自然的选择。 Spring为创建bean实例提供了复杂功能。 Spring Data Redis附带了一个自定义CDI扩展,允许您在CDI环境中使用存储库抽象。扩展是JAR的一部分,因此,要激活它,请将Spring Data Redis JAR拖放到类路径中。
然后,您可以通过为 RedisConnectionFactory
和 RedisOperations
实现CDI Producer来设置基础结构,如以下示例所示:
class RedisOperationsProducer {
@Produces
RedisConnectionFactory redisConnectionFactory() {
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(new RedisStandaloneConfiguration());
jedisConnectionFactory.afterPropertiesSet();
return jedisConnectionFactory;
}
void disposeRedisConnectionFactory(@Disposes RedisConnectionFactory redisConnectionFactory) throws Exception {
if (redisConnectionFactory instanceof DisposableBean) {
((DisposableBean) redisConnectionFactory).destroy();
}
}
@Produces
@ApplicationScoped
RedisOperations<byte[], byte[]> redisOperationsProducer(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
template.setConnectionFactory(redisConnectionFactory);
template.afterPropertiesSet();
return template;
}
}
必要的设置可能会有所不同,具体取决于您的JavaEE环境。
Spring Data Redis CDI扩展将所有可用的存储库作为CDI bean选取,并在容器请求存储库类型的bean时为Spring Data存储库创建代理。因此,获取Spring Data存储库的实例是声明 @Injected
属性的问题,如以下示例所示:
class RepositoryClient {
@Inject
PersonRepository repository;
public void businessMethod() {
List<Person> people = repository.findAll();
}
}
Redis存储库需要 RedisKeyValueAdapter
和 RedisKeyValueTemplate
个实例。如果未找到提供的bean,则由Spring Data CDI扩展创建和管理这些bean。但是,您可以提供自己的bean来配置 RedisKeyValueAdapter
和 RedisKeyValueTemplate
的特定属性。
Redis作为 Store 本身提供了一个非常狭窄的低级API,从而将更高级别的功能(例如二级索引和查询操作)留给用户。
本节提供了存储库抽象发出的命令的更详细视图,以便更好地理解潜在的性能影响。
将以下实体类视为所有操作的起点:
例34.示例实体
@RedisHash("people")
public class Person {
@Id String id;
@Indexed String firstname;
String lastname;
Address hometown;
}
public class Address {
@GeoIndexed Point location;
}
repository.save(new Person("rand", "al'thor"));
HMSET "people:19315449-cda2-4f5c-b696-9cb8018fa1f9" "_class" "Person" "id" "19315449-cda2-4f5c-b696-9cb8018fa1f9" "firstname" "rand" "lastname" "al'thor" (1)
SADD "people" "19315449-cda2-4f5c-b696-9cb8018fa1f9" (2)
SADD "people:firstname:rand" "19315449-cda2-4f5c-b696-9cb8018fa1f9" (3)
SADD "people:19315449-cda2-4f5c-b696-9cb8018fa1f9:idx" "people:firstname:rand" (4)
1 | 将展平的条目另存为哈希。 |
---|---|
2 | 将<1>中写入的哈希的键添加到实体的辅助索引中在相同的密钥空间。 |
3 | 将<2>中写入的哈希的键添加到具有属性值的firstnames的二级索引中。 |
4 | 将索引<3>添加到条目的辅助结构集中,以跟踪要在删除/更新时清除的索引。 |
repository.save(new Person("e82908cf-e7d3-47c2-9eec-b4e0967ad0c9", "Dragon Reborn", "al'thor"));
DEL "people:e82908cf-e7d3-47c2-9eec-b4e0967ad0c9" (1)
HMSET "people:e82908cf-e7d3-47c2-9eec-b4e0967ad0c9" "_class" "Person" "id" "e82908cf-e7d3-47c2-9eec-b4e0967ad0c9" "firstname" "Dragon Reborn" "lastname" "al'thor" (2)
SADD "people" "e82908cf-e7d3-47c2-9eec-b4e0967ad0c9" (3)
SMEMBERS "people:e82908cf-e7d3-47c2-9eec-b4e0967ad0c9:idx" (4)
TYPE "people:firstname:rand" (5)
SREM "people:firstname:rand" "e82908cf-e7d3-47c2-9eec-b4e0967ad0c9" (6)
DEL "people:e82908cf-e7d3-47c2-9eec-b4e0967ad0c9:idx" (7)
SADD "people:firstname:Dragon Reborn" "e82908cf-e7d3-47c2-9eec-b4e0967ad0c9" (8)
SADD "people:e82908cf-e7d3-47c2-9eec-b4e0967ad0c9:idx" "people:firstname:Dragon Reborn" (9)
1 | 删除现有哈希以避免可能不再存在哈希键的剩余部分。 |
---|---|
2 | 将展平的条目另存为哈希。 |
3 | 将<1>中写入的散列的键添加到同一键空间中的实体的辅助索引中。 |
4 | 获取可能需要更新的现有索引结构。 |
5 | 检查索引是否存在以及它是什么类型(text,geo,...)。 |
6 | 从索引中删除可能存在的密钥。 |
7 | 删除包含索引信息的帮助程序。 |
8 | 将<2>中添加的哈希的键添加到具有属性值的firstnames的二级索引中。 |
9 | 将索引<6>添加到条目的辅助结构集中,以跟踪要在删除/更新时清除的索引。 |
地理索引遵循与普通文本相同的规则,但使用地理结构来存储值。保存使用地理索引属性的实体会产生以下命令:
GEOADD "people:hometown:location" "13.361389" "38.115556" "76900e94-b057-44bc-abcf-8126d51a621b" (1)
SADD "people:76900e94-b057-44bc-abcf-8126d51a621b:idx" "people:hometown:location" (2)
1 | 将已保存条目的密钥添加到地理索引中。 |
---|---|
2 | 跟踪索引结构。 |
repository.findByFirstname("egwene");
SINTER "people:firstname:egwene" (1)
HGETALL "people:d70091b5-0b9a-4c0a-9551-519e61bc9ef3" (2)
HGETALL ...
1 | 获取辅助索引中包含的密钥。 |
---|---|
2 | 分别获取<1>返回的每个键。 |
repository.findByHometownLocationNear(new Point(15, 37), new Distance(200, KILOMETERS));
GEORADIUS "people:hometown:location" "15.0" "37.0" "200.0" "km" (1)
HGETALL "people:76900e94-b057-44bc-abcf-8126d51a621b" (2)
HGETALL ...
1 | 获取辅助索引中包含的密钥。 |
---|---|
2 | 分别获取<1>返回的每个键。 |
附录包含各种其他细节,以补充参考文档其余部分中的信息:
“ Schema ”定义Spring Data Redis提供的模式。
“ Command Reference ”详细说明 RedisTemplate
支持哪些命令。
Spring Data Redis Schema (redis-namespace)
命令 | 模板支持 |
---|---|
APPEND | X |
AUTH | X |
BGREWRITEAOF | X |
BGSAVE | X |
BITCOUNT | X |
BITFIELD | X |
BITOP | X |
BLPOP | X |
BRPOP | X |
BRPOPLPUSH | X |
客户杀死 | X |
客户端GETNAME | X |
客户列表 | X |
客户端SETNAME | X |
CLUSTER SLOTS | - |
COMMAND | - |
COMMAND COUNT | - |
COMMAND GETKEYS | - |
COMMAND INFO | - |
CONFIG GET | X |
CONFIG RESETSTAT | X |
CONFIG REWRITE | - |
CONFIG SET | X |
DBSIZE | X |
DEBUG OBJECT | - |
DEBUG SEGFAULT | - |
DECR | X |
DECRBY | X |
DEL | X |
DISCARD | X |
DUMP | X |
ECHO | X |
EVAL | X |
EVALSHA | X |
EXEC | X |
EXISTS | X |
EXPIRE | X |
EXPIREAT | X |
FLUSHALL | X |
FLUSHDB | X |
GET | X |
GETBIT | X |
GETRANGE | X |
GETSET | X |
HDEL | X |
HEXISTS | X |
HGET | X |
HGETALL | X |
HINCRBY | X |
HINCRBYFLOAT | X |
HKEYS | X |
HLEN | X |
HMGET | X |
HMSET | X |
HSCAN | X |
HSET | X |
HSETNX | X |
HVALS | X |
INCR | X |
INCRBY | X |
INCRBYFLOAT | X |
INFO | X |
KEYS | X |
LASTSAVE | X |
LINDEX | X |
LINSERT | X |
LLEN | X |
LPOP | X |
LPUSH | X |
LPUSHX | X |
LRANGE | X |
LREM | X |
LSET | X |
LTRIM | X |
MGET | X |
MIGRATE | - |
MONITOR | - |
MOVE | X |
MSET | X |
MSETNX | X |
MULTI | X |
OBJECT | - |
PERSIST | X |
PEXIPRE | X |
PEXPIREAT | X |
PFADD | X |
PFCOUNT | X |
PFMERGE | X |
PING | X |
PSETEX | X |
PSUBSCRIBE | X |
PTTL | X |
PUBLISH | X |
PUBSUB | - |
PUBSUBSCRIBE | - |
QUIT | X |
RANDOMKEY | X |
重新命名 | X |
RENAMENX | X |
RESTORE | X |
角色 | - |
RPOP | X |
RPOPLPUSH | X |
RPUSH | X |
RPUSHX | X |
SADD | X |
SAVE | X |
SCAN | X |
SCARD | X |
SCRIPT EXITS | X |
SCRIPT FLUSH | X |
SCRIPT KILL | X |
SCRIPT LOAD | X |
SDIFF | X |
SDIFFSTORE | X |
SELECT | X |
SENTINEL FAILOVER | X |
SENTINEL GET-MASTER-ADD-BY-NAME | - |
SENTINEL MASTER | - |
SENTINEL MASTERS | X |
SENTINEL MONITOR | X |
SENTINEL REMOVE | X |
SENTINEL RESET | - |
SENTINEL SET | - |
SENTINEL SLAVES | X |
SET | X |
SETBIT | X |
SETEX | X |
SETNX | X |
SETRANGE | X |
SHUTDOWN | X |
SINTER | X |
SINTERSTORE | X |
SISMEMBER | X |
SLAVEOF | X |
SLOWLOG | - |
SMEMBERS | X |
SMOVE | X |
SORT | X |
SPOP | X |
SRANDMEMBER | X |
SREM | X |
SSCAN | X |
STRLEN | X |
SUBSCRIBE | X |
SUNION | X |
SUNIONSTORE | X |
SYNC | - |
TIME | X |
TTL | X |
TYPE | X |
UNSUBSCRIBE | X |
UNWATCH | X |
WATCH | X |
ZADD | X |
ZCARD | X |
ZCOUNT | X |
ZINCRBY | X |
ZINTERSTORE | X |
ZLEXCOUNT | - |
ZRANGE | X |
ZRANGEBYLEX | - |
ZREVRANGEBYLEX | - |
ZRANGEBYSCORE | X |
ZRANK | X |
ZREM | X |
ZREMRANGEBYLEX | - |
ZREMRANGEBYRANK | X |
ZREVRANGE | X |
ZREVRANGEBYSCORE | X |
ZREVRANK | X |
ZSCAN | X |
ZSCORE | X |
ZUNINONSTORE | X |