5
.gitignore
vendored
5
.gitignore
vendored
@@ -16,3 +16,8 @@ out
|
||||
work
|
||||
*.rdb
|
||||
*.aof
|
||||
build/
|
||||
node_modules
|
||||
node
|
||||
package.json
|
||||
package-lock.json
|
||||
|
||||
@@ -97,10 +97,10 @@ If you'd rather like the latest snapshots of the upcoming major version, use our
|
||||
Having trouble with Spring Data? We’d love to help!
|
||||
|
||||
* Check the
|
||||
https://docs.spring.io/spring-data/redis/docs/current/reference/html/[reference documentation], and https://docs.spring.io/spring-data/redis/docs/current/api/[Javadocs].
|
||||
https://docs.spring.io/spring-data/redis/docs/reference/[reference documentation], and https://docs.spring.io/spring-data/redis/docs/current/api/[Javadocs].
|
||||
* Learn the Spring basics – Spring Data builds on Spring Framework, check the https://spring.io[spring.io] web-site for a wealth of reference documentation.
|
||||
If you are just starting out with Spring, try one of the https://spring.io/guides[guides].
|
||||
* If you are upgrading, check out the https://docs.spring.io/spring-data/redis/docs/current/changelog.txt[changelog] for "`new and noteworthy`" features.
|
||||
* If you are upgrading, check out the https://github.com/spring-projects/spring-data-commons/wiki#release-notes[Release notes] for "`new and noteworthy`" features.
|
||||
* Ask a question - we monitor https://stackoverflow.com[stackoverflow.com] for questions tagged with https://stackoverflow.com/tags/spring-data[`spring-data-redis`].
|
||||
You can also chat with the community on https://gitter.im/spring-projects/spring-data[Gitter].
|
||||
* Report bugs with Spring Data Redis at https://github.com/spring-projects/spring-data-redis/issues/new[github.com/spring-projects/spring-data-redis].
|
||||
@@ -136,10 +136,10 @@ Building the documentation builds also the project without running tests.
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
$ ./mvnw clean install -Pdistribute
|
||||
$ ./mvnw clean install -Pantora
|
||||
----
|
||||
|
||||
The generated documentation is available from `target/site/reference/html/index.html`.
|
||||
The generated documentation is available from `target/antora/site/index.html`.
|
||||
|
||||
== Guides
|
||||
|
||||
|
||||
23
pom.xml
23
pom.xml
@@ -19,6 +19,7 @@
|
||||
|
||||
<properties>
|
||||
<springdata.keyvalue>3.2.0-SNAPSHOT</springdata.keyvalue>
|
||||
<springdata.commons>3.2.0-SNAPSHOT</springdata.commons>
|
||||
<awaitility>4.0.2</awaitility>
|
||||
<beanutils>1.9.4</beanutils>
|
||||
<xstream>1.4.20</xstream>
|
||||
@@ -374,6 +375,28 @@
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>antora-process-resources</id>
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/antora/resources/antora-resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>antora</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>io.spring.maven.antora</groupId>
|
||||
<artifactId>antora-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<repositories>
|
||||
|
||||
42
src/main/antora/antora-playbook.yml
Normal file
42
src/main/antora/antora-playbook.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
# PACKAGES antora@3.2.0-alpha.2 @antora/atlas-extension:1.0.0-alpha.1 @antora/collector-extension@1.0.0-alpha.3 @springio/antora-extensions@1.1.0-alpha.2 @asciidoctor/tabs@1.0.0-alpha.12 @opendevise/antora-release-line-extension@1.0.0-alpha.2
|
||||
#
|
||||
# The purpose of this Antora playbook is to build the docs in the current branch.
|
||||
antora:
|
||||
extensions:
|
||||
- '@antora/collector-extension'
|
||||
- require: '@springio/antora-extensions/root-component-extension'
|
||||
root_component_name: 'data-redis'
|
||||
site:
|
||||
title: Spring Data Redis
|
||||
url: https://docs.spring.io/spring-data-redis/reference/
|
||||
content:
|
||||
sources:
|
||||
- url: ./../../..
|
||||
branches: HEAD
|
||||
start_path: src/main/antora
|
||||
worktrees: true
|
||||
- url: https://github.com/spring-projects/spring-data-commons
|
||||
# Refname matching:
|
||||
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
|
||||
branches: [ main, 3.2.x ]
|
||||
start_path: src/main/antora
|
||||
asciidoc:
|
||||
attributes:
|
||||
page-pagination: ''
|
||||
hide-uri-scheme: '@'
|
||||
tabs-sync-option: '@'
|
||||
chomp: 'all'
|
||||
extensions:
|
||||
- '@asciidoctor/tabs'
|
||||
- '@springio/asciidoctor-extensions'
|
||||
sourcemap: true
|
||||
urls:
|
||||
latest_version_segment: ''
|
||||
runtime:
|
||||
log:
|
||||
failure_level: warn
|
||||
format: pretty
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.3.5/ui-bundle.zip
|
||||
snapshot: true
|
||||
12
src/main/antora/antora.yml
Normal file
12
src/main/antora/antora.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
name: data-redis
|
||||
version: true
|
||||
title: Spring Data Redis
|
||||
nav:
|
||||
- modules/ROOT/nav.adoc
|
||||
ext:
|
||||
collector:
|
||||
- run:
|
||||
command: ./mvnw validate process-resources -Pantora-process-resources
|
||||
local: true
|
||||
scan:
|
||||
dir: target/classes/
|
||||
1
src/main/antora/modules/ROOT/examples/examples
Symbolic link
1
src/main/antora/modules/ROOT/examples/examples
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../test/java/org/springframework/data/redis/examples
|
||||
47
src/main/antora/modules/ROOT/nav.adoc
Normal file
47
src/main/antora/modules/ROOT/nav.adoc
Normal file
@@ -0,0 +1,47 @@
|
||||
* xref:index.adoc[Overview]
|
||||
** xref:commons/upgrade.adoc[]
|
||||
** xref:upgrading.adoc[]
|
||||
|
||||
|
||||
* xref:redis.adoc[]
|
||||
** xref:redis/getting-started.adoc[]
|
||||
** xref:redis/drivers.adoc[]
|
||||
** xref:redis/connection-modes.adoc[]
|
||||
** xref:redis/template.adoc[RedisTemplate]
|
||||
** xref:redis/redis-cache.adoc[]
|
||||
** xref:redis/cluster.adoc[]
|
||||
** xref:redis/hash-mappers.adoc[]
|
||||
** xref:redis/pubsub.adoc[]
|
||||
** xref:redis/redis-streams.adoc[]
|
||||
** xref:redis/scripting.adoc[]
|
||||
** xref:redis/transactions.adoc[]
|
||||
** xref:redis/pipelining.adoc[]
|
||||
** xref:redis/support-classes.adoc[]
|
||||
|
||||
* xref:repositories.adoc[]
|
||||
** xref:repositories/core-concepts.adoc[]
|
||||
** xref:repositories/definition.adoc[]
|
||||
** xref:repositories/create-instances.adoc[]
|
||||
** xref:redis/redis-repositories/usage.adoc[]
|
||||
** xref:repositories/object-mapping.adoc[]
|
||||
** xref:redis/redis-repositories/mapping.adoc[]
|
||||
** xref:redis/redis-repositories/keyspaces.adoc[]
|
||||
** xref:redis/redis-repositories/indexes.adoc[]
|
||||
** xref:redis/redis-repositories/expirations.adoc[]
|
||||
** xref:redis/redis-repositories/queries.adoc[]
|
||||
** xref:redis/redis-repositories/query-by-example.adoc[]
|
||||
** xref:redis/redis-repositories/cluster.adoc[]
|
||||
** xref:redis/redis-repositories/anatomy.adoc[]
|
||||
** xref:repositories/projections.adoc[]
|
||||
** xref:repositories/custom-implementations.adoc[]
|
||||
** xref:repositories/core-domain-events.adoc[]
|
||||
** xref:repositories/null-handling.adoc[]
|
||||
** xref:redis/redis-repositories/cdi-integration.adoc[]
|
||||
** xref:repositories/query-keywords-reference.adoc[]
|
||||
** xref:repositories/query-return-types-reference.adoc[]
|
||||
|
||||
* xref:observability.adoc[]
|
||||
|
||||
* xref:appendix.adoc[]
|
||||
|
||||
* https://github.com/spring-projects/spring-data-commons/wiki[Wiki]
|
||||
@@ -1,7 +1,12 @@
|
||||
[[appendix:command-reference]]
|
||||
[appendix]
|
||||
= Command Reference
|
||||
[[appendix]]
|
||||
= Appendix
|
||||
|
||||
[[schema]]
|
||||
== Schema
|
||||
|
||||
link:https://www.springframework.org/schema/redis/spring-redis-1.0.xsd[Spring Data Redis Schema (redis-namespace)]
|
||||
|
||||
[[supported-commands]]
|
||||
== Supported Commands
|
||||
.Redis commands supported by `RedisTemplate`
|
||||
[width="50%",cols="<2,^1",options="header"]
|
||||
@@ -1,8 +1,6 @@
|
||||
include::../{spring-data-commons-docs}/upgrade.adoc[]
|
||||
|
||||
== What to Read Next
|
||||
include::{commons}@data-commons::page$upgrade.adoc[]
|
||||
|
||||
Once you’ve decided to upgrade your application, you can find detailed information regarding specific features in the rest of the document.
|
||||
You can find <<redis.upgrading,migration guides>> specific to major version migrations at the end of this document.
|
||||
You can find xref:upgrading.adoc#redis.upgrading[migration guides] specific to major version migrations at the end of this document.
|
||||
|
||||
Spring Data's documentation is specific to that version, so any information that you find in here will contain the most up-to-date changes that are in that version.
|
||||
20
src/main/antora/modules/ROOT/pages/index.adoc
Normal file
20
src/main/antora/modules/ROOT/pages/index.adoc
Normal file
@@ -0,0 +1,20 @@
|
||||
[[spring-data-redis-reference-documentation]]
|
||||
= Spring Data Redis
|
||||
:revnumber: {version}
|
||||
:revdate: {localdate}
|
||||
:feature-scroll: true
|
||||
|
||||
_Spring Data Redis provides Redis connectivity and repository support for the Redis database.
|
||||
It eases development of applications with a consistent programming model that need to access Redis data sources._
|
||||
|
||||
[horizontal]
|
||||
xref:redis.adoc[Redis] :: Redis support and connectivity
|
||||
xref:repositories.adoc[Repositories] :: Redis Repositories
|
||||
xref:observability.adoc[Observability] :: Observability Integration
|
||||
https://github.com/spring-projects/spring-data-commons/wiki[Wiki] :: What's New, Upgrade Notes, Supported Versions, additional cross-version information.
|
||||
|
||||
Costin Leau, Jennifer Hickey, Christoph Strobl, Thomas Darimont, Mark Paluch, Jay Bryant
|
||||
|
||||
(C) 2008-2023 VMware, Inc.
|
||||
|
||||
Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
|
||||
107
src/main/antora/modules/ROOT/pages/observability.adoc
Normal file
107
src/main/antora/modules/ROOT/pages/observability.adoc
Normal file
@@ -0,0 +1,107 @@
|
||||
[[redis.observability]]
|
||||
= Observability
|
||||
|
||||
Getting insights from an application component about its operations, timing and relation to application code is crucial to understand latency.
|
||||
Spring Data Redis ships with a Micrometer integration through the Lettuce driver to collect observations during Redis interaction.
|
||||
Once the integration is set up, Micrometer will create meters and spans (for distributed tracing) for each Redis command.
|
||||
|
||||
To enable the integration, apply the following configuration to `LettuceClientConfiguration`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
class ObservabilityConfiguration {
|
||||
|
||||
@Bean
|
||||
public ClientResources clientResources(ObservationRegistry observationRegistry) {
|
||||
|
||||
return ClientResources.builder()
|
||||
.tracing(new MicrometerTracingAdapter(observationRegistry, "my-redis-cache"))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LettuceConnectionFactory lettuceConnectionFactory(ClientResources clientResources) {
|
||||
|
||||
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
|
||||
.clientResources(clientResources).build();
|
||||
RedisConfiguration redisConfiguration = …;
|
||||
return new LettuceConnectionFactory(redisConfiguration, clientConfig);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
See also https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/database/#redis[OpenTelemetry Semantic Conventions] for further reference.
|
||||
|
||||
[[observability-metrics]]
|
||||
== Observability - Metrics
|
||||
|
||||
Below you can find a list of all metrics declared by this project.
|
||||
|
||||
[[observability-metrics-redis-command-observation]]
|
||||
== Redis Command Observation
|
||||
|
||||
____
|
||||
Timer created around a Redis command execution.
|
||||
____
|
||||
|
||||
**Metric name** `spring.data.redis`. **Type** `timer` and **base unit** `seconds`.
|
||||
|
||||
Fully qualified name of the enclosing class `org.springframework.data.redis.connection.lettuce.observability.RedisObservation`.
|
||||
|
||||
|
||||
|
||||
.Low cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`db.operation`|Redis command value.
|
||||
|`db.redis.database_index`|Redis database index.
|
||||
|`db.system`|Database system.
|
||||
|`db.user`|Redis user.
|
||||
|`net.peer.name`|Name of the database host.
|
||||
|`net.peer.port`|Logical remote port number.
|
||||
|`net.sock.peer.addr`|Mongo peer address.
|
||||
|`net.sock.peer.port`|Mongo peer port.
|
||||
|`net.transport`|Network transport.
|
||||
|===
|
||||
|
||||
.High cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`db.statement`|Redis statement.
|
||||
|`spring.data.redis.command.error`|Redis error response.
|
||||
|===
|
||||
|
||||
[[observability-spans]]
|
||||
== Observability - Spans
|
||||
|
||||
Below you can find a list of all spans declared by this project.
|
||||
|
||||
[[observability-spans-redis-command-observation]]
|
||||
== Redis Command Observation Span
|
||||
|
||||
> Timer created around a Redis command execution.
|
||||
|
||||
**Span name** `spring.data.redis`.
|
||||
|
||||
Fully qualified name of the enclosing class `org.springframework.data.redis.connection.lettuce.observability.RedisObservation`.
|
||||
|
||||
|
||||
|
||||
.Tag Keys
|
||||
|===
|
||||
|Name | Description
|
||||
|`db.operation`|Redis command value.
|
||||
|`db.redis.database_index`|Redis database index.
|
||||
|`db.statement`|Redis statement.
|
||||
|`db.system`|Database system.
|
||||
|`db.user`|Redis user.
|
||||
|`net.peer.name`|Name of the database host.
|
||||
|`net.peer.port`|Logical remote port number.
|
||||
|`net.sock.peer.addr`|Mongo peer address.
|
||||
|`net.sock.peer.port`|Mongo peer port.
|
||||
|`net.transport`|Network transport.
|
||||
|`spring.data.redis.command.error`|Redis error response.
|
||||
|===
|
||||
@@ -11,14 +11,14 @@ This section provides an easy-to-follow guide for getting started with the Sprin
|
||||
== Learning Spring
|
||||
|
||||
Spring Data uses Spring framework's
|
||||
{spring-framework-reference}/core.html[core] functionality, including:
|
||||
{spring-framework-docs}/core.html[core] functionality, including:
|
||||
|
||||
|
||||
* {spring-framework-reference}/core.html#beans[IoC] container
|
||||
* {spring-framework-reference}/core.html#validation[type conversion system]
|
||||
* {spring-framework-reference}/core.html#expressions[expression language]
|
||||
* {spring-framework-reference}/integration.html#jmx[JMX integration]
|
||||
* {spring-framework-reference}/data-access.html#dao-exceptions[DAO exception hierarchy].
|
||||
* {spring-framework-docs}/core.html#beans[IoC] container
|
||||
* {spring-framework-docs}/core.html#validation[type conversion system]
|
||||
* {spring-framework-docs}/core.html#expressions[expression language]
|
||||
* {spring-framework-docs}/integration.html#jmx[JMX integration]
|
||||
* {spring-framework-docs}/data-access.html#dao-exceptions[DAO exception hierarchy].
|
||||
|
||||
While you need not know the Spring APIs, understanding the concepts behind them is important.
|
||||
At a minimum, the idea behind Inversion of Control (IoC) should be familiar, and you should be familiar with whatever IoC container you choose to use.
|
||||
43
src/main/antora/modules/ROOT/pages/redis.adoc
Normal file
43
src/main/antora/modules/ROOT/pages/redis.adoc
Normal file
@@ -0,0 +1,43 @@
|
||||
[[redis]]
|
||||
= Redis
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
One of the key-value stores supported by Spring Data is https://redis.io[Redis].
|
||||
To quote the Redis project home page:
|
||||
|
||||
[quote]
|
||||
Redis is an advanced key-value store.
|
||||
It is similar to memcached but the dataset is not volatile, and values can be strings, exactly like in memcached, but also lists, sets, and ordered sets.
|
||||
All this data types can be manipulated with atomic operations to push/pop elements, add/remove elements, perform server side union, intersection, difference between sets, and so forth.
|
||||
Redis supports different kind of sorting abilities.
|
||||
|
||||
Spring Data Redis 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.
|
||||
|
||||
Spring Data support for Redis contains a wide range of features:
|
||||
|
||||
* xref:redis/template.adoc[`RedisTemplate` and `ReactiveRedisTemplate` helper class] that increases productivity when performing common Redis operations.
|
||||
Includes integrated serialization between objects and values.
|
||||
* Exception translation into Spring's portable Data Access Exception hierarchy.
|
||||
* Automatic implementation of xref:repositories.adoc[Repository interfaces], including support for custom query methods.
|
||||
* Feature-rich xref:redis/redis-repositories/mapping.adoc[Object Mapping] integrated with Spring's Conversion Service.
|
||||
* Annotation-based mapping metadata that is extensible to support other metadata formats.
|
||||
* xref:redis/transactions.adoc[Transactions] and xref:redis/pipelining.adoc[Pipelining].
|
||||
* xref:redis/redis-cache.adoc[Redis Cache] integration through Spring's Cache abstraction.
|
||||
* xref:redis/pubsub.adoc[Redis Pub/Sub Messaging] and xref:redis/redis-streams.adoc[Redis Stream] Listeners.
|
||||
* xref:redis/support-classes.adoc[Redis Collection Implementations] for Java such as `RedisList` or `RedisSet`.
|
||||
|
||||
== Why Spring Data Redis?
|
||||
|
||||
The Spring Framework is the leading full-stack Java/JEE application framework.
|
||||
It provides a lightweight container and a non-invasive programming model enabled by the use of dependency injection, AOP, and portable service abstractions.
|
||||
|
||||
https://en.wikipedia.org/wiki/NoSQL[NoSQL] storage systems provide an alternative to classical RDBMS for horizontal scalability and speed.
|
||||
In terms of implementation, key-value stores represent one of the largest (and oldest) members in the NoSQL space.
|
||||
|
||||
The Spring Data Redis (SDR) framework makes it easy to write Spring applications that use the Redis key-value store by eliminating the redundant tasks and boilerplate code required for interacting with the store through Spring's excellent infrastructure support.
|
||||
|
||||
[[redis:architecture]]
|
||||
== Redis Support High-level View
|
||||
|
||||
The Redis support provides several components.For most tasks, the high-level abstractions and support services are the best choice.Note that, at any point, you can move between layers.For example, you can get a low-level connection (or even the native library) to communicate directly with Redis.
|
||||
131
src/main/antora/modules/ROOT/pages/redis/cluster.adoc
Normal file
131
src/main/antora/modules/ROOT/pages/redis/cluster.adoc
Normal file
@@ -0,0 +1,131 @@
|
||||
[[cluster]]
|
||||
= Redis Cluster
|
||||
|
||||
Working with https://redis.io/topics/cluster-spec[Redis Cluster] requires Redis Server version 3.0+.
|
||||
See the https://redis.io/topics/cluster-tutorial[Cluster Tutorial] for more information.
|
||||
|
||||
NOTE: When using xref:repositories.adoc[Redis Repositories] with Redis Cluster, make yourself familiar with how to xref:redis/redis-repositories/cluster.adoc[run Redis Repositories on a Cluster].
|
||||
|
||||
[[cluster.working.with.cluster]]
|
||||
== Working With Redis Cluster Connection
|
||||
|
||||
Redis Cluster behaves differently from single-node Redis or even a Sentinel-monitored master-replica environment.
|
||||
This is because the automatic sharding maps a key to one of `16384` slots, which are distributed across the nodes.
|
||||
Therefore, commands that involve more than one key must assert all keys map to the exact same slot to avoid cross-slot errors.
|
||||
A single cluster node serves only a dedicated set of keys.
|
||||
Commands issued against one particular server return results only for those keys served by that server.
|
||||
As a simple example, consider the `KEYS` command.
|
||||
When issued to a server in a cluster environment, it returns only the keys served by the node the request is sent to and not necessarily all keys within the cluster.
|
||||
So, to get all keys in a cluster environment, you must read the keys from all the known master nodes.
|
||||
|
||||
While redirects for specific keys to the corresponding slot-serving node are handled by the driver libraries, higher-level functions, such as collecting information across nodes or sending commands to all nodes in the cluster, are covered by `RedisClusterConnection`.
|
||||
Picking up the keys example from earlier, this means that the `keys(pattern)` method picks up every master node in the cluster and simultaneously runs the `KEYS` command on every master node while picking up the results and returning the cumulated set of keys.
|
||||
To just request the keys of a single node `RedisClusterConnection` provides overloads for those methods (for example, `keys(node, pattern)`).
|
||||
|
||||
A `RedisClusterNode` can be obtained from `RedisClusterConnection.clusterGetNodes` or it can be constructed by using either the host and the port or the node Id.
|
||||
|
||||
The following example shows a set of commands being run across the cluster:
|
||||
|
||||
.Sample of Running Commands Across the Cluster
|
||||
====
|
||||
[source,text]
|
||||
----
|
||||
redis-cli@127.0.0.1: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>
|
||||
----
|
||||
|
||||
[source,java]
|
||||
----
|
||||
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> Master node serving slots 0 to 5460 replicated to replica at 7382
|
||||
<2> Master node serving slots 5461 to 10922
|
||||
<3> Master node serving slots 10923 to 16383
|
||||
<4> Replica node holding replicants of the master at 7379
|
||||
<5> Request routed to node at 7381 serving slot 12182
|
||||
<6> Request routed to node at 7379 serving slot 5061
|
||||
<7> Request routed to nodes at 7379, 7380, 7381 -> [thing1, thing2]
|
||||
<8> Request routed to node at 7379 -> [thing2]
|
||||
<9> Request routed to node at 7380 -> []
|
||||
<10> Request routed to node at 7381 -> [thing1]
|
||||
<11> Request routed to node at 7382 -> [thing2]
|
||||
====
|
||||
|
||||
When all keys map to the same slot, the native driver library automatically serves cross-slot requests, such as `MGET`.
|
||||
However, once this is not the case, `RedisClusterConnection` runs multiple parallel `GET` commands against the slot-serving nodes and again returns an accumulated result.
|
||||
This is less performant than the single-slot approach and, therefore, should be used with care.
|
||||
If in doubt, consider pinning keys to the same slot by providing a prefix in curly brackets, such as `\{my-prefix}.thing1` and `\{my-prefix}.thing2`, which will both map to the same slot number.
|
||||
The following example shows cross-slot request handling:
|
||||
|
||||
.Sample of Cross-Slot Request Handling
|
||||
====
|
||||
[source,text]
|
||||
----
|
||||
redis-cli@127.0.0.1:7379 > cluster nodes
|
||||
|
||||
6b38bb... 127.0.0.1:7379 master - 0 0 25 connected 0-5460 <1>
|
||||
7bb...
|
||||
----
|
||||
|
||||
[source,java]
|
||||
----
|
||||
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> Same Configuration as in the sample before.
|
||||
<2> Keys map to same slot -> 127.0.0.1:7381 MGET thing1 \{thing1}.thing2
|
||||
<3> Keys map to different slots and get split up into single slot ones routed to the according nodes +
|
||||
-> 127.0.0.1:7379 GET thing2 +
|
||||
-> 127.0.0.1:7381 GET thing1
|
||||
====
|
||||
|
||||
TIP: The preceding examples demonstrate the general strategy followed by Spring Data Redis.
|
||||
Be aware that some operations might require loading huge amounts of data into memory to compute the desired command.
|
||||
Additionally, not all cross-slot requests can safely be ported to multiple single slot requests and error if misused (for example, `PFCOUNT`).
|
||||
|
||||
[[cluster.redistemplate]]
|
||||
== Working with `RedisTemplate` and `ClusterOperations`
|
||||
|
||||
See the xref:redis/template.adoc[Working with Objects through RedisTemplate] section for information about the general purpose, configuration, and usage of `RedisTemplate`.
|
||||
|
||||
CAUTION: Be careful when setting up `RedisTemplate#keySerializer` using any of the JSON `RedisSerializers`, as changing JSON structure has immediate influence on hash slot calculation.
|
||||
|
||||
`RedisTemplate` provides access to cluster-specific operations through the `ClusterOperations` interface, which can be obtained from `RedisTemplate.opsForCluster()`.
|
||||
This lets you explicitly run commands on a single node within the cluster while retaining the serialization and deserialization features configured for the template.
|
||||
It also provides administrative commands (such as `CLUSTER MEET`) or more high-level operations (for example, resharding).
|
||||
|
||||
The following example shows how to access `RedisClusterConnection` with `RedisTemplate`:
|
||||
|
||||
.Accessing `RedisClusterConnection` with `RedisTemplate`
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ClusterOperations clusterOps = redisTemplate.opsForCluster();
|
||||
clusterOps.shutdown(NODE_7379); <1>
|
||||
----
|
||||
|
||||
<1> Shut down node at 7379 and cross fingers there is a replica in place that can take over.
|
||||
====
|
||||
171
src/main/antora/modules/ROOT/pages/redis/connection-modes.adoc
Normal file
171
src/main/antora/modules/ROOT/pages/redis/connection-modes.adoc
Normal file
@@ -0,0 +1,171 @@
|
||||
[[configuration]]
|
||||
= Connection Modes
|
||||
|
||||
Redis can be operated in various setups.
|
||||
Each mode of operation requires specific configuration that is explained in the following sections.
|
||||
|
||||
[[redis:standalone]]
|
||||
== Redis Standalone
|
||||
|
||||
The easiest way to get started is by using Redis Standalone with a single Redis server,
|
||||
|
||||
Configure `LettuceClientConfiguration` or `JedisConnectionFactory`, as shown in the following example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
class RedisStandaloneConfiguration {
|
||||
|
||||
/**
|
||||
* Lettuce
|
||||
*/
|
||||
@Bean
|
||||
public RedisConnectionFactory lettuceConnectionFactory() {
|
||||
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("server", 6379));
|
||||
}
|
||||
|
||||
/**
|
||||
* Jedis
|
||||
*/
|
||||
@Bean
|
||||
public RedisConnectionFactory jedisConnectionFactory() {
|
||||
return new JedisConnectionFactory(new RedisStandaloneConfiguration("server", 6379));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[redis:write-to-master-read-from-replica]]
|
||||
== Write to Master, Read from Replica
|
||||
|
||||
The Redis Master/Replica setup -- without automatic failover (for automatic failover see: <<redis:sentinel, Sentinel>>) -- not only allows data to be safely stored at more nodes.
|
||||
It also allows, by using xref:redis/drivers.adoc#redis:connectors:lettuce[Lettuce], reading data from replicas while pushing writes to the master.
|
||||
You can set the read/write strategy to be used by using `LettuceClientConfiguration`, as shown in the following example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
class WriteToMasterReadFromReplicaConfiguration {
|
||||
|
||||
@Bean
|
||||
public LettuceConnectionFactory redisConnectionFactory() {
|
||||
|
||||
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
|
||||
.readFrom(REPLICA_PREFERRED)
|
||||
.build();
|
||||
|
||||
RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration("server", 6379);
|
||||
|
||||
return new LettuceConnectionFactory(serverConfig, clientConfig);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
TIP: For environments reporting non-public addresses through the `INFO` command (for example, when using AWS), use `RedisStaticMasterReplicaConfiguration` instead of `RedisStandaloneConfiguration`. Please note that `RedisStaticMasterReplicaConfiguration` does not support Pub/Sub because of missing Pub/Sub message propagation across individual servers.
|
||||
|
||||
[[redis:sentinel]]
|
||||
== Redis Sentinel
|
||||
|
||||
For dealing with high-availability Redis, Spring Data Redis has support for https://redis.io/topics/sentinel[Redis Sentinel], using `RedisSentinelConfiguration`, as shown in the following example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
----
|
||||
|
||||
[TIP]
|
||||
====
|
||||
`RedisSentinelConfiguration` can also be defined with a `PropertySource`, which lets you set the following properties:
|
||||
|
||||
.Configuration Properties
|
||||
* `spring.redis.sentinel.master`: name of the master node.
|
||||
* `spring.redis.sentinel.nodes`: Comma delimited list of host:port pairs.
|
||||
* `spring.redis.sentinel.username`: The username to apply when authenticating with Redis Sentinel (requires Redis 6)
|
||||
* `spring.redis.sentinel.password`: The password to apply when authenticating with Redis Sentinel
|
||||
====
|
||||
|
||||
Sometimes, direct interaction with one of the Sentinels is required. Using `RedisConnectionFactory.getSentinelConnection()` or `RedisConnection.getSentinelCommands()` gives you access to the first active Sentinel configured.
|
||||
|
||||
[[cluster.enable]]
|
||||
== Redis Cluster
|
||||
|
||||
xref:redis/cluster.adoc[Cluster support] is based on the same building blocks as non-clustered communication. `RedisClusterConnection`, an extension to `RedisConnection`, handles the communication with the Redis Cluster and translates errors into the Spring DAO exception hierarchy.
|
||||
`RedisClusterConnection` instances are created with the `RedisConnectionFactory`, which has to be set up with the associated `RedisClusterConfiguration`, as shown in the following example:
|
||||
|
||||
.Sample RedisConnectionFactory Configuration for Redis Cluster
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@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 LettuceConnectionFactory(
|
||||
new RedisClusterConfiguration(clusterProperties.getNodes()));
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[TIP]
|
||||
====
|
||||
`RedisClusterConfiguration` can also be defined through `PropertySource` and has the following properties:
|
||||
|
||||
.Configuration Properties
|
||||
- `spring.redis.cluster.nodes`: Comma-delimited list of host:port pairs.
|
||||
- `spring.redis.cluster.max-redirects`: Number of allowed cluster redirections.
|
||||
====
|
||||
|
||||
NOTE: The initial configuration points driver libraries to an initial set of cluster nodes. Changes resulting from live cluster reconfiguration are kept only in the native driver and are not written back to the configuration.
|
||||
227
src/main/antora/modules/ROOT/pages/redis/drivers.adoc
Normal file
227
src/main/antora/modules/ROOT/pages/redis/drivers.adoc
Normal file
@@ -0,0 +1,227 @@
|
||||
[[redis:connectors]]
|
||||
= Drivers
|
||||
|
||||
One of the first tasks when using Redis and Spring is to connect to the store through the IoC container.
|
||||
To do that, a Java connector (or binding) is required.
|
||||
No matter the library you choose, you need to use only one set of Spring Data Redis APIs (which behaves consistently across all connectors).
|
||||
The `org.springframework.data.redis.connection` package and its `RedisConnection` and `RedisConnectionFactory` interfaces for working with and retrieving active connections to Redis.
|
||||
|
||||
[[redis:connectors:connection]]
|
||||
== RedisConnection and RedisConnectionFactory
|
||||
|
||||
`RedisConnection` provides the core building block for Redis communication, as it handles the communication with the Redis backend.
|
||||
It also automatically translates underlying connecting library exceptions to Spring's consistent {spring-framework-docs}/data-access.html#dao-exceptions[DAO exception hierarchy] so that you can switch connectors without any code changes, as the operation semantics remain the same.
|
||||
|
||||
NOTE: For the corner cases where the native library API is required, `RedisConnection` provides a dedicated method (`getNativeConnection`) that returns the raw, underlying object used for communication.
|
||||
|
||||
Active `RedisConnection` objects are created through `RedisConnectionFactory`.
|
||||
In addition, the factory acts as `PersistenceExceptionTranslator` objects, meaning that, once declared, they let you do transparent exception translation.
|
||||
For example, you can do exception translation through the use of the `@Repository` annotation and AOP.
|
||||
For more information, see the dedicated {spring-framework-docs}/data-access.html#orm-exception-translation[section] in the Spring Framework documentation.
|
||||
|
||||
NOTE: `RedisConnection` classes are **not** Thread-safe.
|
||||
While the underlying native connection, such as Lettuce's `StatefulRedisConnection`, may be Thread-safe, Spring Data Redis's `LettuceConnection` class itself is not.
|
||||
Therefore, you should **not** share instances of a `RedisConnection` across multiple Threads.
|
||||
This is especially true for transactional, or blocking Redis operations and commands, such as `BLPOP`.
|
||||
In transactional and pipelining operations, for instance, `RedisConnection` holds onto unguarded mutable state to complete the operation correctly, thereby making it unsafe to use with multiple Threads.
|
||||
This is by design.
|
||||
|
||||
TIP: If you need to share (stateful) Redis resources, like connections, across multiple Threads, for performance reasons or otherwise, then you should acquire the native connection and use the Redis client library (driver) API directly.
|
||||
Alternatively, you can use the `RedisTemplate`, which acquires and manages connections for operations (and Redis commands) in a Thread-safe manner.
|
||||
See xref:redis/template.adoc[documentation] on `RedisTemplate` for more details.
|
||||
|
||||
NOTE: Depending on the underlying configuration, the factory can return a new connection or an existing connection (when a pool or shared native connection is used).
|
||||
|
||||
The easiest way to work with a `RedisConnectionFactory` is to configure the appropriate connector through the IoC container and inject it into the using class.
|
||||
|
||||
Unfortunately, currently, not all connectors support all Redis features.
|
||||
When invoking a method on the Connection API that is unsupported by the underlying library, an `UnsupportedOperationException` is thrown.
|
||||
The following overview explains features that are supported by the individual Redis connectors:
|
||||
|
||||
[[redis:connectors:overview]]
|
||||
.Feature Availability across Redis Connectors
|
||||
|===
|
||||
| Supported Feature | Lettuce | Jedis
|
||||
|
||||
| Standalone Connections
|
||||
| X
|
||||
| X
|
||||
|
||||
| xref:redis.adoc#redis:write-to-master-read-from-replica[Master/Replica Connections]
|
||||
| X
|
||||
|
|
||||
|
||||
| xref:redis.adoc#redis:sentinel[Redis Sentinel]
|
||||
| Master Lookup, Sentinel Authentication, Replica Reads
|
||||
| Master Lookup
|
||||
|
||||
| xref:redis/cluster.adoc[Redis Cluster]
|
||||
| Cluster Connections, Cluster Node Connections, Replica Reads
|
||||
| Cluster Connections, Cluster Node Connections
|
||||
|
||||
| Transport Channels
|
||||
| TCP, OS-native TCP (epoll, kqueue), Unix Domain Sockets
|
||||
| TCP
|
||||
|
||||
| Connection Pooling
|
||||
| X (using `commons-pool2`)
|
||||
| X (using `commons-pool2`)
|
||||
|
||||
| Other Connection Features
|
||||
| Singleton-connection sharing for non-blocking commands
|
||||
| Pipelining and Transactions mutually exclusive. Cannot use server/connection commands in pipeline/transactions.
|
||||
|
||||
| SSL Support
|
||||
| X
|
||||
| X
|
||||
|
||||
| xref:redis/pubsub.adoc[Pub/Sub]
|
||||
| X
|
||||
| X
|
||||
|
||||
| xref:redis/pipelining.adoc[Pipelining]
|
||||
| X
|
||||
| X (Pipelining and Transactions mutually exclusive)
|
||||
|
||||
| xref:redis/transactions.adoc[Transactions]
|
||||
| X
|
||||
| X (Pipelining and Transactions mutually exclusive)
|
||||
|
||||
| Datatype support
|
||||
| Key, String, List, Set, Sorted Set, Hash, Server, Stream, Scripting, Geo, HyperLogLog
|
||||
| Key, String, List, Set, Sorted Set, Hash, Server, Scripting, Geo, HyperLogLog
|
||||
|
||||
| Reactive (non-blocking) API
|
||||
| X
|
||||
|
|
||||
|
||||
|===
|
||||
|
||||
[[redis:connectors:lettuce]]
|
||||
== Configuring the Lettuce Connector
|
||||
|
||||
https://github.com/lettuce-io/lettuce-core[Lettuce] is a https://netty.io/[Netty]-based open-source connector supported by Spring Data Redis through the `org.springframework.data.redis.connection.lettuce` package.
|
||||
|
||||
.Add the following to the pom.xml files `dependencies` element:
|
||||
[source,xml,subs="+attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
|
||||
<!-- other dependency elements omitted -->
|
||||
|
||||
<dependency>
|
||||
<groupId>io.lettuce</groupId>
|
||||
<artifactId>lettuce-core</artifactId>
|
||||
<version>{lettuce}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
The following example shows how to create a new Lettuce connection factory:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
|
||||
@Bean
|
||||
public LettuceConnectionFactory redisConnectionFactory() {
|
||||
|
||||
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("server", 6379));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
There are also a few Lettuce-specific connection parameters that can be tweaked.
|
||||
By default, all `LettuceConnection` instances created by the `LettuceConnectionFactory` share the same thread-safe native connection for all non-blocking and non-transactional operations.
|
||||
To use a dedicated connection each time, set `shareNativeConnection` to `false`. `LettuceConnectionFactory` can also be configured to use a `LettucePool` for pooling blocking and transactional connections or all connections if `shareNativeConnection` is set to `false`.
|
||||
|
||||
The following example shows a more sophisticated configuration, including SSL and timeouts, that uses `LettuceClientConfigurationBuilder`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public LettuceConnectionFactory lettuceConnectionFactory() {
|
||||
|
||||
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
|
||||
.useSsl().and()
|
||||
.commandTimeout(Duration.ofSeconds(2))
|
||||
.shutdownTimeout(Duration.ZERO)
|
||||
.build();
|
||||
|
||||
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("localhost", 6379), clientConfig);
|
||||
}
|
||||
----
|
||||
|
||||
For more detailed client configuration tweaks, see https://docs.spring.io/spring-data/redis/docs/current/api/org/springframework/data/redis/connection/lettuce/LettuceClientConfiguration.html[`LettuceClientConfiguration`].
|
||||
|
||||
Lettuce integrates with Netty's https://netty.io/wiki/native-transports.html[native transports], letting you use Unix domain sockets to communicate with Redis.
|
||||
Make sure to include the appropriate native transport dependencies that match your runtime environment.
|
||||
The following example shows how to create a Lettuce Connection factory for a Unix domain socket at `/var/run/redis.sock`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
|
||||
@Bean
|
||||
public LettuceConnectionFactory redisConnectionFactory() {
|
||||
|
||||
return new LettuceConnectionFactory(new RedisSocketConfiguration("/var/run/redis.sock"));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: Netty currently supports the epoll (Linux) and kqueue (BSD/macOS) interfaces for OS-native transport.
|
||||
|
||||
[[redis:connectors:jedis]]
|
||||
== Configuring the Jedis Connector
|
||||
|
||||
https://github.com/redis/jedis[Jedis] is a community-driven connector supported by the Spring Data Redis module through the `org.springframework.data.redis.connection.jedis` package.
|
||||
|
||||
.Add the following to the pom.xml files `dependencies` element:
|
||||
[source,xml,subs="+attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
|
||||
<!-- other dependency elements omitted -->
|
||||
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>{jedis}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
In its simplest form, the Jedis configuration looks as follow:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
|
||||
@Bean
|
||||
public JedisConnectionFactory redisConnectionFactory() {
|
||||
return new JedisConnectionFactory();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
For production use, however, you might want to tweak settings such as the host or password, as shown in the following example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
class RedisConfiguration {
|
||||
|
||||
@Bean
|
||||
public JedisConnectionFactory redisConnectionFactory() {
|
||||
|
||||
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("server", 6379);
|
||||
return new JedisConnectionFactory(config);
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -0,0 +1,41 @@
|
||||
[[redis.getting-started]]
|
||||
= Getting Started
|
||||
|
||||
An easy way to bootstrap setting up a working environment is to create a Spring-based project via https://start.spring.io/#!type=maven-project&dependencies=data-redis[start.spring.io] or create a Spring project in https://spring.io/tools[Spring Tools].
|
||||
|
||||
[[redis.examples-repo]]
|
||||
== Examples Repository
|
||||
|
||||
The GitHub https://github.com/spring-projects/spring-data-examples[spring-data-examples repository] hosts several examples that you can download and play around with to get a feel for how the library works.
|
||||
|
||||
[[redis.hello-world]]
|
||||
== Hello World
|
||||
|
||||
First, you need to set up a running Redis server.
|
||||
Spring Data Redis requires Redis 2.6 or above and Spring Data Redis integrates with https://github.com/lettuce-io/lettuce-core[Lettuce] and https://github.com/redis/jedis[Jedis], two popular open-source Java libraries for Redis.
|
||||
|
||||
Now you can create a simple Java application that stores and reads a value to and from Redis.
|
||||
|
||||
Create the main application to run, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Imperative::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
include::example$examples/RedisApplication.java[tags=file]
|
||||
----
|
||||
|
||||
Reactive::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
include::example$examples/ReactiveRedisApplication.java[tags=file]
|
||||
----
|
||||
======
|
||||
|
||||
Even in this simple example, there are a few notable things to point out:
|
||||
|
||||
* You can create an instance of `RedisTemplate` (or `ReactiveRedisTemplate` for reactive usage) with a `RedisConnectionFactory`. Connection factories are an abstraction on top of the supported drivers.
|
||||
* There's no single way to use Redis as it comes with support for a wide range of data structures such as plain keys ("strings"), lists, sets, sorted sets, streams, hashes and so on.
|
||||
133
src/main/antora/modules/ROOT/pages/redis/hash-mappers.adoc
Normal file
133
src/main/antora/modules/ROOT/pages/redis/hash-mappers.adoc
Normal file
@@ -0,0 +1,133 @@
|
||||
[[redis.hashmappers.root]]
|
||||
= Hash Mapping
|
||||
|
||||
Data can be stored by using various data structures within Redis. `Jackson2JsonRedisSerializer` can convert objects in https://en.wikipedia.org/wiki/JSON[JSON] format. Ideally, JSON can be stored as a value by using plain keys. You can achieve a more sophisticated mapping of structured objects by using Redis hashes. Spring Data Redis offers various strategies for mapping data to hashes (depending on the use case):
|
||||
|
||||
* Direct mapping, by using `HashOperations` and a xref:redis.adoc#redis:serializer[serializer]
|
||||
* Using xref:repositories.adoc[Redis Repositories]
|
||||
* Using `HashMapper` and `HashOperations`
|
||||
|
||||
[[redis.hashmappers.mappers]]
|
||||
== Hash Mappers
|
||||
|
||||
Hash mappers are converters of map objects to a `Map<K, V>` and back. `HashMapper` is intended for using with Redis Hashes.
|
||||
|
||||
Multiple implementations are available:
|
||||
|
||||
* `BeanUtilsHashMapper` using Spring's {spring-framework-javadoc}/org/springframework/beans/BeanUtils.html[BeanUtils].
|
||||
* `ObjectHashMapper` using xref:redis/redis-repositories/mapping.adoc[Object-to-Hash Mapping].
|
||||
* <<redis.hashmappers.jackson2,`Jackson2HashMapper`>> using https://github.com/FasterXML/jackson[FasterXML Jackson].
|
||||
|
||||
The following example shows one way to implement hash mapping:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
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);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[redis.hashmappers.jackson2]]
|
||||
=== Jackson2HashMapper
|
||||
|
||||
`Jackson2HashMapper` provides Redis Hash mapping for domain objects by using https://github.com/FasterXML/jackson[FasterXML Jackson].
|
||||
`Jackson2HashMapper` can map top-level properties as Hash field names and, optionally, flatten the structure.
|
||||
Simple types map to simple values. Complex types (nested objects, collections, maps, and so on) are represented as nested JSON.
|
||||
|
||||
Flattening creates individual hash entries for all nested properties and resolves complex types into simple types, as far as possible.
|
||||
|
||||
Consider the following class and the data structure it contains:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class Person {
|
||||
String firstname;
|
||||
String lastname;
|
||||
Address address;
|
||||
Date date;
|
||||
LocalDateTime localDateTime;
|
||||
}
|
||||
|
||||
public class Address {
|
||||
String city;
|
||||
String country;
|
||||
}
|
||||
----
|
||||
|
||||
The following table shows how the data in the preceding class would appear in normal mapping:
|
||||
|
||||
.Normal Mapping
|
||||
[width="80%",cols="<1,<2",options="header"]
|
||||
|====
|
||||
|Hash Field
|
||||
|Value
|
||||
|
||||
|firstname
|
||||
|`Jon`
|
||||
|
||||
|lastname
|
||||
|`Snow`
|
||||
|
||||
|address
|
||||
|`{ "city" : "Castle Black", "country" : "The North" }`
|
||||
|
||||
|date
|
||||
|`1561543964015`
|
||||
|
||||
|localDateTime
|
||||
|`2018-01-02T12:13:14`
|
||||
|====
|
||||
|
||||
The following table shows how the data in the preceding class would appear in flat mapping:
|
||||
|
||||
.Flat Mapping
|
||||
[width="80%",cols="<1,<2",options="header"]
|
||||
|====
|
||||
|Hash Field
|
||||
|Value
|
||||
|
||||
|firstname
|
||||
|`Jon`
|
||||
|
||||
|lastname
|
||||
|`Snow`
|
||||
|
||||
|address.city
|
||||
|`Castle Black`
|
||||
|
||||
|address.country
|
||||
|`The North`
|
||||
|
||||
|date
|
||||
|`1561543964015`
|
||||
|
||||
|localDateTime
|
||||
|`2018-01-02T12:13:14`
|
||||
|====
|
||||
|
||||
NOTE: Flattening requires all property names to not interfere with the JSON path. Using dots or brackets in map keys or as property names is not supported when you use flattening. The resulting hash cannot be mapped back into an Object.
|
||||
|
||||
NOTE: `java.util.Date` and `java.util.Calendar` are represented with milliseconds. JSR-310 Date/Time types are serialized to their `toString` form if `jackson-datatype-jsr310` is on the class path.
|
||||
@@ -1,5 +1,5 @@
|
||||
[[pubsub]]
|
||||
= Redis Messaging (Pub/Sub)
|
||||
= Pub/Sub Messaging
|
||||
|
||||
Spring Data provides dedicated messaging integration for Redis, similar in functionality and naming to the JMS integration in Spring Framework.
|
||||
|
||||
@@ -15,20 +15,41 @@ The `org.springframework.data.redis.connection` and `org.springframework.data.re
|
||||
[[redis:pubsub:publish]]
|
||||
== Publishing (Sending Messages)
|
||||
|
||||
To publish a message, you can use, as with the other operations, either the low-level `RedisConnection` or the high-level `RedisTemplate`. Both entities offer the `publish` method, which accepts the message and the destination channel as arguments. While `RedisConnection` requires raw data (array of bytes), the `RedisTemplate` lets arbitrary objects be passed in as messages, as shown in the following example:
|
||||
To publish a message, you can use, as with the other operations, either the low-level `[Reactive]RedisConnection` or the high-level `[Reactive]RedisOperations`. Both entities offer the `publish` method, which accepts the message and the destination channel as arguments. While `RedisConnection` requires raw data (array of bytes), the `[Reactive]RedisOperations` lets arbitrary objects be passed in as messages, as shown in the following example:
|
||||
|
||||
[source,java]
|
||||
[tabs]
|
||||
======
|
||||
Imperative::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// send message through connection RedisConnection con = ...
|
||||
byte[] msg = ...
|
||||
byte[] channel = ...
|
||||
con.publish(msg, channel);
|
||||
// send message through connection
|
||||
RedisConnection con = …
|
||||
byte[] msg = …
|
||||
byte[] channel = …
|
||||
con.pubSubCommands().publish(msg, channel);
|
||||
|
||||
// send message through RedisTemplate
|
||||
RedisTemplate template = ...
|
||||
Long numberOfClients = template.convertAndSend("hello!", "world");
|
||||
// send message through RedisOperations
|
||||
RedisOperations operations = …
|
||||
Long numberOfClients = operations.convertAndSend("hello!", "world");
|
||||
----
|
||||
|
||||
Reactive::
|
||||
+
|
||||
[source,java,role="secondary"]
|
||||
----
|
||||
// send message through connection
|
||||
ReactiveRedisConnection con = …
|
||||
ByteBuffer[] msg = …
|
||||
ByteBuffer[] channel = …
|
||||
con.pubSubCommands().publish(msg, channel);
|
||||
|
||||
// send message through ReactiveRedisOperations
|
||||
ReactiveRedisOperations operations = …
|
||||
Mono<Long> numberOfClients = operations.convertAndSend("hello!", "world");
|
||||
----
|
||||
======
|
||||
|
||||
[[redis:pubsub:subscribe]]
|
||||
== Subscribing (Receiving Messages)
|
||||
|
||||
@@ -36,7 +57,7 @@ On the receiving side, one can subscribe to one or multiple channels either by n
|
||||
|
||||
At the low-level, `RedisConnection` offers the `subscribe` and `pSubscribe` methods that map the Redis commands for subscribing by channel or by pattern, respectively. Note that multiple channels or patterns can be used as arguments. To change the subscription of a connection or query whether it is listening, `RedisConnection` provides the `getSubscription` and `isSubscribed` methods.
|
||||
|
||||
NOTE: Subscription commands in Spring Data Redis are blocking. That is, calling subscribe on a connection causes the current thread to block as it starts waiting for messages. The thread is released only if the subscription is canceled, which happens when another thread invokes `unsubscribe` or `pUnsubscribe` on the *same* connection. See "`<<redis:pubsub:subscribe:containers>>`" (later in this document) for a solution to this problem.
|
||||
NOTE: Subscription commands in Spring Data Redis are blocking. That is, calling subscribe on a connection causes the current thread to block as it starts waiting for messages. The thread is released only if the subscription is canceled, which happens when another thread invokes `unsubscribe` or `pUnsubscribe` on the *same* connection. See "`xref:redis/pubsub.adoc#redis:pubsub:subscribe:containers[Message Listener Containers]`" (later in this document) for a solution to this problem.
|
||||
|
||||
As mentioned earlier, once subscribed, a connection starts waiting for messages. Only commands that add new subscriptions, modify existing subscriptions, and cancel existing subscriptions are allowed. Invoking anything other than `subscribe`, `pSubscribe`, `unsubscribe`, or `pUnsubscribe` throws an exception.
|
||||
|
||||
@@ -86,8 +107,10 @@ public class DefaultMessageDelegate implements MessageDelegate {
|
||||
|
||||
Notice how the above implementation of the `MessageDelegate` interface (the above `DefaultMessageDelegate` class) has *no* Redis dependencies at all. It truly is a POJO that we make into an MDP with the following configuration:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@@ -116,7 +139,8 @@ class MyConfig {
|
||||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -136,7 +160,7 @@ class MyConfig {
|
||||
...
|
||||
</beans>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
NOTE: The listener topic can be either a channel (for example, `topic="chatroom"`) or a pattern (for example, `topic="*room"`)
|
||||
|
||||
@@ -165,3 +189,48 @@ The preceding example uses the Redis namespace to declare the message listener c
|
||||
----
|
||||
|
||||
Each time a message is received, the adapter automatically and transparently performs translation (using the configured `RedisSerializer`) between the low-level format and the required object type. Any exception caused by the method invocation is caught and handled by the container (by default, exceptions get logged).
|
||||
|
||||
[[redis:reactive:pubsub:subscribe:containers]]
|
||||
== Reactive Message Listener Container
|
||||
|
||||
Spring Data offers `ReactiveRedisMessageListenerContainer` which does all the heavy lifting of conversion and subscription state management on behalf of the user.
|
||||
|
||||
The message listener container itself does not require external threading resources. It uses the driver threads to publish messages.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
ReactiveRedisConnectionFactory factory = …
|
||||
ReactiveRedisMessageListenerContainer container = new ReactiveRedisMessageListenerContainer(factory);
|
||||
|
||||
Flux<ChannelMessage<String, String>> stream = container.receive(ChannelTopic.of("my-channel"));
|
||||
----
|
||||
|
||||
To await and ensure proper subscription, you can use the `receiveLater` method that returns a `Mono<Flux<ChannelMessage>>`.
|
||||
The resulting `Mono` completes with an inner publisher as a result of completing the subscription to the given topics. By intercepting `onNext` signals, you can synchronize server-side subscriptions.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
ReactiveRedisConnectionFactory factory = …
|
||||
ReactiveRedisMessageListenerContainer container = new ReactiveRedisMessageListenerContainer(factory);
|
||||
|
||||
Mono<Flux<ChannelMessage<String, String>>> stream = container.receiveLater(ChannelTopic.of("my-channel"));
|
||||
|
||||
stream.doOnNext(inner -> // notification hook when Redis subscriptions are synchronized with the server)
|
||||
.flatMapMany(Function.identity())
|
||||
.…;
|
||||
----
|
||||
|
||||
[[redis:reactive:pubsub:subscribe:template]]
|
||||
=== Subscribing via template API
|
||||
|
||||
As mentioned above you can directly use `ReactiveRedisTemplate` to subscribe to channels / patterns. This approach
|
||||
offers a straight forward, though limited solution as you lose the option to add subscriptions after the initial
|
||||
ones. Nevertheless you still can control the message stream via the returned `Flux` using eg. `take(Duration)`. When
|
||||
done reading, on error or cancellation all bound resources are freed again.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
redisTemplate.listenToChannel("channel1", "channel2").doOnNext(msg -> {
|
||||
// message processing ...
|
||||
}).subscribe();
|
||||
----
|
||||
@@ -1,9 +1,7 @@
|
||||
[[redis:support:cache-abstraction]]
|
||||
= Redis Cache
|
||||
|
||||
NOTE: Changed in 2.0
|
||||
|
||||
Spring Data Redis provides an implementation of Spring Framework's {spring-framework-reference}/integration.html#cache[Cache Abstraction] in the `org.springframework.data.redis.cache` package.
|
||||
Spring Data Redis provides an implementation of Spring Framework's {spring-framework-docs}/integration.html#cache[Cache Abstraction] in the `org.springframework.data.redis.cache` package.
|
||||
To use Redis as a backing implementation, add `RedisCacheManager` to your configuration, as follows:
|
||||
|
||||
[source,java]
|
||||
@@ -87,7 +85,8 @@ RedisCacheManager cacheManager = RedisCacheManager
|
||||
[NOTE]
|
||||
====
|
||||
The `KEYS` batch strategy is fully supported using any driver and Redis operation mode (Standalone, Clustered).
|
||||
`SCAN` is fully supported when using the Lettuce driver. Jedis supports `SCAN` only in non-clustered modes.
|
||||
`SCAN` is fully supported when using the Lettuce driver.
|
||||
Jedis supports `SCAN` only in non-clustered modes.
|
||||
====
|
||||
|
||||
The following table lists the default settings for `RedisCacheManager`:
|
||||
@@ -162,7 +161,8 @@ The entry must be written to prevent the entry from expiring when declaring a TT
|
||||
[NOTE]
|
||||
====
|
||||
Some data stores expire an entry when TTL is configured no matter what type of data access operation occurs on the entry (reads, writes, or otherwise).
|
||||
After the set, configured TTL expiration timeout, the entry is evicted from the data store regardless. Eviction actions (for example: destroy, invalidate, overflow-to-disk (for persistent stores), etc.) are data store specific.
|
||||
After the set, configured TTL expiration timeout, the entry is evicted from the data store regardless.
|
||||
Eviction actions (for example: destroy, invalidate, overflow-to-disk (for persistent stores), etc.) are data store specific.
|
||||
====
|
||||
|
||||
[[redis:support:cache-abstraction:expiration:tti]]
|
||||
@@ -180,7 +180,7 @@ If all cache entries should expire after a set duration of time, then simply con
|
||||
|
||||
[source,java]
|
||||
----
|
||||
RedisCacheConfiguration fiveMinuteTtlExpirationCacheConfiguration =
|
||||
RedisCacheConfiguration fiveMinuteTtlExpirationDefaults =
|
||||
RedisCacheConfiguration.defaultCacheConfig().enableTtl(Duration.ofMinutes(5));
|
||||
----
|
||||
|
||||
@@ -188,10 +188,11 @@ However, if the TTL expiration timeout should vary by cache entry, then you must
|
||||
|
||||
[source,java]
|
||||
----
|
||||
class MyCustomTtlFunction implements TtlFunction {
|
||||
enum MyCustomTtlFunction implements TtlFunction {
|
||||
|
||||
static final MyCustomTtlFunction INSTANCE = new MyCustomTtlFunction();
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Duration getTimeToLive(Object key, @Nullable Object value) {
|
||||
// compute a TTL expiration timeout (Duration) based on the cache entry key and/or value
|
||||
}
|
||||
@@ -209,7 +210,7 @@ Then, you can either configure the fixed `Duration` or the dynamic, per-cache en
|
||||
[source,java]
|
||||
----
|
||||
RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
|
||||
.cacheDefaults(fiveMinuteTtlExpirationCacheConfiguration)
|
||||
.cacheDefaults(fiveMinuteTtlExpirationDefaults)
|
||||
.build();
|
||||
----
|
||||
|
||||
@@ -218,37 +219,39 @@ Or, alternatively:
|
||||
.Global, dynamically computed per-cache entry Duration TTL expiration timeout
|
||||
[source,java]
|
||||
----
|
||||
RedisCacheConfiguration defaults = RedisCacheConfiguration.defaultCacheConfig()
|
||||
.entryTtl(MyCustomTtlFunction.INSTANCE);
|
||||
|
||||
RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
|
||||
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
|
||||
.entryTtl(MyCustomTtlFunction.INSTANCE))
|
||||
.cacheDefaults(defaults)
|
||||
.build();
|
||||
----
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
If you try to set both a fixed `Duration` and dynamic, per-cache entry `Duration` TTL expiration using a custom `TtlFunction`, then last one wins!
|
||||
====
|
||||
|
||||
Of course, you can combine both global and per-cache configuration using:
|
||||
|
||||
.Global fixed Duration TTL expiration timeout
|
||||
[source,java]
|
||||
----
|
||||
|
||||
RedisCacheConfiguration predefined = RedisCacheConfiguration.defaultCacheConfig()
|
||||
.entryTtl(MyCustomTtlFunction.INSTANCE));
|
||||
|
||||
Map<String, RedisCacheConfiguration> initialCaches = Collections.singletonMap("predefined",predefined);
|
||||
|
||||
RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
|
||||
.cacheDefaults(fiveMinuteTtlExpirationCacheConfiguration)
|
||||
.withInitialCacheConfiguration(Collections.singletonMap("predefined",
|
||||
RedisCacheConfiguration.defaultCacheConfig().entryTtl(MyCustomTtlFunction.INSTANCE)))
|
||||
.cacheDefaults(fiveMinuteTtlExpirationDefaults)
|
||||
.withInitialCacheConfiguration()
|
||||
.build();
|
||||
----
|
||||
|
||||
[[redis:support:cache-abstraction:expiration:tti]]
|
||||
[[redis:support:cache-abstraction:expiration:tti2]]
|
||||
=== Time-To-Idle (TTI) Expiration
|
||||
|
||||
Redis itself does not support the concept of true, time-to-idle (TTI) expiration.
|
||||
Still, using Spring Data Redis's Cache implementation, it is possible to achieve time-to-idle (TTI) expiration-like behavior.
|
||||
|
||||
The configuration of TTI in Spring Data Redis's Cache implementation must be explicitly enabled, that is, is opt-in.
|
||||
Additionally, you must also provide TTL configuration using either a fixed `Duration` or a custom implementation of the `TtlFunction` interface as described above in <<redis:support:cache-abstraction:expiration>>.
|
||||
Additionally, you must also provide TTL configuration using either a fixed `Duration` or a custom implementation of the `TtlFunction` interface as described above in <<redis:support:cache-abstraction:expiration,Redis Cache Expiration>>.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -264,19 +267,14 @@ class RedisConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
RedisCacheConfiguration redisCacheConfiguration() {
|
||||
RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
|
||||
|
||||
return RedisCacheConfiguration.defaultCacheConfig()
|
||||
RedisCacheConfiguration defaults = RedisCacheConfiguration.defaultCacheConfig()
|
||||
.entryTtl(Duration.ofMinutes(5))
|
||||
.enableTimeToIdle();
|
||||
}
|
||||
|
||||
@Bean
|
||||
RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory,
|
||||
RedisCacheConfiguraton cacheConfiguraton) {
|
||||
|
||||
return RedisCacheManager.builder(connectionFactory)
|
||||
.cacheDefaults(cacheConfiguration)
|
||||
.cacheDefaults(defaults)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -303,5 +301,6 @@ There are no exceptions to this rule.
|
||||
If you are mixing and matching different data access patterns across your Spring Data Redis application (for example: caching, invoking operations using `RedisTemplate` and possibly, or especially when using Spring Data Repository CRUD operations), then accessing an entry may not necessarily prevent the entry from expiring if TTL expiration was set.
|
||||
For example, an entry maybe "put" in (written to) the cache during a `@Cacheable` service method invocation with a TTL expiration (i.e. `SET <expiration options>`) and later read using a Spring Data Redis Repository before the expiration timeout (using `GET` without expiration options).
|
||||
A simple `GET` without specifying expiration options will not reset the TTL expiration timeout on an entry.
|
||||
Therefore, the entry may expire before the next data access operation, even though it was just read. Since this cannot be enforced in the Redis server, then it is the responsibility of your application to consistently access an entry when time-to-idle expiration is configured, in and outside of caching, where appropriate.
|
||||
Therefore, the entry may expire before the next data access operation, even though it was just read.
|
||||
Since this cannot be enforced in the Redis server, then it is the responsibility of your application to consistently access an entry when time-to-idle expiration is configured, in and outside of caching, where appropriate.
|
||||
====
|
||||
@@ -0,0 +1,141 @@
|
||||
[[redis.repositories.anatomy]]
|
||||
= Redis Repositories Anatomy
|
||||
|
||||
Redis as a store itself offers a very narrow low-level API leaving higher level functions, such as secondary indexes and query operations, up to the user.
|
||||
|
||||
This section provides a more detailed view of commands issued by the repository abstraction for a better understanding of potential performance implications.
|
||||
|
||||
Consider the following entity class as the starting point for all operations:
|
||||
|
||||
.Example entity
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@RedisHash("people")
|
||||
public class Person {
|
||||
|
||||
@Id String id;
|
||||
@Indexed String firstname;
|
||||
String lastname;
|
||||
Address hometown;
|
||||
}
|
||||
|
||||
public class Address {
|
||||
|
||||
@GeoIndexed Point location;
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[redis.repositories.anatomy.insert]]
|
||||
== Insert new
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
repository.save(new Person("rand", "al'thor"));
|
||||
----
|
||||
|
||||
[source,text]
|
||||
----
|
||||
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> Save the flattened entry as hash.
|
||||
<2> Add the key of the hash written in <1> to the helper index of entities in the same keyspace.
|
||||
<3> Add the key of the hash written in <2> to the secondary index of firstnames with the properties value.
|
||||
<4> Add the index of <3> to the set of helper structures for entry to keep track of indexes to clean on delete/update.
|
||||
====
|
||||
|
||||
[[redis.repositories.anatomy.replace]]
|
||||
== Replace existing
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
repository.save(new Person("e82908cf-e7d3-47c2-9eec-b4e0967ad0c9", "Dragon Reborn", "al'thor"));
|
||||
----
|
||||
|
||||
[source,text]
|
||||
----
|
||||
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> Remove the existing hash to avoid leftovers of hash keys potentially no longer present.
|
||||
<2> Save the flattened entry as hash.
|
||||
<3> Add the key of the hash written in <1> to the helper index of entities in the same keyspace.
|
||||
<4> Get existing index structures that might need to be updated.
|
||||
<5> Check if the index exists and what type it is (text, geo, …).
|
||||
<6> Remove a potentially existing key from the index.
|
||||
<7> Remove the helper holding index information.
|
||||
<8> Add the key of the hash added in <2> to the secondary index of firstnames with the properties value.
|
||||
<9> Add the index of <6> to the set of helper structures for entry to keep track of indexes to clean on delete/update.
|
||||
====
|
||||
|
||||
[[redis.repositories.anatomy.geo]]
|
||||
== Save Geo Data
|
||||
|
||||
Geo indexes follow the same rules as normal text based ones but use geo structure to store values.
|
||||
Saving an entity that uses a Geo-indexed property results in the following commands:
|
||||
|
||||
====
|
||||
[source,text]
|
||||
----
|
||||
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> Add the key of the saved entry to the the geo index.
|
||||
<2> Keep track of the index structure.
|
||||
====
|
||||
|
||||
[[redis.repositories.anatomy.index]]
|
||||
== Find using simple index
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
repository.findByFirstname("egwene");
|
||||
----
|
||||
|
||||
[source,text]
|
||||
----
|
||||
SINTER "people:firstname:egwene" <1>
|
||||
HGETALL "people:d70091b5-0b9a-4c0a-9551-519e61bc9ef3" <2>
|
||||
HGETALL ...
|
||||
----
|
||||
|
||||
<1> Fetch keys contained in the secondary index.
|
||||
<2> Fetch each key returned by <1> individually.
|
||||
====
|
||||
|
||||
[[redis.repositories.anatomy.geo-index]]
|
||||
== Find using Geo Index
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
repository.findByHometownLocationNear(new Point(15, 37), new Distance(200, KILOMETERS));
|
||||
----
|
||||
|
||||
[source,text]
|
||||
----
|
||||
GEORADIUS "people:hometown:location" "15.0" "37.0" "200.0" "km" <1>
|
||||
HGETALL "people:76900e94-b057-44bc-abcf-8126d51a621b" <2>
|
||||
HGETALL ...
|
||||
----
|
||||
|
||||
<1> Fetch keys contained in the secondary index.
|
||||
<2> Fetch each key returned by <1> individually.
|
||||
====
|
||||
@@ -0,0 +1,68 @@
|
||||
[[redis.repositories.cdi-integration]]
|
||||
= CDI Integration
|
||||
|
||||
Instances of the repository interfaces are usually created by a container, for which Spring is the most natural choice when working with Spring Data.
|
||||
Spring offers sophisticated for creating bean instances.
|
||||
Spring Data Redis ships with a custom CDI extension that lets you use the repository abstraction in CDI environments.
|
||||
The extension is part of the JAR, so, to activate it, drop the Spring Data Redis JAR into your classpath.
|
||||
|
||||
You can then set up the infrastructure by implementing a CDI Producer for the `RedisConnectionFactory` and `RedisOperations`, as shown in the following example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
class RedisOperationsProducer {
|
||||
|
||||
|
||||
@Produces
|
||||
RedisConnectionFactory redisConnectionFactory() {
|
||||
|
||||
LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(new RedisStandaloneConfiguration());
|
||||
connectionFactory.afterPropertiesSet();
|
||||
connectionFactory.start();
|
||||
|
||||
return connectionFactory;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
The necessary setup can vary, depending on your JavaEE environment.
|
||||
|
||||
The Spring Data Redis CDI extension picks up all available repositories as CDI beans and creates a proxy for a Spring Data repository whenever a bean of a repository type is requested by the container.
|
||||
Thus, obtaining an instance of a Spring Data repository is a matter of declaring an `@Injected` property, as shown in the following example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
class RepositoryClient {
|
||||
|
||||
@Inject
|
||||
PersonRepository repository;
|
||||
|
||||
public void businessMethod() {
|
||||
List<Person> people = repository.findAll();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
A Redis Repository requires `RedisKeyValueAdapter` and `RedisKeyValueTemplate` instances.
|
||||
These beans are created and managed by the Spring Data CDI extension if no provided beans are found.
|
||||
You can, however, supply your own beans to configure the specific properties of `RedisKeyValueAdapter` and `RedisKeyValueTemplate`.
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
[[redis.repositories.cluster]]
|
||||
= Redis Repositories Running on a Cluster
|
||||
|
||||
You can use the Redis repository support in a clustered Redis environment.
|
||||
See the "`xref:redis/cluster.adoc[Redis Cluster]`" section for `ConnectionFactory` configuration details.
|
||||
Still, some additional configuration must be done, because the default key distribution spreads entities and secondary indexes through out the whole cluster and its slots.
|
||||
|
||||
The following table shows the details of data on a cluster (based on previous examples):
|
||||
|
||||
[options = "header, autowidth"]
|
||||
|===============
|
||||
|Key|Type|Slot|Node
|
||||
|people:e2c7dcee-b8cd-4424-883e-736ce564363e|id for hash|15171|127.0.0.1:7381
|
||||
|people:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56|id for hash|7373|127.0.0.1:7380
|
||||
|people:firstname:rand|index|1700|127.0.0.1:7379
|
||||
|
|
||||
|===============
|
||||
|
||||
====
|
||||
|
||||
Some commands (such as `SINTER` and `SUNION`) can only be processed on the server side when all involved keys map to the same slot.
|
||||
Otherwise, computation has to be done on client side.
|
||||
Therefore, it is useful to pin keyspaces to a single slot, which lets make use of Redis server side computation right away.
|
||||
The following table shows what happens when you do (note the change in the slot column and the port value in the node column):
|
||||
|
||||
[options = "header, autowidth"]
|
||||
|===============
|
||||
|Key|Type|Slot|Node
|
||||
|\{people}:e2c7dcee-b8cd-4424-883e-736ce564363e|id for hash|2399|127.0.0.1:7379
|
||||
|\{people}:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56|id for hash|2399|127.0.0.1:7379
|
||||
|\{people}:firstname:rand|index|2399|127.0.0.1:7379
|
||||
|
|
||||
|===============
|
||||
====
|
||||
|
||||
TIP: Define and pin keyspaces by using `@RedisHash("\{yourkeyspace}")` to specific slots when you use Redis cluster.
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
[[redis.repositories.expirations]]
|
||||
= Time To Live
|
||||
|
||||
Objects stored in Redis may be valid only for a certain amount of time.
|
||||
This is especially useful for persisting short-lived objects in Redis without having to remove them manually when they reach their end of life.
|
||||
The expiration time in seconds can be set with `@RedisHash(timeToLive=...)` as well as by using `KeyspaceSettings` (see xref:redis/redis-repositories/keyspaces.adoc[Keyspaces]).
|
||||
|
||||
More flexible expiration times can be set by using the `@TimeToLive` annotation on either a numeric property or a method.
|
||||
However, do not apply `@TimeToLive` on both a method and a property within the same class.
|
||||
The following example shows the `@TimeToLive` annotation on a property and on a method:
|
||||
|
||||
.Expirations
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
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();
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
NOTE: Annotating a property explicitly with `@TimeToLive` reads back the actual `TTL` or `PTTL` value from Redis. -1 indicates that the object has no associated expiration.
|
||||
|
||||
The repository implementation ensures subscription to https://redis.io/topics/notifications[Redis keyspace notifications] via `RedisMessageListenerContainer`.
|
||||
|
||||
When the expiration is set to a positive value, the corresponding `EXPIRE` command is run.
|
||||
In addition to persisting the original, a phantom copy is persisted in Redis and set to expire five minutes after the original one.
|
||||
This is done to enable the Repository support to publish `RedisKeyExpiredEvent`, holding the expired value in Spring's `ApplicationEventPublisher` whenever a key expires, even though the original values have already been removed.
|
||||
Expiry events are received on all connected applications that use Spring Data Redis repositories.
|
||||
|
||||
By default, the key expiry listener is disabled when initializing the application.
|
||||
The startup mode can be adjusted in `@EnableRedisRepositories` or `RedisKeyValueAdapter` to start the listener with the application or upon the first insert of an entity with a TTL.
|
||||
See https://docs.spring.io/spring-data/redis/docs/{version}/api/org/springframework/data/redis/core/RedisKeyValueAdapter.EnableKeyspaceEvents.html[`EnableKeyspaceEvents`] for possible values.
|
||||
|
||||
The `RedisKeyExpiredEvent` holds a copy of the expired domain object as well as the key.
|
||||
|
||||
NOTE: Delaying or disabling the expiry event listener startup impacts `RedisKeyExpiredEvent` publishing.
|
||||
A disabled event listener does not publish expiry events.
|
||||
A delayed startup can cause loss of events because of the delayed listener initialization.
|
||||
|
||||
NOTE: The keyspace notification message listener alters `notify-keyspace-events` settings in Redis, if those are not already set.
|
||||
Existing settings are not overridden, so you must set up those settings correctly (or leave them empty).
|
||||
Note that `CONFIG` is disabled on AWS ElastiCache, and enabling the listener leads to an error.
|
||||
To work around this behavior, set the `keyspaceNotificationsConfigParameter` parameter to an empty string.
|
||||
This prevents `CONFIG` command usage.
|
||||
|
||||
NOTE: Redis Pub/Sub messages are not persistent.
|
||||
If a key expires while the application is down, the expiry event is not processed, which may lead to secondary indexes containing references to the expired object.
|
||||
|
||||
NOTE: `@EnableKeyspaceEvents(shadowCopy = OFF)` disable storage of phantom copies and reduces data size within Redis. `RedisKeyExpiredEvent` will only contain the `id` of the expired key.
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
[[redis.repositories.indexes]]
|
||||
= Secondary Indexes
|
||||
|
||||
https://redis.io/topics/indexes[Secondary indexes] are used to enable lookup operations based on native Redis structures.
|
||||
Values are written to the according indexes on every save and are removed when objects are deleted or xref:redis/redis-repositories/expirations.adoc[expire].
|
||||
|
||||
[[redis.repositories.indexes.simple]]
|
||||
== Simple Property Index
|
||||
|
||||
Given the sample `Person` entity shown earlier, we can create an index for `firstname` by annotating the property with `@Indexed`, as shown in the following example:
|
||||
|
||||
.Annotation driven indexing
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@RedisHash("people")
|
||||
public class Person {
|
||||
|
||||
@Id String id;
|
||||
@Indexed String firstname;
|
||||
String lastname;
|
||||
Address address;
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Indexes are built up for actual property values.
|
||||
Saving two Persons (for example, "rand" and "aviendha") results in setting up indexes similar to the following:
|
||||
|
||||
====
|
||||
[source,text]
|
||||
----
|
||||
SADD people:firstname:rand e2c7dcee-b8cd-4424-883e-736ce564363e
|
||||
SADD people:firstname:aviendha a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56
|
||||
----
|
||||
====
|
||||
|
||||
It is also possible to have indexes on nested elements.
|
||||
Assume `Address` has a `city` property that is annotated with `@Indexed`.
|
||||
In that case, once `person.address.city` is not `null`, we have Sets for each city, as shown in the following example:
|
||||
|
||||
====
|
||||
[source,text]
|
||||
----
|
||||
SADD people:address.city:tear e2c7dcee-b8cd-4424-883e-736ce564363e
|
||||
----
|
||||
====
|
||||
|
||||
Furthermore, the programmatic setup lets you define indexes on map keys and list properties, as shown in the following example:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@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`
|
||||
====
|
||||
|
||||
CAUTION: Indexes cannot be resolved on xref:redis/redis-repositories/usage.adoc#redis.repositories.references[References].
|
||||
|
||||
As with keyspaces, you can configure indexes without needing to annotate the actual domain type, as shown in the following example:
|
||||
|
||||
.Index Setup with @EnableRedisRepositories
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Again, as with keyspaces, you can programmatically configure indexes, as shown in the following example:
|
||||
|
||||
.Programmatic Index setup
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[redis.repositories.indexes.geospatial]]
|
||||
== Geospatial Index
|
||||
|
||||
Assume the `Address` type contains a `location` property of type `Point` that holds the geo coordinates of the particular address.
|
||||
By annotating the property with `@GeoIndexed`, Spring Data Redis adds those values by using Redis `GEO` commands, as shown in the following example:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@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> Query method declaration on a nested property, using `Point` and `Distance`.
|
||||
<2> Query method declaration on a nested property, using `Circle` to search within.
|
||||
<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`
|
||||
====
|
||||
|
||||
In the preceding example the, longitude and latitude values are stored by using `GEOADD` that use the object's `id` as the member's name.
|
||||
The finder methods allow usage of `Circle` or `Point, Distance` combinations for querying those values.
|
||||
|
||||
NOTE: It is **not** possible to combine `near` and `within` with other criteria.
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
[[redis.repositories.keyspaces]]
|
||||
= Keyspaces
|
||||
|
||||
Keyspaces define prefixes used to create the actual key for the Redis Hash.
|
||||
By default, the prefix is set to `getClass().getName()`.
|
||||
You can alter this default by setting `@RedisHash` on the aggregate root level or by setting up a programmatic configuration.
|
||||
However, the annotated keyspace supersedes any other configuration.
|
||||
|
||||
The following example shows how to set the keyspace configuration with the `@EnableRedisRepositories` annotation:
|
||||
|
||||
.Keyspace Setup via `@EnableRedisRepositories`
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The following example shows how to programmatically set the keyspace:
|
||||
|
||||
.Programmatic Keyspace setup
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
@EnableRedisRepositories
|
||||
public class ApplicationConfig {
|
||||
|
||||
//... RedisConnectionFactory and RedisTemplate Bean definitions omitted
|
||||
|
||||
@Bean
|
||||
public RedisMappingContext keyValueMappingContext() {
|
||||
return new RedisMappingContext(
|
||||
new MappingConfiguration(new IndexConfiguration(), new MyKeyspaceConfiguration()));
|
||||
}
|
||||
|
||||
public static class MyKeyspaceConfiguration extends KeyspaceConfiguration {
|
||||
|
||||
@Override
|
||||
protected Iterable<KeyspaceSettings> initialConfiguration() {
|
||||
return Collections.singleton(new KeyspaceSettings(Person.class, "people"));
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
[[redis.repositories.mapping]]
|
||||
= Object-to-Hash Mapping
|
||||
|
||||
The Redis Repository support persists Objects to Hashes.
|
||||
This requires an Object-to-Hash conversion which is done by a `RedisConverter`.
|
||||
The default implementation uses `Converter` for mapping property values to and from Redis native `byte[]`.
|
||||
|
||||
Given the `Person` type from the previous sections, the default mapping looks like the following:
|
||||
|
||||
====
|
||||
[source,text]
|
||||
----
|
||||
_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> The `_class` attribute is included on the root level as well as on any nested interface or abstract types.
|
||||
<2> Simple property values are mapped by path.
|
||||
<3> Properties of complex types are mapped by their dot path.
|
||||
====
|
||||
|
||||
[[mapping-conversion]]
|
||||
== Data Mapping and Type Conversion
|
||||
|
||||
This section explains how types are mapped to and from a Hash representation:
|
||||
|
||||
[cols="1,2,3",options="header"]
|
||||
.Default Mapping Rules
|
||||
|===
|
||||
| Type
|
||||
| Sample
|
||||
| Mapped Value
|
||||
|
||||
| Simple Type +
|
||||
(for example, String)
|
||||
| String firstname = "rand";
|
||||
| firstname = "rand"
|
||||
|
||||
| Byte array (`byte[]`)
|
||||
| byte[] image = "rand".getBytes();
|
||||
| image = "rand"
|
||||
|
||||
| Complex Type +
|
||||
(for example, Address)
|
||||
| Address address = new Address("emond's field");
|
||||
| address.city = "emond's field"
|
||||
|
||||
| List +
|
||||
of Simple Type
|
||||
| List<String> nicknames = asList("dragon reborn", "lews therin");
|
||||
| nicknames.[0] = "dragon reborn", +
|
||||
nicknames.[1] = "lews therin"
|
||||
|
||||
| Map +
|
||||
of Simple Type
|
||||
| Map<String, String> atts = asMap({"eye-color", "grey"}, {"...
|
||||
| atts.[eye-color] = "grey", +
|
||||
atts.[hair-color] = "...
|
||||
|
||||
| List +
|
||||
of Complex Type
|
||||
| List<Address> addresses = asList(new Address("em...
|
||||
| addresses.[0].city = "emond's field", +
|
||||
addresses.[1].city = "...
|
||||
|
||||
| Map +
|
||||
of Complex Type
|
||||
| Map<String, Address> addresses = asMap({"home", new Address("em...
|
||||
| addresses.[home].city = "emond's field", +
|
||||
addresses.[work].city = "...
|
||||
|===
|
||||
|
||||
CAUTION: Due to the flat representation structure, Map keys need to be simple types, such as ``String`` or ``Number``.
|
||||
|
||||
Mapping behavior can be customized by registering the corresponding `Converter` in `RedisCustomConversions`.
|
||||
Those converters can take care of converting from and to a single `byte[]` as well as `Map<String,byte[]>`.
|
||||
The first one is suitable for (for example) converting a complex type to (for example) a binary JSON representation that still uses the default mappings hash structure.
|
||||
The second option offers full control over the resulting hash.
|
||||
|
||||
WARNING: Writing objects to a Redis hash deletes the content from the hash and re-creates the whole hash, so data that has not been mapped is lost.
|
||||
|
||||
The following example shows two sample byte array converters:
|
||||
|
||||
.Sample byte[] Converters
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@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);
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Using the preceding byte array `Converter` produces output similar to the following:
|
||||
|
||||
====
|
||||
[source,text]
|
||||
----
|
||||
_class = org.example.Person
|
||||
id = e2c7dcee-b8cd-4424-883e-736ce564363e
|
||||
firstname = rand
|
||||
lastname = al’thor
|
||||
address = { city : "emond's field", country : "andor" }
|
||||
----
|
||||
====
|
||||
|
||||
The following example shows two examples of `Map` converters:
|
||||
|
||||
.Sample Map<String,byte[]> Converters
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@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<Map<String, byte[]>, Address> {
|
||||
|
||||
@Override
|
||||
public Address convert(Map<String,byte[]> source) {
|
||||
return new Address(new String(source.get("ciudad")));
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Using the preceding Map `Converter` produces output similar to the following:
|
||||
|
||||
====
|
||||
[source,text]
|
||||
----
|
||||
_class = org.example.Person
|
||||
id = e2c7dcee-b8cd-4424-883e-736ce564363e
|
||||
firstname = rand
|
||||
lastname = al’thor
|
||||
ciudad = "emond's field"
|
||||
----
|
||||
====
|
||||
|
||||
NOTE: Custom conversions have no effect on index resolution. xref:redis/redis-repositories/indexes.adoc[Secondary Indexes] are still created, even for custom converted types.
|
||||
|
||||
[[customizing-type-mapping]]
|
||||
== Customizing Type Mapping
|
||||
|
||||
If you want to avoid writing the entire Java class name as type information and would rather like to use a key, you can use the `@TypeAlias` annotation on the entity class being persisted.
|
||||
If you need to customize the mapping even more, look at the https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/convert/TypeInformationMapper.html[`TypeInformationMapper`] interface.
|
||||
An instance of that interface can be configured at the `DefaultRedisTypeMapper`, which can be configured on `MappingRedisConverter`.
|
||||
|
||||
The following example shows how to define a type alias for an entity:
|
||||
|
||||
.Defining `@TypeAlias` for an entity
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@TypeAlias("pers")
|
||||
class Person {
|
||||
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The resulting document contains `pers` as the value in a `_class` field.
|
||||
|
||||
[[configuring-custom-type-mapping]]
|
||||
=== Configuring Custom Type Mapping
|
||||
|
||||
The following example demonstrates how to configure a custom `RedisTypeMapper` in `MappingRedisConverter`:
|
||||
|
||||
.Configuring a custom `RedisTypeMapper` via Spring Java Config
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
class CustomRedisTypeMapper extends DefaultRedisTypeMapper {
|
||||
//implement custom type mapping here
|
||||
}
|
||||
----
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@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();
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
[[redis.repositories.queries]]
|
||||
= Redis-specific Query Methods
|
||||
|
||||
Query methods allow automatic derivation of simple finder queries from the method name, as shown in the following example:
|
||||
|
||||
.Sample Repository finder Method
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public interface PersonRepository extends CrudRepository<Person, String> {
|
||||
|
||||
List<Person> findByFirstname(String firstname);
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
NOTE: Please make sure properties used in finder methods are set up for indexing.
|
||||
|
||||
NOTE: Query methods for Redis repositories support only queries for entities and collections of entities with paging.
|
||||
|
||||
Using derived query methods might not always be sufficient to model the queries to run. `RedisCallback` offers more control over the actual matching of index structures or even custom indexes.
|
||||
To do so, provide a `RedisCallback` that returns a single or `Iterable` set of `id` values, as shown in the following example:
|
||||
|
||||
.Sample finder using RedisCallback
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
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);
|
||||
----
|
||||
====
|
||||
|
||||
The following table provides an overview of the keywords supported for Redis and what a method containing that keyword essentially translates to:
|
||||
|
||||
====
|
||||
.Supported keywords inside method names
|
||||
[options = "header, autowidth"]
|
||||
|===============
|
||||
|Keyword|Sample|Redis snippet
|
||||
|`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.repositories.queries.sort]]
|
||||
== Sorting Query Method results
|
||||
|
||||
Redis repositories allow various approaches to define sorting order.
|
||||
Redis itself does not support in-flight sorting when retrieving hashes or sets.
|
||||
Therefore, Redis repository query methods construct a `Comparator` that is applied to the result before returning results as `List`.
|
||||
Let's take a look at the following example:
|
||||
|
||||
.Sorting Query Results
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
interface PersonRepository extends RedisRepository<Person, String> {
|
||||
|
||||
List<Person> findByFirstnameOrderByAgeDesc(String firstname); <1>
|
||||
|
||||
List<Person> findByFirstname(String firstname, Sort sort); <2>
|
||||
}
|
||||
----
|
||||
|
||||
<1> Static sorting derived from method name.
|
||||
<2> Dynamic sorting using a method argument.
|
||||
====
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
include::{commons}@data-commons::page$query-by-example.adoc[]
|
||||
|
||||
[[query-by-example.running]]
|
||||
== Running an Example
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
[[redis.repositories.usage]]
|
||||
= Usage
|
||||
|
||||
Spring Data Redis lets you easily implement domain entities, as shown in the following example:
|
||||
|
||||
.Sample Person Entity
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@RedisHash("people")
|
||||
public class Person {
|
||||
|
||||
@Id String id;
|
||||
String firstname;
|
||||
String lastname;
|
||||
Address address;
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
We have a pretty simple domain object here.
|
||||
Note that it has a `@RedisHash` annotation on its type and a property named `id` that is annotated with `org.springframework.data.annotation.Id`.
|
||||
Those two items are responsible for creating the actual key used to persist the hash.
|
||||
|
||||
NOTE: Properties annotated with `@Id` as well as those named `id` are considered as the identifier properties.
|
||||
Those with the annotation are favored over others.
|
||||
|
||||
To now actually have a component responsible for storage and retrieval, we need to define a repository interface, as shown in the following example:
|
||||
|
||||
.Basic Repository Interface To Persist Person Entities
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public interface PersonRepository extends CrudRepository<Person, String> {
|
||||
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
As our repository extends `CrudRepository`, it provides basic CRUD and finder operations.
|
||||
The thing we need in between to glue things together is the corresponding Spring configuration, shown in the following example:
|
||||
|
||||
.JavaConfig for Redis Repositories
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
@EnableRedisRepositories
|
||||
public class ApplicationConfig {
|
||||
|
||||
@Bean
|
||||
public RedisConnectionFactory connectionFactory() {
|
||||
return new LettuceConnectionFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||
|
||||
RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
|
||||
template.setConnectionFactory(redisConnectionFactory);
|
||||
return template;
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Given the preceding setup, we can inject `PersonRepository` into our components, as shown in the following example:
|
||||
|
||||
.Access to Person Entities
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@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> Generates a new `id` if the current value is `null` or reuses an already set `id` value and stores properties of type `Person` inside the Redis Hash with a key that has a pattern of `keyspace:id` -- in this case, it might be `people:5d67b7e1-8640-4475-beeb-c666fab4c0e5`.
|
||||
<2> Uses the provided `id` to retrieve the object stored at `keyspace:id`.
|
||||
<3> Counts the total number of entities available within the keyspace, `people`, defined by `@RedisHash` on `Person`.
|
||||
<4> Removes the key for the given object from Redis.
|
||||
====
|
||||
|
||||
[[redis.repositories.references]]
|
||||
== Persisting References
|
||||
|
||||
Marking properties with `@Reference` allows storing a simple key reference instead of copying values into the hash itself.
|
||||
On loading from Redis, references are resolved automatically and mapped back into the object, as shown in the following example:
|
||||
|
||||
.Sample Property Reference
|
||||
====
|
||||
[source,text]
|
||||
----
|
||||
_class = org.example.Person
|
||||
id = e2c7dcee-b8cd-4424-883e-736ce564363e
|
||||
firstname = rand
|
||||
lastname = al’thor
|
||||
mother = people:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56 <1>
|
||||
----
|
||||
|
||||
<1> Reference stores the whole key (`keyspace:id`) of the referenced object.
|
||||
====
|
||||
|
||||
WARNING: Referenced Objects are not persisted when the referencing object is saved.
|
||||
You must persist changes on referenced objects separately, since only the reference is stored.
|
||||
Indexes set on properties of referenced types are not resolved.
|
||||
|
||||
[[redis.repositories.partial-updates]]
|
||||
== Persisting Partial Updates
|
||||
|
||||
In some cases, you need not load and rewrite the entire entity just to set a new value within it.
|
||||
A session timestamp for the last active time might be such a scenario where you want to alter one property.
|
||||
`PartialUpdate` lets you define `set` and `delete` actions on existing objects while taking care of updating potential expiration times of both the entity itself and index structures.
|
||||
The following example shows a partial update:
|
||||
|
||||
.Sample Partial Update
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
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> Set the simple `firstname` property to `mat`.
|
||||
<2> Set the simple 'address.city' property to 'emond's field' without having to pass in the entire object.
|
||||
This does not work when a custom conversion is registered.
|
||||
<3> Remove the `age` property.
|
||||
<4> Set complex `address` property.
|
||||
<5> Set a map of values, which removes the previously existing map and replaces the values with the given ones.
|
||||
<6> Automatically update the server expiration time when altering xref:redis/redis-repositories/expirations.adoc[Time To Live].
|
||||
====
|
||||
|
||||
NOTE: Updating complex objects as well as map (or other collection) structures requires further interaction with Redis to determine existing values, which means that rewriting the entire entity might be faster.
|
||||
@@ -10,7 +10,7 @@ Redis Streams can be roughly divided into two areas of functionality:
|
||||
* Appending records
|
||||
* Consuming records
|
||||
|
||||
Although this pattern has similarities to <<pubsub, Pub/Sub>>, the main difference lies in the persistence of messages and how they are consumed.
|
||||
Although this pattern has similarities to xref:redis/pubsub.adoc[Pub/Sub], the main difference lies in the persistence of messages and how they are consumed.
|
||||
|
||||
While Pub/Sub relies on the broadcasting of transient messages (i.e. if you don't listen, you miss a message), Redis Stream use a persistent, append-only data type that retains messages until the stream is trimmed. Another difference in consumption is that Pub/Sub registers a server-side subscription. Redis pushes arriving messages to the client while Redis Streams require active polling.
|
||||
|
||||
@@ -46,7 +46,7 @@ At the low-level, `RedisConnection` offers the `xRead` and `xReadGroup` methods
|
||||
|
||||
NOTE: Subscription commands in Redis can be blocking. That is, calling `xRead` on a connection causes the current thread to block as it starts waiting for messages. The thread is released only if the read command times out or receives a message.
|
||||
|
||||
To consume stream messages, one can either poll for messages in application code, or use one of the two <<redis.streams.receive.containers>>, the imperative or the reactive one. Each time a new records arrives, the container notifies the application code.
|
||||
To consume stream messages, one can either poll for messages in application code, or use one of the two xref:redis/redis-streams.adoc#redis.streams.receive.containers[Asynchronous reception through Message Listener Containers], the imperative or the reactive one. Each time a new records arrives, the container notifies the application code.
|
||||
|
||||
[[redis.streams.receive.synchronous]]
|
||||
=== Synchronous reception
|
||||
@@ -81,6 +81,7 @@ Spring Data ships with two implementations tailored to the used programming mode
|
||||
Both containers allow runtime configuration changes so that you can add or remove subscriptions while an application is running without the need for a restart. Additionally, the container uses a lazy subscription approach, using a `RedisConnection` only when needed. If all the listeners are unsubscribed, it automatically performs a cleanup, and the thread is released.
|
||||
|
||||
|
||||
[[imperative-streammessagelistenercontainer]]
|
||||
==== Imperative `StreamMessageListenerContainer`
|
||||
|
||||
In a fashion similar to a Message-Driven Bean (MDB) in the EJB world, the Stream-Driven POJO (SDP) acts as a receiver for Stream messages. The one restriction on an SDP is that it must implement the `org.springframework.data.redis.stream.StreamListener` interface. Please also be aware that in the case where your POJO receives messages on multiple threads, it is important to ensure that your implementation is thread-safe.
|
||||
@@ -129,6 +130,7 @@ Subscription subscription = container.receive(StreamOffset.fromStart("my-stream"
|
||||
|
||||
Please refer to the Javadoc of the various message listener containers for a full description of the features supported by each implementation.
|
||||
|
||||
[[reactive-streamreceiver]]
|
||||
==== Reactive `StreamReceiver`
|
||||
|
||||
Reactive consumption of streaming data sources typically happens through a `Flux` of events or messages. The reactive receiver implementation is provided with `StreamReceiver` and its overloaded `receive(…)` messages. The reactive approach requires fewer infrastructure resources such as threads in comparison to `StreamMessageListenerContainer` as it is leveraging threading resources provided by the driver. The receiving stream is a demand-driven publisher of ``StreamMessage``:
|
||||
@@ -210,7 +212,7 @@ Reading from a specific message id and the last consumed message can be consider
|
||||
Using the latest message for read can skip messages that were added to the stream while the poll operation was in the state of dead time. Polling introduces a dead time in which messages can arrive between individual polling commands. Stream consumption is not a linear contiguous read but split into repeating `XREAD` calls.
|
||||
|
||||
[[redis.streams.receive.serialization]]
|
||||
=== Serialization
|
||||
== Serialization
|
||||
|
||||
Any Record sent to the stream needs to be serialized to its binary format. Due to the streams closeness to the hash data structure the stream key, field names and values use the according serializers configured on the `RedisTemplate`.
|
||||
|
||||
@@ -226,9 +228,10 @@ Any Record sent to the stream needs to be serialized to its binary format. Due t
|
||||
Please make sure to review ``RedisSerializer``s in use and note that if you decide to not use any serializer you need to make sure those values are binary already.
|
||||
|
||||
[[redis.streams.hashing]]
|
||||
=== Object Mapping
|
||||
== Object Mapping
|
||||
|
||||
==== Simple Values
|
||||
[[simple-values]]
|
||||
=== Simple Values
|
||||
|
||||
`StreamOperations` allows to append simple values, via `ObjectRecord`, directly to the stream without having to put those values into a `Map` structure.
|
||||
The value will then be assigned to an _payload_ field and can be extracted when reading back the value.
|
||||
@@ -251,19 +254,20 @@ List<ObjectRecord<String, String>> records = redisTemplate()
|
||||
|
||||
``ObjectRecord``s pass through the very same serialization process as the all other records, thus the Record can also obtained using the untyped read operation returning a `MapRecord`.
|
||||
|
||||
==== Complex Values
|
||||
[[complex-values]]
|
||||
=== Complex Values
|
||||
|
||||
Adding a complex value to the stream can be done in 3 ways:
|
||||
|
||||
* Convert to simple value using eg. a String JSON representation.
|
||||
* Convert to simple value using e. g. a String JSON representation.
|
||||
* Serialize the value with a suitable `RedisSerializer`.
|
||||
* Convert the value into a `Map` suitable for serialization using a `HashMapper`.
|
||||
* Convert the value into a `Map` suitable for serialization using a xref:redis/hash-mappers.adoc[`HashMapper`].
|
||||
|
||||
The first variant is the most straight forward one but neglects the field value capabilities offered by the stream structure, still the values in the stream will be readable for other consumers.
|
||||
The 2nd option holds the same benefits as the first one, but may lead to a very specific consumer limitations as the all consumers must implement the very same serialization mechanism.
|
||||
The `HashMapper` approach is the a bit more complex one making use of the steams hash structure, but flattening the source. Still other consumers remain able to read the records as long as suitable serializer combinations are chosen.
|
||||
|
||||
NOTE: HashMappers convert the payload to a `Map` with specific types. Make sure to use Hash-Key and Hash-Value serializers that are capable of (de-)serializing the hash.
|
||||
NOTE: xref:redis/hash-mappers.adoc[HashMappers] convert the payload to a `Map` with specific types. Make sure to use Hash-Key and Hash-Value serializers that are capable of (de-)serializing the hash.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -281,7 +285,7 @@ List<ObjectRecord<String, User>> records = redisTemplate()
|
||||
----
|
||||
<1> XADD user-logon * "_class" "com.example.User" "firstname" "night" "lastname" "angel"
|
||||
|
||||
`StreamOperations` use by default <<redis.repositories.mapping, ObjectHashMapper>>.
|
||||
`StreamOperations` use by default xref:redis/redis-repositories/mapping.adoc[ObjectHashMapper].
|
||||
You may provide a `HashMapper` suitable for your requirements when obtaining `StreamOperations`.
|
||||
|
||||
[source,java]
|
||||
@@ -1,5 +1,5 @@
|
||||
[[scripting]]
|
||||
= Redis Scripting
|
||||
= Scripting
|
||||
|
||||
Redis versions 2.6 and higher provide support for running Lua scripts through the https://redis.io/commands/eval[eval] and https://redis.io/commands/evalsha[evalsha] commands. Spring Data Redis provides a high-level abstraction for running scripts that handles serialization and automatically uses the Redis script cache.
|
||||
|
||||
@@ -19,19 +19,45 @@ public RedisScript<Boolean> script() {
|
||||
}
|
||||
----
|
||||
|
||||
[source,java]
|
||||
[tabs]
|
||||
======
|
||||
Imperative::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class Example {
|
||||
|
||||
@Autowired
|
||||
RedisOperations<String, String> redisOperations;
|
||||
|
||||
@Autowired
|
||||
RedisScript<Boolean> script;
|
||||
|
||||
public boolean checkAndSet(String expectedValue, String newValue) {
|
||||
return redisTemplate.execute(script, singletonList("key"), asList(expectedValue, newValue));
|
||||
return redisOperations.execute(script, singletonList("key"), asList(expectedValue, newValue));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Reactive::
|
||||
+
|
||||
[source,java,role="secondary"]
|
||||
----
|
||||
public class Example {
|
||||
|
||||
@Autowired
|
||||
ReactiveRedisOperations<String, String> redisOperations;
|
||||
|
||||
@Autowired
|
||||
RedisScript<Boolean> script;
|
||||
|
||||
public Flux<Boolean> checkAndSet(String expectedValue, String newValue) {
|
||||
return redisOperations.execute(script, singletonList("key"), asList(expectedValue, newValue));
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,lua]
|
||||
----
|
||||
-- checkandset.lua
|
||||
@@ -47,6 +73,6 @@ The preceding code configures a `RedisScript` pointing to a file called `checkan
|
||||
|
||||
TIP: It is ideal to configure a single instance of `DefaultRedisScript` in your application context to avoid re-calculation of the script's SHA1 on every script run.
|
||||
|
||||
The `checkAndSet` method above then runs the scripts. Scripts can be run within a `SessionCallback` as part of a transaction or pipeline. See "`<<tx>>`" and "`<<pipeline>>`" for more information.
|
||||
The `checkAndSet` method above then runs the scripts. Scripts can be run within a `SessionCallback` as part of a transaction or pipeline. See "`xref:redis/transactions.adoc[Redis Transactions]`" and "`xref:redis/pipelining.adoc[Pipelining]`" for more information.
|
||||
|
||||
The scripting support provided by Spring Data Redis also lets you schedule Redis scripts for periodic running by using the Spring Task and Scheduler abstractions. See the https://spring.io/projects/spring-framework/[Spring Framework] documentation for more details.
|
||||
@@ -0,0 +1,67 @@
|
||||
[[redis:support]]
|
||||
= Support Classes
|
||||
|
||||
Package `org.springframework.data.redis.support` offers various reusable components that rely on Redis as a backing store.
|
||||
Currently, the package contains various JDK-based interface implementations on top of Redis, such as https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/atomic/package-summary.html[atomic] counters and JDK https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collection.html[Collections].
|
||||
|
||||
NOTE: `RedisList` is forward-compatible with Java 21 `SequencedCollection`.
|
||||
|
||||
The atomic counters make it easy to wrap Redis key incrementation while the collections allow easy management of Redis keys with minimal storage exposure or API leakage.
|
||||
In particular, the `RedisSet` and `RedisZSet` interfaces offer easy access to the set operations supported by Redis, such as `intersection` and `union`. `RedisList` implements the `List`, `Queue`, and `Deque` contracts (and their equivalent blocking siblings) on top of Redis, exposing the storage as a FIFO (First-In-First-Out), LIFO (Last-In-First-Out) or capped collection with minimal configuration.
|
||||
The following example shows the configuration for a bean that uses a `RedisList`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
class MyConfig {
|
||||
|
||||
// …
|
||||
|
||||
@Bean
|
||||
RedisList<String> stringRedisTemplate(RedisTemplate<String, String> redisTemplate) {
|
||||
return new DefaultRedisList<>(template, "queue-key");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<?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 https://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>
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows a Java configuration example for a `Deque`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class AnotherExample {
|
||||
|
||||
// injected
|
||||
private Deque<String> queue;
|
||||
|
||||
public void addTag(String tag) {
|
||||
queue.push(tag);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
As shown in the preceding example, the consuming code is decoupled from the actual storage implementation.
|
||||
In fact, there is no indication that Redis is used underneath.
|
||||
This makes moving from development to production environments transparent and highly increases testability (the Redis implementation can be replaced with an in-memory one).
|
||||
386
src/main/antora/modules/ROOT/pages/redis/template.adoc
Normal file
386
src/main/antora/modules/ROOT/pages/redis/template.adoc
Normal file
@@ -0,0 +1,386 @@
|
||||
[[redis:template]]
|
||||
= Working with Objects through `RedisTemplate`
|
||||
|
||||
Most users are likely to use `RedisTemplate` and its corresponding package, `org.springframework.data.redis.core` or its reactive variant `ReactiveRedisTemplate`.
|
||||
The template is, in fact, the central class of the Redis module, due to its rich feature set.
|
||||
The template offers a high-level abstraction for Redis interactions.
|
||||
While `[Reactive]RedisConnection` offers low-level methods that accept and return binary values (`byte` arrays), the template takes care of serialization and connection management, freeing the user from dealing with such details.
|
||||
|
||||
The `RedisTemplate` class implements the `RedisOperations` interface and its reactive variant `ReactiveRedisTemplate` implements `ReactiveRedisOperations`.
|
||||
|
||||
NOTE: The preferred way to reference operations on a `[Reactive]RedisTemplate` instance is through the
|
||||
`[Reactive]RedisOperations` interface.
|
||||
|
||||
Moreover, the template provides operations views (following the grouping from the Redis command https://redis.io/commands[reference]) that offer rich, generified interfaces for working against a certain type or certain key (through the `KeyBound` interfaces) as described in the following table:
|
||||
|
||||
.Operational views
|
||||
[%collapsible]
|
||||
=======
|
||||
[tabs]
|
||||
======
|
||||
Imperative::
|
||||
+
|
||||
[width="80%",cols="<1,<2",options="header",role="primary"]
|
||||
|====
|
||||
|Interface
|
||||
|Description
|
||||
|
||||
2+^|_Key Type Operations_
|
||||
|
||||
|`GeoOperations`
|
||||
|Redis geospatial operations, such as `GEOADD`, `GEORADIUS`,...
|
||||
|
||||
|`HashOperations`
|
||||
|Redis hash operations
|
||||
|
||||
|`HyperLogLogOperations`
|
||||
|Redis HyperLogLog operations, such as `PFADD`, `PFCOUNT`,...
|
||||
|
||||
|`ListOperations`
|
||||
|Redis list operations
|
||||
|
||||
|`SetOperations`
|
||||
|Redis set operations
|
||||
|
||||
|`ValueOperations`
|
||||
|Redis string (or value) operations
|
||||
|
||||
|`ZSetOperations`
|
||||
|Redis zset (or sorted set) operations
|
||||
|
||||
2+^|_Key Bound Operations_
|
||||
|
||||
|`BoundGeoOperations`
|
||||
|Redis key bound geospatial operations
|
||||
|
||||
|`BoundHashOperations`
|
||||
|Redis hash key bound operations
|
||||
|
||||
|`BoundKeyOperations`
|
||||
|Redis key bound operations
|
||||
|
||||
|`BoundListOperations`
|
||||
|Redis list key bound operations
|
||||
|
||||
|`BoundSetOperations`
|
||||
|Redis set key bound operations
|
||||
|
||||
|`BoundValueOperations`
|
||||
|Redis string (or value) key bound operations
|
||||
|
||||
|`BoundZSetOperations`
|
||||
|Redis zset (or sorted set) key bound operations
|
||||
|
||||
|====
|
||||
|
||||
Reactive::
|
||||
+
|
||||
[width="80%",cols="<1,<2",options="header",role="secondary"]
|
||||
|====
|
||||
|Interface
|
||||
|Description
|
||||
|
||||
2+^|_Key Type Operations_
|
||||
|
||||
|`ReactiveGeoOperations`
|
||||
|Redis geospatial operations such as `GEOADD`, `GEORADIUS`, and others)
|
||||
|
||||
|`ReactiveHashOperations`
|
||||
|Redis hash operations
|
||||
|
||||
|`ReactiveHyperLogLogOperations`
|
||||
|Redis HyperLogLog operations such as (`PFADD`, `PFCOUNT`, and others)
|
||||
|
||||
|`ReactiveListOperations`
|
||||
|Redis list operations
|
||||
|
||||
|`ReactiveSetOperations`
|
||||
|Redis set operations
|
||||
|
||||
|`ReactiveValueOperations`
|
||||
|Redis string (or value) operations
|
||||
|
||||
|`ReactiveZSetOperations`
|
||||
|Redis zset (or sorted set) operations
|
||||
|====
|
||||
======
|
||||
=======
|
||||
|
||||
Once configured, the template is thread-safe and can be reused across multiple instances.
|
||||
|
||||
`RedisTemplate` uses a Java-based serializer for most of its operations.
|
||||
This means that any object written or read by the template is serialized and deserialized through Java.
|
||||
|
||||
You can change the serialization mechanism on the template, and the Redis module offers several implementations, which are available in the `org.springframework.data.redis.serializer` package.
|
||||
See <<redis:serializer,Serializers>> for more information.
|
||||
You can also set any of the serializers to null and use RedisTemplate with raw byte arrays by setting the `enableDefaultSerializer` property to `false`.
|
||||
Note that the template requires all keys to be non-null.
|
||||
However, values can be null as long as the underlying serializer accepts them.
|
||||
Read the Javadoc of each serializer for more information.
|
||||
|
||||
For cases where you need a certain template view, declare the view as a dependency and inject the template.
|
||||
The container automatically performs the conversion, eliminating the `opsFor[X]` calls, as shown in the following example:
|
||||
|
||||
.Configuring Template API
|
||||
[tabs]
|
||||
======
|
||||
Java Imperative::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
class MyConfig {
|
||||
|
||||
@Bean
|
||||
LettuceConnectionFactory connectionFactory() {
|
||||
return new LettuceConnectionFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
RedisTemplate<String, String> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
|
||||
RedisTemplate<String, String> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(redisConnectionFactory);
|
||||
return template;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Java Reactive::
|
||||
+
|
||||
[source,java,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class MyConfig {
|
||||
|
||||
@Bean
|
||||
LettuceConnectionFactory connectionFactory() {
|
||||
return new LettuceConnectionFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveRedisTemplate<String, String> ReactiveRedisTemplate(ReactoveRedisConnectionFactory connectionFactory) {
|
||||
return new ReactiveRedisTemplate<>(connectionFactory, RedisSerializationContext.string());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="tertiary"]
|
||||
----
|
||||
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>
|
||||
<!-- redis template definition -->
|
||||
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="redisConnectionFactory"/>
|
||||
...
|
||||
|
||||
</beans>
|
||||
----
|
||||
======
|
||||
|
||||
.Pushing an item to a List using `[Reactive]RedisTemplate`
|
||||
[tabs]
|
||||
======
|
||||
Imperative::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class Example {
|
||||
|
||||
// inject the actual operations
|
||||
@Autowired
|
||||
private RedisOperations<String, String> operations;
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Reactive::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
public class Example {
|
||||
|
||||
// inject the actual template
|
||||
@Autowired
|
||||
private ReactiveRedisOperations<String, String> operations;
|
||||
|
||||
public Mono<Long> addLink(String userId, URL url) {
|
||||
return operations.opsForList().leftPush(userId, url.toExternalForm());
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[redis:string]]
|
||||
== String-focused Convenience Classes
|
||||
|
||||
Since it is quite common for the keys and values stored in Redis to be `java.lang.String`, the Redis modules provides two extensions to `RedisConnection` and `RedisTemplate`, respectively the `StringRedisConnection` (and its `DefaultStringRedisConnection` implementation) and `StringRedisTemplate` as a convenient one-stop solution for intensive String operations.
|
||||
In addition to being bound to `String` keys, the template and the connection use the `StringRedisSerializer` underneath, which means the stored keys and values are human-readable (assuming the same encoding is used both in Redis and your code).
|
||||
The following listings show an example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java Imperative::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
class RedisConfiguration {
|
||||
|
||||
@Bean
|
||||
LettuceConnectionFactory redisConnectionFactory() {
|
||||
return new LettuceConnectionFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||
|
||||
StringRedisTemplate template = new StringRedisTemplate();
|
||||
template.setConnectionFactory(redisConnectionFactory);
|
||||
return template;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Java Reactive::
|
||||
+
|
||||
[source,java,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class RedisConfiguration {
|
||||
|
||||
@Bean
|
||||
LettuceConnectionFactory redisConnectionFactory() {
|
||||
return new LettuceConnectionFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveStringRedisTemplate reactiveRedisTemplate(ReactiveRedisConnectionFactory factory) {
|
||||
return new ReactiveStringRedisTemplate<>(factory);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="tertiary"]
|
||||
----
|
||||
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>
|
||||
|
||||
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" p:connection-factory-ref="redisConnectionFactory"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Imperative::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class Example {
|
||||
|
||||
@Autowired
|
||||
private StringRedisTemplate redisTemplate;
|
||||
|
||||
public void addLink(String userId, URL url) {
|
||||
redisTemplate.opsForList().leftPush(userId, url.toExternalForm());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Reactive::
|
||||
+
|
||||
[source,java,role="secondary"]
|
||||
----
|
||||
public class Example {
|
||||
|
||||
@Autowired
|
||||
private ReactiveStringRedisTemplate redisTemplate;
|
||||
|
||||
public Mono<Long> addLink(String userId, URL url) {
|
||||
return redisTemplate.opsForList().leftPush(userId, url.toExternalForm());
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
As with the other Spring templates, `RedisTemplate` and `StringRedisTemplate` let you talk directly to Redis through the `RedisCallback` interface.
|
||||
This feature gives complete control to you, as it talks directly to the `RedisConnection`.
|
||||
Note that the callback receives an instance of `StringRedisConnection` when a `StringRedisTemplate` is used.
|
||||
The following example shows how to use the `RedisCallback` interface:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public void useCallback() {
|
||||
|
||||
redisOperations.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:serializer]]
|
||||
== Serializers
|
||||
|
||||
From the framework perspective, the data stored in Redis is only bytes.
|
||||
While Redis itself supports various types, for the most part, these refer to the way the data is stored rather than what it represents.
|
||||
It is up to the user to decide whether the information gets translated into strings or any other objects.
|
||||
|
||||
In Spring Data, the conversion between the user (custom) types and raw data (and vice-versa) is handled by Spring Data Redis in the `org.springframework.data.redis.serializer` package.
|
||||
|
||||
This package contains two types of serializers that, as the name implies, take care of the serialization process:
|
||||
|
||||
* Two-way serializers based on ``RedisSerializer``.
|
||||
* Element readers and writers that use `RedisElementReader` and ``RedisElementWriter``.
|
||||
|
||||
The main difference between these variants is that `RedisSerializer` primarily serializes to `byte[]` while readers and writers use `ByteBuffer`.
|
||||
|
||||
Multiple implementations are available (including two that have been already mentioned in this documentation):
|
||||
|
||||
* `JdkSerializationRedisSerializer`, which is used by default for `RedisCache` and `RedisTemplate`.
|
||||
* the `StringRedisSerializer`.
|
||||
|
||||
However, one can use `OxmSerializer` for Object/XML mapping through Spring {spring-framework-docs}/data-access.html#oxm[OXM] support or `Jackson2JsonRedisSerializer` or `GenericJackson2JsonRedisSerializer` for storing data in https://en.wikipedia.org/wiki/JSON[JSON] format.
|
||||
|
||||
Do note that the storage format is not limited only to values.
|
||||
It can be used for keys, values, or hashes without any restrictions.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
By default, `RedisCache` and `RedisTemplate` are configured to use Java native serialization.
|
||||
Java native serialization is known for allowing the running of remote code caused by payloads that exploit vulnerable libraries and classes injecting unverified bytecode.
|
||||
Manipulated input could lead to unwanted code being run in the application during the deserialization step.
|
||||
As a consequence, do not use serialization in untrusted environments.
|
||||
In general, we strongly recommend any other message format (such as JSON) instead.
|
||||
|
||||
If you are concerned about security vulnerabilities due to Java serialization, consider the general-purpose serialization filter mechanism at the core JVM level:
|
||||
|
||||
* https://docs.oracle.com/en/java/javase/17/core/serialization-filtering1.html[Filter Incoming Serialization Data].
|
||||
* https://openjdk.org/jeps/290[JEP 290].
|
||||
* https://owasp.org/www-community/vulnerabilities/Deserialization_of_untrusted_data[OWASP: Deserialization of untrusted data].
|
||||
====
|
||||
@@ -10,7 +10,7 @@ Spring Data Redis provides the `SessionCallback` interface for use when multiple
|
||||
[source,java]
|
||||
----
|
||||
//execute a transaction
|
||||
List<Object> txResults = redisTemplate.execute(new SessionCallback<List<Object>>() {
|
||||
List<Object> txResults = redisOperations.execute(new SessionCallback<List<Object>>() {
|
||||
public List<Object> execute(RedisOperations operations) throws DataAccessException {
|
||||
operations.multi();
|
||||
operations.opsForSet().add("key", "value1");
|
||||
@@ -26,7 +26,7 @@ System.out.println("Number of items added to set: " + txResults.get(0));
|
||||
There is an additional `exec` method that lets you pass a custom serializer for transaction results.
|
||||
|
||||
[[tx.spring]]
|
||||
== @Transactional Support
|
||||
== `@Transactional` Support
|
||||
|
||||
By default, `RedisTemplate` does not participate in managed Spring transactions.
|
||||
If you want `RedisTemplate` to make use of Redis transaction when using `@Transactional` or `TransactionTemplate`, you need to be explicitly enable transaction support for each `RedisTemplate` by setting `setEnableTransactionSupport(true)`.
|
||||
@@ -73,7 +73,7 @@ public class RedisTxContextConfiguration {
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Configures a Spring Context to enable {spring-framework-reference}/data-access.html#transaction-declarative[declarative transaction management].
|
||||
<1> Configures a Spring Context to enable {spring-framework-docs}/data-access.html#transaction-declarative[declarative transaction management].
|
||||
<2> Configures `RedisTemplate` to participate in transactions by binding connections to the current thread.
|
||||
<3> Transaction management requires a `PlatformTransactionManager`.
|
||||
Spring Data Redis does not ship with a `PlatformTransactionManager` implementation.
|
||||
15
src/main/antora/modules/ROOT/pages/repositories.adoc
Normal file
15
src/main/antora/modules/ROOT/pages/repositories.adoc
Normal file
@@ -0,0 +1,15 @@
|
||||
[[redis.repositories]]
|
||||
= Redis Repositories
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
This chapter explains the basic foundations of Spring Data repositories and Redis specifics.
|
||||
Before continuing to the Redis specifics, make sure you have a sound understanding of the basic concepts.
|
||||
|
||||
The goal of the Spring Data repository abstraction is to significantly reduce the amount of boilerplate code required to implement data access layers for various persistence stores.
|
||||
|
||||
Working with Redis Repositories lets you seamlessly convert and store domain objects in Redis Hashes, apply custom mapping strategies, and use secondary indexes.
|
||||
|
||||
IMPORTANT: Redis Repositories require at least Redis Server version 2.8.0 and do not work with transactions.
|
||||
Make sure to use a `RedisTemplate` with xref:redis/transactions.adoc#tx.spring[disabled transaction support].
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
include::{commons}@data-commons::page$repositories/core-concepts.adoc[]
|
||||
|
||||
[[redis.entity-persistence.state-detection-strategies]]
|
||||
include::{commons}@data-commons::page$is-new-state-detection.adoc[leveloffset=+1]
|
||||
@@ -0,0 +1 @@
|
||||
include::{commons}@data-commons::page$repositories/core-domain-events.adoc[]
|
||||
@@ -0,0 +1,4 @@
|
||||
[[core.extensions.querydsl]]
|
||||
= Querydsl
|
||||
|
||||
Spring Data Redis does not support Querydsl.
|
||||
@@ -0,0 +1 @@
|
||||
include::{commons}@data-commons::page$repositories/create-instances.adoc[]
|
||||
@@ -0,0 +1 @@
|
||||
include::{commons}@data-commons::page$repositories/custom-implementations.adoc[]
|
||||
@@ -0,0 +1 @@
|
||||
include::{commons}@data-commons::page$repositories/definition.adoc[]
|
||||
@@ -0,0 +1 @@
|
||||
include::{commons}@data-commons::page$repositories/null-handling.adoc[]
|
||||
@@ -0,0 +1 @@
|
||||
include::{commons}@data-commons::page$object-mapping.adoc[]
|
||||
@@ -0,0 +1,4 @@
|
||||
[[cassandra.projections]]
|
||||
= Projections
|
||||
|
||||
include::{commons}@data-commons::page$repositories/projections.adoc[leveloffset=+1]
|
||||
@@ -0,0 +1 @@
|
||||
include::{commons}@data-commons::page$repositories/query-keywords-reference.adoc[]
|
||||
@@ -0,0 +1 @@
|
||||
include::{commons}@data-commons::page$repositories/query-methods-details.adoc[]
|
||||
@@ -0,0 +1 @@
|
||||
include::{commons}@data-commons::page$repositories/query-return-types-reference.adoc[]
|
||||
@@ -1,5 +1,4 @@
|
||||
[[redis.upgrading]]
|
||||
[appendix]
|
||||
= Migration Guides
|
||||
|
||||
This section contains details about migration steps, deprecations, and removals.
|
||||
@@ -45,6 +44,7 @@ This section contains details about migration steps, deprecations, and removals.
|
||||
|
||||
|===
|
||||
|
||||
[[changed-methods-and-types]]
|
||||
=== Changed Methods and Types
|
||||
|
||||
.Core
|
||||
22
src/main/antora/resources/antora-resources/antora.yml
Normal file
22
src/main/antora/resources/antora-resources/antora.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
version: ${antora-component.version}
|
||||
prerelease: ${antora-component.prerelease}
|
||||
|
||||
asciidoc:
|
||||
attributes:
|
||||
version: ${project.version}
|
||||
springversionshort: ${spring.short}
|
||||
springversion: ${spring}
|
||||
attribute-missing: 'warn'
|
||||
commons: ${springdata.commons.docs}
|
||||
lettuce: ${lettuce}
|
||||
jedis: ${jedis}
|
||||
include-xml-namespaces: false
|
||||
spring-data-commons-docs-url: https://docs.spring.io/spring-data-commons/reference
|
||||
spring-data-commons-javadoc-base: https://docs.spring.io/spring-data/commons/docs/${springdata.commons}/api/
|
||||
springdocsurl: https://docs.spring.io/spring-framework/reference/{springversionshort}
|
||||
springjavadocurl: https://docs.spring.io/spring-framework/docs/${spring}/javadoc-api
|
||||
spring-framework-docs: '{springdocsurl}'
|
||||
spring-framework-javadoc: '{springjavadocurl}'
|
||||
springhateoasversion: ${spring-hateoas}
|
||||
releasetrainversion: ${releasetrain}
|
||||
store: Redis
|
||||
@@ -1,5 +0,0 @@
|
||||
[[appendix:schema]]
|
||||
[appendix]
|
||||
= Schema
|
||||
|
||||
link:https://www.springframework.org/schema/redis/spring-redis-1.0.xsd[Spring Data Redis Schema (redis-namespace)]
|
||||
@@ -1,8 +0,0 @@
|
||||
[[appendix]]
|
||||
= Appendix Document Structure
|
||||
|
||||
The appendix contains various additional detail that complements the information in the rest of the reference documentation:
|
||||
|
||||
* "`<<appendix:schema>>`" defines the schemas provided by Spring Data Redis.
|
||||
|
||||
* "`<<appendix:command-reference>>`" details which commands are supported by `RedisTemplate`.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 8.7 KiB |
@@ -1,41 +0,0 @@
|
||||
= Spring Data Redis
|
||||
Costin Leau, Jennifer Hickey, Christoph Strobl, Thomas Darimont, Mark Paluch, Jay Bryant
|
||||
:revnumber: {version}
|
||||
:revdate: {localdate}
|
||||
ifdef::backend-epub3[:front-cover-image: image:epub-cover.png[Front Cover,1050,1600]]
|
||||
:spring-data-commons-docs: ../../../../spring-data-commons/src/main/asciidoc
|
||||
:spring-framework-javadoc: https://docs.spring.io/spring-framework/docs/{springVersion}/javadoc-api
|
||||
:spring-framework-reference: link:{springDocsUrl}
|
||||
:store: Redis
|
||||
|
||||
(C) 2011-2023 The original authors.
|
||||
|
||||
NOTE: Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
|
||||
|
||||
toc::[]
|
||||
|
||||
include::preface.adoc[]
|
||||
|
||||
include::{spring-data-commons-docs}/dependencies.adoc[leveloffset=+1]
|
||||
|
||||
[[reference]]
|
||||
= Reference Documentation
|
||||
|
||||
include::reference/introduction.adoc[leveloffset=+1]
|
||||
include::reference/upgrade.adoc[leveloffset=+1]
|
||||
include::reference/why-sdr.adoc[leveloffset=+1]
|
||||
include::reference/redis.adoc[leveloffset=+1]
|
||||
include::reference/observability.adoc[leveloffset=+1]
|
||||
include::reference/reactive-redis.adoc[leveloffset=+1]
|
||||
include::reference/redis-cluster.adoc[leveloffset=+1]
|
||||
include::reference/redis-repositories.adoc[leveloffset=+1]
|
||||
|
||||
[[appendixes]]
|
||||
= Appendixes
|
||||
|
||||
:numbered!:
|
||||
|
||||
include::appendix/introduction.adoc[leveloffset=+1]
|
||||
include::appendix/appendix-schema.adoc[leveloffset=+1]
|
||||
include::appendix/appendix-command-reference.adoc[leveloffset=+1]
|
||||
include::appendix/upgrading.adoc[leveloffset=+1]
|
||||
@@ -1,6 +0,0 @@
|
||||
[[observability-conventions]]
|
||||
=== Observability - Conventions
|
||||
|
||||
Below you can find a list of all `GlobalObservabilityConventions` and `ObservabilityConventions` declared by this project.
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
[[observability-metrics]]
|
||||
=== Observability - Metrics
|
||||
|
||||
Below you can find a list of all metrics declared by this project.
|
||||
|
||||
[[observability-metrics-redis-command-observation]]
|
||||
==== Redis Command Observation
|
||||
|
||||
____
|
||||
Timer created around a Redis command execution.
|
||||
____
|
||||
|
||||
**Metric name** `spring.data.redis`. **Type** `timer` and **base unit** `seconds`.
|
||||
|
||||
Fully qualified name of the enclosing class `org.springframework.data.redis.connection.lettuce.observability.RedisObservation`.
|
||||
|
||||
|
||||
|
||||
.Low cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`db.operation`|Redis command value.
|
||||
|`db.redis.database_index`|Redis database index.
|
||||
|`db.system`|Database system.
|
||||
|`db.user`|Redis user.
|
||||
|`net.peer.name`|Name of the database host.
|
||||
|`net.peer.port`|Logical remote port number.
|
||||
|`net.sock.peer.addr`|Mongo peer address.
|
||||
|`net.sock.peer.port`|Mongo peer port.
|
||||
|`net.transport`|Network transport.
|
||||
|===
|
||||
|
||||
.High cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`db.statement`|Redis statement.
|
||||
|`spring.data.redis.command.error`|Redis error response.
|
||||
|===
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
[[observability-spans]]
|
||||
=== Observability - Spans
|
||||
|
||||
Below you can find a list of all spans declared by this project.
|
||||
|
||||
[[observability-spans-redis-command-observation]]
|
||||
==== Redis Command Observation Span
|
||||
|
||||
> Timer created around a Redis command execution.
|
||||
|
||||
**Span name** `spring.data.redis`.
|
||||
|
||||
Fully qualified name of the enclosing class `org.springframework.data.redis.connection.lettuce.observability.RedisObservation`.
|
||||
|
||||
|
||||
|
||||
.Tag Keys
|
||||
|===
|
||||
|Name | Description
|
||||
|`db.operation`|Redis command value.
|
||||
|`db.redis.database_index`|Redis database index.
|
||||
|`db.statement`|Redis statement.
|
||||
|`db.system`|Database system.
|
||||
|`db.user`|Redis user.
|
||||
|`net.peer.name`|Name of the database host.
|
||||
|`net.peer.port`|Logical remote port number.
|
||||
|`net.sock.peer.addr`|Mongo peer address.
|
||||
|`net.sock.peer.port`|Mongo peer port.
|
||||
|`net.transport`|Network transport.
|
||||
|`spring.data.redis.command.error`|Redis error response.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
[[introduction]]
|
||||
= Introduction
|
||||
|
||||
[[introduction.structure]]
|
||||
== Document Structure
|
||||
|
||||
This part of the reference documentation explains the core functionality offered by Spring Data Redis.
|
||||
It explains Key-Value module concepts and semantics and the syntax for various stores namespaces.
|
||||
For an introduction to key-value stores, Spring, or Spring Data examples, see <<get-started:first-steps:nosql>>.
|
||||
This documentation refers only to Spring Data Redis Support and assumes the user is familiar with key-value storage and Spring concepts.
|
||||
|
||||
"`<<redis>>`" introduces the Redis module feature set.
|
||||
|
||||
"`<<redis.repositories>>`" introduces the repository support for Redis.
|
||||
|
||||
This document is the reference guide for Spring Data Redis (SDR) Support.
|
||||
@@ -1,40 +0,0 @@
|
||||
[[redis.observability]]
|
||||
== Observability
|
||||
|
||||
Getting insights from an application component about its operations, timing and relation to application code is crucial to understand latency.
|
||||
Spring Data Redis ships with a Micrometer integration through the Lettuce driver to collect observations during Redis interaction.
|
||||
Once the integration is set up, Micrometer will create meters and spans (for distributed tracing) for each Redis command.
|
||||
|
||||
To enable the integration, apply the following configuration to `LettuceClientConfiguration`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
class ObservabilityConfiguration {
|
||||
|
||||
@Bean
|
||||
public ClientResources clientResources(ObservationRegistry observationRegistry) {
|
||||
|
||||
return ClientResources.builder()
|
||||
.tracing(new MicrometerTracingAdapter(observationRegistry, "my-redis-cache"))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LettuceConnectionFactory lettuceConnectionFactory(ClientResources clientResources) {
|
||||
|
||||
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
|
||||
.clientResources(clientResources).build();
|
||||
RedisConfiguration redisConfiguration = …;
|
||||
return new LettuceConnectionFactory(redisConfiguration, clientConfig);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
include::../observability/_conventions.adoc[]
|
||||
|
||||
include::../observability/_metrics.adoc[]
|
||||
|
||||
include::../observability/_spans.adoc[]
|
||||
|
||||
See also https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/database/#redis[OpenTelemetry Semantic Conventions] for further reference.
|
||||
@@ -1,88 +0,0 @@
|
||||
[[redis:reactive:pubsub]]
|
||||
= Redis Messaging/PubSub
|
||||
|
||||
Spring Data provides dedicated messaging integration for Redis, very similar in functionality and naming to the JMS integration in Spring Framework; in fact, users familiar with the JMS support in Spring should feel right at home.
|
||||
|
||||
Redis messaging can be roughly divided into two areas of functionality, namely the production or publication and consumption or subscription of messages, hence the shortcut pubsub (Publish/Subscribe). The `ReactiveRedisTemplate` class is used for message production. For asynchronous reception, Spring Data provides a dedicated message listener container that is used consume a stream of messages.
|
||||
For the purpose of just subscribing `ReactiveRedisTemplate` offers stripped down alternatives to utilizing a listener container.
|
||||
|
||||
The package `org.springframework.data.redis.connection` and `org.springframework.data.redis.listener` provide the core functionality for using Redis messaging.
|
||||
|
||||
[[redis:reactive:pubsub:publish]]
|
||||
== Sending/Publishing messages
|
||||
|
||||
To publish a message, one can use, as with the other operations, either the low-level `ReactiveRedisConnection` or the high-level `ReactiveRedisTemplate`. Both entities offer a publish method that accepts as an argument the message that needs to be sent as well as the destination channel. While `ReactiveRedisConnection` requires raw-data, the `ReactiveRedisTemplate` allow arbitrary objects to be passed in as messages:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
// 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");
|
||||
----
|
||||
|
||||
[[redis:reactive:pubsub:subscribe]]
|
||||
== Receiving/Subscribing for messages
|
||||
|
||||
On the receiving side, one can subscribe to one or multiple channels either by naming them directly or by using pattern matching. The latter approach is quite useful as it not only allows multiple subscriptions to be created with one command but to also listen on channels not yet created at subscription time (as long as they match the pattern).
|
||||
|
||||
At the low-level, `ReactiveRedisConnection` offers `subscribe` and `pSubscribe` methods that map the Redis commands for subscribing by channel respectively by pattern. Note that multiple channels or patterns can be used as arguments. To change a subscription, simply query the channels and patterns of `ReactiveSubscription`.
|
||||
|
||||
NOTE: Reactive subscription commands in Spring Data Redis are non-blocking and may end without emitting an element.
|
||||
|
||||
As mentioned above, once subscribed a connection starts waiting for messages. No other commands can be invoked on it except for adding new subscriptions or modifying/canceling the existing ones. Commands other than `subscribe`, `pSubscribe`, `unsubscribe`, or `pUnsubscribe` are illegal and will cause an exception.
|
||||
|
||||
In order to receive messages, one needs to obtain the message stream. Note that a subscription only publishes messages for channels and patterns that are registered with that particular subscription. The message stream itself is a hot sequence that produces elements without regard to demand. Make sure to register sufficient demand to not exhaust the message buffer.
|
||||
|
||||
[[redis:reactive:pubsub:subscribe:containers]]
|
||||
=== Message Listener Containers
|
||||
|
||||
Spring Data offers `ReactiveRedisMessageListenerContainer` which does all the heavy lifting of conversion and subscription state management on behalf of the user.
|
||||
|
||||
`ReactiveRedisMessageListenerContainer` acts as a message listener container. It is used to receive messages from a Redis channel and expose a stream of messages that emits channel messages with deserialization applied. It takes care of registering to receive messages, resource acquisition and release, exception conversion and the like. This allows you as an application developer to write the (possibly complex) business logic associated with receiving a message (and reacting to it), and delegates boilerplate Redis infrastructure concerns to the framework. Message streams register a subscription in Redis upon publisher subscription and unregister if the subscription gets canceled.
|
||||
|
||||
Furthermore, to minimize the application footprint, `ReactiveRedisMessageListenerContainer` allows one connection and one thread to be shared by multiple listeners even though they do not share a subscription. Thus no matter how many listeners or channels an application tracks, the runtime cost will remain the same through out its lifetime. Moreover, the container allows runtime configuration changes so one can add or remove listeners while an application is running without the need for restart. Additionally, the container uses a lazy subscription approach, using a `ReactiveRedisConnection` only when needed - if all the listeners are unsubscribed, cleanup is automatically performed.
|
||||
|
||||
The message listener container itself does not require external threading resources. It uses the driver threads to publish messages.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
ReactiveRedisConnectionFactory factory = …
|
||||
ReactiveRedisMessageListenerContainer container = new ReactiveRedisMessageListenerContainer(factory);
|
||||
|
||||
Flux<ChannelMessage<String, String>> stream = container.receive(ChannelTopic.of("my-channel"));
|
||||
----
|
||||
|
||||
To await and ensure proper subscription, you can use the `receiveLater` method that returns a `Mono<Flux<ChannelMessage>>`.
|
||||
The resulting `Mono` completes with an inner publisher as a result of completing the subscription to the given topics. By intercepting `onNext` signals, you can synchronize server-side subscriptions.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
ReactiveRedisConnectionFactory factory = …
|
||||
ReactiveRedisMessageListenerContainer container = new ReactiveRedisMessageListenerContainer(factory);
|
||||
|
||||
Mono<Flux<ChannelMessage<String, String>>> stream = container.receiveLater(ChannelTopic.of("my-channel"));
|
||||
|
||||
stream.doOnNext(inner -> // notification hook when Redis subscriptions are synchronized with the server)
|
||||
.flatMapMany(Function.identity())
|
||||
.…;
|
||||
----
|
||||
|
||||
[[redis:reactive:pubsub:subscribe:template]]
|
||||
=== Subscribing via template API
|
||||
|
||||
As mentioned above you can directly use `ReactiveRedisTemplate` to subscribe to channels / patterns. This approach
|
||||
offers a straight forward, though limited solution as you lose the option to add subscriptions after the initial
|
||||
ones. Nevertheless you still can control the message stream via the returned `Flux` using eg. `take(Duration)`. When
|
||||
done reading, on error or cancellation all bound resources are freed again.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
redisTemplate.listenToChannel("channel1", "channel2").doOnNext(msg -> {
|
||||
// message processing ...
|
||||
}).subscribe();
|
||||
----
|
||||
@@ -1,191 +0,0 @@
|
||||
[[redis:reactive]]
|
||||
= Reactive Redis support
|
||||
:referenceDir: .
|
||||
|
||||
This section covers reactive Redis support and how to get started. Reactive Redis support naturally has certain overlaps with <<redis,imperative Redis support>>.
|
||||
|
||||
[[redis:reactive:requirements]]
|
||||
== Redis Requirements
|
||||
|
||||
Spring Data Redis currently integrates with https://github.com/lettuce-io/lettuce-core[Lettuce] as the only reactive Java connector. https://projectreactor.io/[Project Reactor] is used as reactive composition library.
|
||||
|
||||
[[redis:reactive:connectors]]
|
||||
== Connecting to Redis by Using a Reactive Driver
|
||||
|
||||
One of the first tasks when using Redis and Spring is to connect to the store through the IoC container. To do that, a Java connector (or binding) is required. No matter the library you choose, you must use the `org.springframework.data.redis.connection` package and its `ReactiveRedisConnection` and `ReactiveRedisConnectionFactory` interfaces to work with and retrieve active `connections` to Redis.
|
||||
|
||||
[[redis:reactive:connectors:operation-modes]]
|
||||
=== Redis Operation Modes
|
||||
|
||||
Redis can be run as a standalone server, with <<redis:sentinel,Redis Sentinel>>, or in <<cluster,Redis Cluster>> mode.
|
||||
https://github.com/lettuce-io/lettuce-core[Lettuce] supports all of the previously mentioned connection types.
|
||||
|
||||
[[redis:reactive:connectors:connection]]
|
||||
=== `ReactiveRedisConnection` and `ReactiveRedisConnectionFactory`
|
||||
|
||||
`ReactiveRedisConnection` is the core of Redis communication, as it handles the communication with the Redis back-end. It also automatically translates the underlying driver exceptions to Spring's consistent DAO exception {spring-framework-reference}/data-access.html#dao-exceptions[hierarchy], so you can switch the connectors without any code changes, as the operation semantics remain the same.
|
||||
|
||||
`ReactiveRedisConnectionFactory` creates active `ReactiveRedisConnection` instances. In addition, the factories act as `PersistenceExceptionTranslator` instances, meaning that, once declared, they let you do transparent exception translation -- for example, exception translation through the use of the `@Repository` annotation and AOP. For more information, see the dedicated {spring-framework-reference}/data-access.html#orm-exception-translation[section] in the Spring Framework documentation.
|
||||
|
||||
NOTE: Depending on the underlying configuration, the factory can return a new connection or an existing connection (in case a pool or shared native connection is used).
|
||||
|
||||
TIP: The easiest way to work with a `ReactiveRedisConnectionFactory` is to configure the appropriate connector through the IoC container and inject it into the using class.
|
||||
|
||||
[[redis:reactive:connectors:lettuce]]
|
||||
=== Configuring a Lettuce Connector
|
||||
|
||||
https://github.com/lettuce-io/lettuce-core[Lettuce] is supported by Spring Data Redis through the `org.springframework.data.redis.connection.lettuce` package.
|
||||
|
||||
You can set up `ReactiveRedisConnectionFactory` for Lettuce as follows:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public ReactiveRedisConnectionFactory connectionFactory() {
|
||||
return new LettuceConnectionFactory("localhost", 6379);
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows a more sophisticated configuration, including SSL and timeouts, that uses `LettuceClientConfigurationBuilder`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@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);
|
||||
}
|
||||
----
|
||||
|
||||
For more detailed client configuration tweaks, see https://docs.spring.io/spring-data/redis/docs/{revnumber}/api/org/springframework/data/redis/connection/lettuce/LettuceClientConfiguration.html[`LettuceClientConfiguration`].
|
||||
|
||||
[[redis:reactive:template]]
|
||||
== Working with Objects through ReactiveRedisTemplate
|
||||
|
||||
Most users are likely to use `ReactiveRedisTemplate` and its corresponding package, `org.springframework.data.redis.core`. Due to its rich feature set, the template is, in fact, the central class of the Redis module. The template offers a high-level abstraction for Redis interactions. While `ReactiveRedisConnection` offers low-level methods that accept and return binary values (`ByteBuffer`), the template takes care of serialization and connection management, freeing you from dealing with such details.
|
||||
|
||||
Moreover, the template provides operation views (following the grouping from Redis command https://redis.io/commands[reference]) that offer rich, generified interfaces for working against a certain type as described in the following table:
|
||||
|
||||
.Operational views
|
||||
[width="80%",cols="<1,<2",options="header"]
|
||||
|====
|
||||
|Interface
|
||||
|Description
|
||||
|
||||
2+^|_Key Type Operations_
|
||||
|
||||
|ReactiveGeoOperations
|
||||
|Redis geospatial operations such as `GEOADD`, `GEORADIUS`, and others)
|
||||
|
||||
|ReactiveHashOperations
|
||||
|Redis hash operations
|
||||
|
||||
|ReactiveHyperLogLogOperations
|
||||
|Redis HyperLogLog operations such as (`PFADD`, `PFCOUNT`, and others)
|
||||
|
||||
|ReactiveListOperations
|
||||
|Redis list operations
|
||||
|
||||
|ReactiveSetOperations
|
||||
|Redis set operations
|
||||
|
||||
|ReactiveValueOperations
|
||||
|Redis string (or value) operations
|
||||
|
||||
|ReactiveZSetOperations
|
||||
|Redis zset (or sorted set) operations
|
||||
|====
|
||||
|
||||
Once configured, the template is thread-safe and can be reused across multiple instances.
|
||||
|
||||
`ReactiveRedisTemplate` uses a Java-based serializer for most of its operations. This means that any object written or read by the template is serialized or deserialized through `RedisElementWriter` or `RedisElementReader`. The serialization context is passed to the template upon construction, and the Redis module offers several implementations available in the `org.springframework.data.redis.serializer` package. See <<redis:serializer>> for more information.
|
||||
|
||||
The following example shows a `ReactiveRedisTemplate` being used to return a `Mono`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
class RedisConfiguration {
|
||||
|
||||
@Bean
|
||||
ReactiveRedisTemplate<String, String> reactiveRedisTemplate(ReactiveRedisConnectionFactory factory) {
|
||||
return new ReactiveRedisTemplate<>(factory, RedisSerializationContext.string());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[source,java]
|
||||
----
|
||||
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:reactive:string]]
|
||||
== String-focused Convenience Classes
|
||||
|
||||
Since it is quite common for keys and values stored in Redis to be a `java.lang.String`, the Redis module provides a String-based extension to `ReactiveRedisTemplate`: `ReactiveStringRedisTemplate`. It is a convenient one-stop solution for intensive `String` operations. In addition to being bound to `String` keys, the template uses the String-based `RedisSerializationContext`, which means the stored keys and values are human readable (assuming the same encoding is used in both Redis and your code). The following example shows `ReactiveStringRedisTemplate` in use:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
class RedisConfiguration {
|
||||
|
||||
@Bean
|
||||
ReactiveStringRedisTemplate reactiveRedisTemplate(ReactiveRedisConnectionFactory factory) {
|
||||
return new ReactiveStringRedisTemplate<>(factory);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class Example {
|
||||
|
||||
@Autowired
|
||||
private ReactiveStringRedisTemplate redisTemplate;
|
||||
|
||||
public Mono<Long> addLink(String userId, URL url) {
|
||||
return redisTemplate.opsForList().leftPush(userId, url.toExternalForm());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
include::{referenceDir}/reactive-messaging.adoc[leveloffset=+1]
|
||||
|
||||
[[redis:reactive:scripting]]
|
||||
== Reactive Scripting
|
||||
|
||||
You can run Redis scripts with the reactive infrastructure by using the `ReactiveScriptExecutor`, which is best accessed through `ReactiveRedisTemplate`.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
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);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
See to the <<scripting,scripting section>> for more details on scripting commands.
|
||||
@@ -1,173 +0,0 @@
|
||||
[[cluster]]
|
||||
= Redis Cluster
|
||||
|
||||
Working with https://redis.io/topics/cluster-spec[Redis Cluster] requires Redis Server version 3.0+. See the https://redis.io/topics/cluster-tutorial[Cluster Tutorial] for more information.
|
||||
|
||||
[[cluster.enable]]
|
||||
== Enabling Redis Cluster
|
||||
|
||||
Cluster support is based on the same building blocks as non-clustered communication. `RedisClusterConnection`, an extension to `RedisConnection`, handles the communication with the Redis Cluster and translates errors into the Spring DAO exception hierarchy.
|
||||
`RedisClusterConnection` instances are created with the `RedisConnectionFactory`, which has to be set up with the associated `RedisClusterConfiguration`, as shown in the following example:
|
||||
|
||||
.Sample RedisConnectionFactory Configuration for Redis Cluster
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@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 LettuceConnectionFactory(
|
||||
new RedisClusterConfiguration(clusterProperties.getNodes()));
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[TIP]
|
||||
====
|
||||
`RedisClusterConfiguration` can also be defined through `PropertySource` and has the following properties:
|
||||
|
||||
.Configuration Properties
|
||||
- `spring.redis.cluster.nodes`: Comma-delimited list of host:port pairs.
|
||||
- `spring.redis.cluster.max-redirects`: Number of allowed cluster redirections.
|
||||
====
|
||||
|
||||
NOTE: The initial configuration points driver libraries to an initial set of cluster nodes. Changes resulting from live cluster reconfiguration are kept only in the native driver and are not written back to the configuration.
|
||||
|
||||
[[cluster.working.with.cluster]]
|
||||
== Working With Redis Cluster Connection
|
||||
|
||||
As mentioned earlier, Redis Cluster behaves differently from single-node Redis or even a Sentinel-monitored master-replica environment. This is because the automatic sharding maps a key to one of 16384 slots, which are distributed across the nodes. Therefore, commands that involve more than one key must assert all keys map to the exact same slot to avoid cross-slot errors.
|
||||
A single cluster node serves only a dedicated set of keys. Commands issued against one particular server return results only for those keys served by that server. As a simple example, consider the `KEYS` command. When issued to a server in a cluster environment, it returns only the keys served by the node the request is sent to and not necessarily all keys within the cluster. So, to get all keys in a cluster environment, you must read the keys from all the known master nodes.
|
||||
|
||||
While redirects for specific keys to the corresponding slot-serving node are handled by the driver libraries, higher-level functions, such as collecting information across nodes or sending commands to all nodes in the cluster, are covered by `RedisClusterConnection`. Picking up the keys example from earlier, this means that the `keys(pattern)` method picks up every master node in the cluster and simultaneously runs the `KEYS` command on every master node while picking up the results and returning the cumulated set of keys. To just request the keys of a single node `RedisClusterConnection` provides overloads for those methods (for example, `keys(node, pattern)`).
|
||||
|
||||
A `RedisClusterNode` can be obtained from `RedisClusterConnection.clusterGetNodes` or it can be constructed by using either the host and the port or the node Id.
|
||||
|
||||
The following example shows a set of commands being run across the cluster:
|
||||
|
||||
.Sample of Running Commands Across the Cluster
|
||||
====
|
||||
[source,text]
|
||||
----
|
||||
redis-cli@127.0.0.1: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>
|
||||
----
|
||||
|
||||
[source,java]
|
||||
----
|
||||
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> Master node serving slots 0 to 5460 replicated to replica at 7382
|
||||
<2> Master node serving slots 5461 to 10922
|
||||
<3> Master node serving slots 10923 to 16383
|
||||
<4> Replica node holding replicants of the master at 7379
|
||||
<5> Request routed to node at 7381 serving slot 12182
|
||||
<6> Request routed to node at 7379 serving slot 5061
|
||||
<7> Request routed to nodes at 7379, 7380, 7381 -> [thing1, thing2]
|
||||
<8> Request routed to node at 7379 -> [thing2]
|
||||
<9> Request routed to node at 7380 -> []
|
||||
<10> Request routed to node at 7381 -> [thing1]
|
||||
<11> Request routed to node at 7382 -> [thing2]
|
||||
====
|
||||
|
||||
When all keys map to the same slot, the native driver library automatically serves cross-slot requests, such as `MGET`. However, once this is not the case, `RedisClusterConnection` runs multiple parallel `GET` commands against the slot-serving nodes and again returns an accumulated result. This is less performant than the single-slot approach and, therefore, should be used with care. If in doubt, consider pinning keys to the same slot by providing a prefix in curly brackets, such as `{my-prefix}.thing1` and `{my-prefix}.thing2`, which will both map to the same slot number. The following example shows cross-slot request handling:
|
||||
|
||||
.Sample of Cross-Slot Request Handling
|
||||
====
|
||||
[source,text]
|
||||
----
|
||||
redis-cli@127.0.0.1:7379 > cluster nodes
|
||||
|
||||
6b38bb... 127.0.0.1:7379 master - 0 0 25 connected 0-5460 <1>
|
||||
7bb...
|
||||
----
|
||||
|
||||
[source,java]
|
||||
----
|
||||
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> Same Configuration as in the sample before.
|
||||
<2> Keys map to same slot -> 127.0.0.1:7381 MGET thing1 {thing1}.thing2
|
||||
<3> Keys map to different slots and get split up into single slot ones routed to the according nodes +
|
||||
-> 127.0.0.1:7379 GET thing2 +
|
||||
-> 127.0.0.1:7381 GET thing1
|
||||
====
|
||||
|
||||
TIP: The preceding examples demonstrate the general strategy followed by Spring Data Redis. Be aware that some operations might require loading huge amounts of data into memory to compute the desired command. Additionally, not all cross-slot requests can safely be ported to multiple single slot requests and error if misused (for example, `PFCOUNT`).
|
||||
|
||||
[[cluster.redistemplate]]
|
||||
== Working with `RedisTemplate` and `ClusterOperations`
|
||||
|
||||
See the <<redis:template>> section for information about the general purpose, configuration, and usage of `RedisTemplate`.
|
||||
|
||||
CAUTION: Be careful when setting up `RedisTemplate#keySerializer` using any of the JSON `RedisSerializers`, as changing JSON structure has immediate influence on hash slot calculation.
|
||||
|
||||
`RedisTemplate` provides access to cluster-specific operations through the `ClusterOperations` interface, which can be obtained from `RedisTemplate.opsForCluster()`. This lets you explicitly run commands on a single node within the cluster while retaining the serialization and deserialization features configured for the template. It also provides administrative commands (such as `CLUSTER MEET`) or more high-level operations (for example, resharding).
|
||||
|
||||
The following example shows how to access `RedisClusterConnection` with `RedisTemplate`:
|
||||
|
||||
|
||||
.Accessing `RedisClusterConnection` with `RedisTemplate`
|
||||
====
|
||||
[source,text]
|
||||
----
|
||||
ClusterOperations clusterOps = redisTemplate.opsForCluster();
|
||||
clusterOps.shutdown(NODE_7379); <1>
|
||||
----
|
||||
<1> Shut down node at 7379 and cross fingers there is a replica in place that can take over.
|
||||
====
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,774 +0,0 @@
|
||||
[[redis]]
|
||||
= Redis support
|
||||
:referenceDir: .
|
||||
|
||||
One of the key-value stores supported by Spring Data is https://redis.io[Redis]. To quote the Redis project home page:
|
||||
|
||||
[quote]
|
||||
Redis is an advanced key-value store. It is similar to memcached but the dataset is not volatile, and values can be strings, exactly like in memcached, but also lists, sets, and ordered sets. All this data types can be manipulated with atomic operations to push/pop elements, add/remove elements, perform server side union, intersection, difference between sets, and so forth. Redis supports different kind of sorting abilities.
|
||||
|
||||
Spring Data Redis 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.
|
||||
|
||||
[[redis:setup]]
|
||||
== Getting Started
|
||||
|
||||
An easy way to setting up a working environment is to create a Spring-based project in https://spring.io/tools/[STS].
|
||||
|
||||
First, you need to set up a running Redis server.
|
||||
|
||||
To create a Spring project in STS:
|
||||
|
||||
. Go to File -> New -> Spring Template Project -> Simple Spring Utility Project, and press Yes when prompted. Then enter a project and a package name, such as `org.spring.redis.example`.
|
||||
.Add the following to the pom.xml files `dependencies` element:
|
||||
+
|
||||
[source,xml,subs="+attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
|
||||
<!-- other dependency elements omitted -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-redis</artifactId>
|
||||
<version>{version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
----
|
||||
. Change the version of Spring in the pom.xml to be
|
||||
+
|
||||
[source,xml,subs="+attributes"]
|
||||
----
|
||||
<spring.framework.version>{springVersion}</spring.framework.version>
|
||||
----
|
||||
. Add the following location of the Spring Milestone repository for Maven to your `pom.xml` such that it is at the same level of your `<dependencies/>` element:
|
||||
+
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-milestone</id>
|
||||
<name>Spring Maven MILESTONE Repository</name>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
|
||||
The repository is also https://repo.spring.io/milestone/org/springframework/data/[browseable here].
|
||||
|
||||
[[redis:requirements]]
|
||||
== Redis Requirements
|
||||
|
||||
Spring Redis requires Redis 2.6 or above and Spring Data Redis integrates with https://github.com/lettuce-io/lettuce-core[Lettuce] and https://github.com/redis/jedis[Jedis], two popular open-source Java libraries for Redis.
|
||||
|
||||
[[redis:architecture]]
|
||||
== Redis Support High-level View
|
||||
|
||||
The Redis support provides several components. For most tasks, the high-level abstractions and support services are the best choice. Note that, at any point, you can move between layers. For example, you can get a low-level connection (or even the native library) to communicate directly with Redis.
|
||||
|
||||
[[redis:connectors]]
|
||||
== Connecting to Redis
|
||||
|
||||
One of the first tasks when using Redis and Spring is to connect to the store through the IoC container. To do that, a Java connector (or binding) is required. No matter the library you choose, you need to use only one set of Spring Data Redis APIs (which behaves consistently across all connectors): the `org.springframework.data.redis.connection` package and its `RedisConnection` and `RedisConnectionFactory` interfaces for working with and retrieving active connections to Redis.
|
||||
|
||||
[[redis:connectors:connection]]
|
||||
=== RedisConnection and RedisConnectionFactory
|
||||
|
||||
`RedisConnection` provides the core building block for Redis communication, as it handles the communication with the Redis backend. It also automatically translates underlying connecting library exceptions to Spring's consistent {spring-framework-reference}/data-access.html#dao-exceptions[DAO exception hierarchy] so that you can switch connectors without any code changes, as the operation semantics remain the same.
|
||||
|
||||
NOTE: For the corner cases where the native library API is required, `RedisConnection` provides a dedicated method (`getNativeConnection`) that returns the raw, underlying object used for communication.
|
||||
|
||||
Active `RedisConnection` objects are created through `RedisConnectionFactory`. In addition, the factory acts as `PersistenceExceptionTranslator` objects, meaning that, once declared, they let you do transparent exception translation. For example, you can do exception translation through the use of the `@Repository` annotation and AOP. For more information, see the dedicated {spring-framework-reference}/data-access.html#orm-exception-translation[section] in the Spring Framework documentation.
|
||||
|
||||
NOTE: `RedisConnection` classes are **not** Thread-safe.
|
||||
While the underlying native connection, such as Lettuce's `StatefulRedisConnection`, may be Thread-safe, Spring Data Redis's `LettuceConnection` class itself is not.
|
||||
Therefore, you should **not** share instances of a `RedisConnection` across multiple Threads.
|
||||
This is especially true for transactional, or blocking Redis operations and commands, such as `BLPOP`.
|
||||
In transactional and pipelining operations, for instance, `RedisConnection` holds onto unguarded mutable state to complete the operation correctly, thereby making it unsafe to use with multiple Threads.
|
||||
This is by design.
|
||||
|
||||
TIP: If you need to share (stateful) Redis resources, like connections, across multiple Threads, for performance reasons or otherwise, then you should acquire the native connection and use the Redis client library (driver) API directly.
|
||||
Alternatively, you can use the `RedisTemplate`, which acquires and manages connections for operations (and Redis commands) in a Thread-safe manner. See <<redis:template,documentation>> on `RedisTemplate` for more details.
|
||||
|
||||
NOTE: Depending on the underlying configuration, the factory can return a new connection or an existing connection (when a pool or shared native connection is used).
|
||||
|
||||
The easiest way to work with a `RedisConnectionFactory` is to configure the appropriate connector through the IoC container and inject it into the using class.
|
||||
|
||||
Unfortunately, currently, not all connectors support all Redis features. When invoking a method on the Connection API that is unsupported by the underlying library, an `UnsupportedOperationException` is thrown. The following overview explains features that are supported by the individual Redis connectors:
|
||||
|
||||
|
||||
[[redis:connectors:overview]]
|
||||
.Feature Availability across Redis Connectors
|
||||
|===
|
||||
| Supported Feature | Lettuce | Jedis
|
||||
|
||||
| Standalone Connections
|
||||
| X
|
||||
| X
|
||||
|
||||
| <<redis:write-to-master-read-from-replica,Master/Replica Connections>>
|
||||
| X
|
||||
|
|
||||
|
||||
| <<redis:sentinel,Redis Sentinel>>
|
||||
| Master Lookup, Sentinel Authentication, Replica Reads
|
||||
| Master Lookup
|
||||
|
||||
| <<cluster,Redis Cluster>>
|
||||
| Cluster Connections, Cluster Node Connections, Replica Reads
|
||||
| Cluster Connections, Cluster Node Connections
|
||||
|
||||
| Transport Channels
|
||||
| TCP, OS-native TCP (epoll, kqueue), Unix Domain Sockets
|
||||
| TCP
|
||||
|
||||
| Connection Pooling
|
||||
| X (using `commons-pool2`)
|
||||
| X (using `commons-pool2`)
|
||||
|
||||
| Other Connection Features
|
||||
| Singleton-connection sharing for non-blocking commands
|
||||
| Pipelining and Transactions mutually exclusive. Cannot use server/connection commands in pipeline/transactions.
|
||||
|
||||
| SSL Support
|
||||
| X
|
||||
| X
|
||||
|
||||
| <<pubsub,Pub/Sub>>
|
||||
| X
|
||||
| X
|
||||
|
||||
| <<pipeline,Pipelining>>
|
||||
| X
|
||||
| X (Pipelining and Transactions mutually exclusive)
|
||||
|
||||
| <<tx,Transactions>>
|
||||
| X
|
||||
| X (Pipelining and Transactions mutually exclusive)
|
||||
|
||||
| Datatype support
|
||||
| Key, String, List, Set, Sorted Set, Hash, Server, Stream, Scripting, Geo, HyperLogLog
|
||||
| Key, String, List, Set, Sorted Set, Hash, Server, Scripting, Geo, HyperLogLog
|
||||
|
||||
| <<redis:reactive,Reactive (non-blocking) API>>
|
||||
| X
|
||||
|
|
||||
|
||||
|===
|
||||
|
||||
[[redis:connectors:lettuce]]
|
||||
=== Configuring the Lettuce Connector
|
||||
|
||||
https://github.com/lettuce-io/lettuce-core[Lettuce] is a https://netty.io/[Netty]-based open-source connector supported by Spring Data Redis through the `org.springframework.data.redis.connection.lettuce` package.
|
||||
|
||||
.Add the following to the pom.xml files `dependencies` element:
|
||||
[source,xml,subs="+attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
|
||||
<!-- other dependency elements omitted -->
|
||||
|
||||
<dependency>
|
||||
<groupId>io.lettuce</groupId>
|
||||
<artifactId>lettuce-core</artifactId>
|
||||
<version>{lettuce}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
The following example shows how to create a new Lettuce connection factory:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
|
||||
@Bean
|
||||
public LettuceConnectionFactory redisConnectionFactory() {
|
||||
|
||||
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("server", 6379));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
There are also a few Lettuce-specific connection parameters that can be tweaked. By default, all `LettuceConnection` instances created by the `LettuceConnectionFactory` share the same thread-safe native connection for all non-blocking and non-transactional operations. To use a dedicated connection each time, set `shareNativeConnection` to `false`. `LettuceConnectionFactory` can also be configured to use a `LettucePool` for pooling blocking and transactional connections or all connections if `shareNativeConnection` is set to `false`.
|
||||
|
||||
Lettuce integrates with Netty's https://netty.io/wiki/native-transports.html[native transports], letting you use Unix domain sockets to communicate with Redis. Make sure to include the appropriate native transport dependencies that match your runtime environment. The following example shows how to create a Lettuce Connection factory for a Unix domain socket at `/var/run/redis.sock`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
|
||||
@Bean
|
||||
public LettuceConnectionFactory redisConnectionFactory() {
|
||||
|
||||
return new LettuceConnectionFactory(new RedisSocketConfiguration("/var/run/redis.sock"));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: Netty currently supports the epoll (Linux) and kqueue (BSD/macOS) interfaces for OS-native transport.
|
||||
|
||||
[[redis:connectors:jedis]]
|
||||
=== Configuring the Jedis Connector
|
||||
|
||||
https://github.com/redis/jedis[Jedis] is a community-driven connector supported by the Spring Data Redis module through the `org.springframework.data.redis.connection.jedis` package.
|
||||
|
||||
|
||||
.Add the following to the pom.xml files `dependencies` element:
|
||||
[source,xml,subs="+attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
|
||||
<!-- other dependency elements omitted -->
|
||||
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>{jedis}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
In its simplest form, the Jedis configuration looks as follow:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
|
||||
@Bean
|
||||
public JedisConnectionFactory redisConnectionFactory() {
|
||||
return new JedisConnectionFactory();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
For production use, however, you might want to tweak settings such as the host or password, as shown in the following example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
class RedisConfiguration {
|
||||
|
||||
@Bean
|
||||
public JedisConnectionFactory redisConnectionFactory() {
|
||||
|
||||
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("server", 6379);
|
||||
return new JedisConnectionFactory(config);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[redis:write-to-master-read-from-replica]]
|
||||
=== Write to Master, Read from Replica
|
||||
|
||||
The Redis Master/Replica setup -- without automatic failover (for automatic failover see: <<redis:sentinel, Sentinel>>) -- not only allows data to be safely stored at more nodes. It also allows, by using <<redis:connectors:lettuce, Lettuce>>, reading data from replicas while pushing writes to the master. You can set the read/write strategy to be used by using `LettuceClientConfiguration`, as shown in the following example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
class WriteToMasterReadFromReplicaConfiguration {
|
||||
|
||||
@Bean
|
||||
public LettuceConnectionFactory redisConnectionFactory() {
|
||||
|
||||
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
|
||||
.readFrom(REPLICA_PREFERRED)
|
||||
.build();
|
||||
|
||||
RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration("server", 6379);
|
||||
|
||||
return new LettuceConnectionFactory(serverConfig, clientConfig);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
TIP: For environments reporting non-public addresses through the `INFO` command (for example, when using AWS), use `RedisStaticMasterReplicaConfiguration` instead of `RedisStandaloneConfiguration`. Please note that `RedisStaticMasterReplicaConfiguration` does not support Pub/Sub because of missing Pub/Sub message propagation across individual servers.
|
||||
|
||||
[[redis:sentinel]]
|
||||
== Redis Sentinel Support
|
||||
|
||||
For dealing with high-availability Redis, Spring Data Redis has support for https://redis.io/topics/sentinel[Redis Sentinel], using `RedisSentinelConfiguration`, as shown in the following example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
----
|
||||
|
||||
[TIP]
|
||||
====
|
||||
`RedisSentinelConfiguration` can also be defined with a `PropertySource`, which lets you set the following properties:
|
||||
|
||||
.Configuration Properties
|
||||
* `spring.redis.sentinel.master`: name of the master node.
|
||||
* `spring.redis.sentinel.nodes`: Comma delimited list of host:port pairs.
|
||||
* `spring.redis.sentinel.username`: The username to apply when authenticating with Redis Sentinel (requires Redis 6)
|
||||
* `spring.redis.sentinel.password`: The password to apply when authenticating with Redis Sentinel
|
||||
====
|
||||
|
||||
Sometimes, direct interaction with one of the Sentinels is required. Using `RedisConnectionFactory.getSentinelConnection()` or `RedisConnection.getSentinelCommands()` gives you access to the first active Sentinel configured.
|
||||
|
||||
[[redis:template]]
|
||||
== Working with Objects through RedisTemplate
|
||||
|
||||
Most users are likely to use `RedisTemplate` and its corresponding package, `org.springframework.data.redis.core`. The template is, in fact, the central class of the Redis module, due to its rich feature set. The template offers a high-level abstraction for Redis interactions. While `RedisConnection` offers low-level methods that accept and return binary values (`byte` arrays), the template takes care of serialization and connection management, freeing the user from dealing with such details.
|
||||
|
||||
Moreover, the template provides operations views (following the grouping from the Redis command https://redis.io/commands[reference]) that offer rich, generified interfaces for working against a certain type or certain key (through the `KeyBound` interfaces) as described in the following table:
|
||||
|
||||
.Operational views
|
||||
[width="80%",cols="<1,<2",options="header"]
|
||||
|====
|
||||
|Interface
|
||||
|Description
|
||||
|
||||
2+^|_Key Type Operations_
|
||||
|
||||
|`GeoOperations`
|
||||
|Redis geospatial operations, such as `GEOADD`, `GEORADIUS`,...
|
||||
|
||||
|`HashOperations`
|
||||
|Redis hash operations
|
||||
|
||||
|`HyperLogLogOperations`
|
||||
|Redis HyperLogLog operations, such as `PFADD`, `PFCOUNT`,...
|
||||
|
||||
|`ListOperations`
|
||||
|Redis list operations
|
||||
|
||||
|`SetOperations`
|
||||
|Redis set operations
|
||||
|
||||
|`ValueOperations`
|
||||
|Redis string (or value) operations
|
||||
|
||||
|`ZSetOperations`
|
||||
|Redis zset (or sorted set) operations
|
||||
|
||||
2+^|_Key Bound Operations_
|
||||
|
||||
|`BoundGeoOperations`
|
||||
|Redis key bound geospatial operations
|
||||
|
||||
|`BoundHashOperations`
|
||||
|Redis hash key bound operations
|
||||
|
||||
|`BoundKeyOperations`
|
||||
|Redis key bound operations
|
||||
|
||||
|`BoundListOperations`
|
||||
|Redis list key bound operations
|
||||
|
||||
|`BoundSetOperations`
|
||||
|Redis set key bound operations
|
||||
|
||||
|`BoundValueOperations`
|
||||
|Redis string (or value) key bound operations
|
||||
|
||||
|`BoundZSetOperations`
|
||||
|Redis zset (or sorted set) key bound operations
|
||||
|
||||
|====
|
||||
|
||||
Once configured, the template is thread-safe and can be reused across multiple instances.
|
||||
|
||||
`RedisTemplate` uses a Java-based serializer for most of its operations. This means that any object written or read by the template is serialized and deserialized through Java. You can change the serialization mechanism on the template, and the Redis module offers several implementations, which are available in the `org.springframework.data.redis.serializer` package. See <<redis:serializer>> for more information. You can also set any of the serializers to null and use RedisTemplate with raw byte arrays by setting the `enableDefaultSerializer` property to `false`. Note that the template requires all keys to be non-null. However, values can be null as long as the underlying serializer accepts them. Read the Javadoc of each serializer for more information.
|
||||
|
||||
For cases where you need a certain template view, declare the view as a dependency and inject the template. The container automatically performs the conversion, eliminating the `opsFor[X]` calls, as shown in the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
class MyConfig {
|
||||
|
||||
@Bean
|
||||
LettuceConnectionFactory redisConnectionFactory() {
|
||||
return new LettuceConnectionFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||
|
||||
RedisTemplate<String, String> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(redisConnectionFactory);
|
||||
return template;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>
|
||||
<!-- redis template definition -->
|
||||
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="redisConnectionFactory"/>
|
||||
...
|
||||
|
||||
</beans>
|
||||
----
|
||||
====
|
||||
|
||||
[source,java]
|
||||
----
|
||||
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:string]]
|
||||
== String-focused Convenience Classes
|
||||
|
||||
Since it is quite common for the keys and values stored in Redis to be `java.lang.String`, the Redis modules provides two extensions to `RedisConnection` and `RedisTemplate`, respectively the `StringRedisConnection` (and its `DefaultStringRedisConnection` implementation) and `StringRedisTemplate` as a convenient one-stop solution for intensive String operations. In addition to being bound to `String` keys, the template and the connection use the `StringRedisSerializer` underneath, which means the stored keys and values are human-readable (assuming the same encoding is used both in Redis and your code). The following listings show an example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
class MyConfig {
|
||||
|
||||
@Bean
|
||||
LettuceConnectionFactory redisConnectionFactory() {
|
||||
return new LettuceConnectionFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||
|
||||
StringRedisTemplate template = new StringRedisTemplate();
|
||||
template.setConnectionFactory(redisConnectionFactory);
|
||||
return template;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>
|
||||
|
||||
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" p:connection-factory-ref="redisConnectionFactory"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
====
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class Example {
|
||||
|
||||
@Autowired
|
||||
private StringRedisTemplate redisTemplate;
|
||||
|
||||
public void addLink(String userId, URL url) {
|
||||
redisTemplate.opsForList().leftPush(userId, url.toExternalForm());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
As with the other Spring templates, `RedisTemplate` and `StringRedisTemplate` let you talk directly to Redis through the `RedisCallback` interface. This feature gives complete control to you, as it talks directly to the `RedisConnection`. Note that the callback receives an instance of `StringRedisConnection` when a `StringRedisTemplate` is used. The following example shows how to use the `RedisCallback` interface:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
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:serializer]]
|
||||
== Serializers
|
||||
|
||||
From the framework perspective, the data stored in Redis is only bytes. While Redis itself supports various types, for the most part, these refer to the way the data is stored rather than what it represents. It is up to the user to decide whether the information gets translated into strings or any other objects.
|
||||
|
||||
In Spring Data, the conversion between the user (custom) types and raw data (and vice-versa) is handled by Spring Data Redis in the `org.springframework.data.redis.serializer` package.
|
||||
|
||||
This package contains two types of serializers that, as the name implies, take care of the serialization process:
|
||||
|
||||
* Two-way serializers based on ``RedisSerializer``.
|
||||
* Element readers and writers that use `RedisElementReader` and ``RedisElementWriter``.
|
||||
|
||||
The main difference between these variants is that `RedisSerializer` primarily serializes to `byte[]` while readers and writers use `ByteBuffer`.
|
||||
|
||||
Multiple implementations are available (including two that have been already mentioned in this documentation):
|
||||
|
||||
* `JdkSerializationRedisSerializer`, which is used by default for `RedisCache` and `RedisTemplate`.
|
||||
* the `StringRedisSerializer`.
|
||||
|
||||
However one can use `OxmSerializer` for Object/XML mapping through Spring {spring-framework-reference}/data-access.html#oxm[OXM] support or `Jackson2JsonRedisSerializer` or `GenericJackson2JsonRedisSerializer` for storing data in https://en.wikipedia.org/wiki/JSON[JSON] format.
|
||||
|
||||
Do note that the storage format is not limited only to values. It can be used for keys, values, or hashes without any restrictions.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
By default, `RedisCache` and `RedisTemplate` are configured to use Java native serialization. Java native serialization is known for allowing the running of remote code caused by payloads that exploit vulnerable libraries and classes injecting unverified bytecode. Manipulated input could lead to unwanted code being run in the application during the deserialization step. As a consequence, do not use serialization in untrusted environments. In general, we strongly recommend any other message format (such as JSON) instead.
|
||||
|
||||
If you are concerned about security vulnerabilities due to Java serialization, consider the general-purpose serialization filter mechanism at the core JVM level:
|
||||
|
||||
* https://docs.oracle.com/en/java/javase/17/core/serialization-filtering1.html[Filter Incoming Serialization Data].
|
||||
* https://openjdk.org/jeps/290[JEP 290].
|
||||
* https://owasp.org/www-community/vulnerabilities/Deserialization_of_untrusted_data[OWASP: Deserialization of untrusted data].
|
||||
====
|
||||
|
||||
[[redis.hashmappers.root]]
|
||||
== Hash mapping
|
||||
|
||||
Data can be stored by using various data structures within Redis. `Jackson2JsonRedisSerializer` can convert objects in https://en.wikipedia.org/wiki/JSON[JSON] format. Ideally, JSON can be stored as a value by using plain keys. You can achieve a more sophisticated mapping of structured objects by using Redis hashes. Spring Data Redis offers various strategies for mapping data to hashes (depending on the use case):
|
||||
|
||||
* Direct mapping, by using `HashOperations` and a <<redis:serializer,serializer>>
|
||||
* Using <<redis.repositories>>
|
||||
* Using `HashMapper` and `HashOperations`
|
||||
|
||||
[[redis.hashmappers.mappers]]
|
||||
=== Hash Mappers
|
||||
|
||||
Hash mappers are converters of map objects to a `Map<K, V>` and back. `HashMapper` is intended for using with Redis Hashes.
|
||||
|
||||
Multiple implementations are available:
|
||||
|
||||
* `BeanUtilsHashMapper` using Spring's {spring-framework-javadoc}/org/springframework/beans/BeanUtils.html[BeanUtils].
|
||||
* `ObjectHashMapper` using <<redis.repositories.mapping>>.
|
||||
* <<redis.hashmappers.jackson2,`Jackson2HashMapper`>> using https://github.com/FasterXML/jackson[FasterXML Jackson].
|
||||
|
||||
The following example shows one way to implement hash mapping:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
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);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[redis.hashmappers.jackson2]]
|
||||
=== Jackson2HashMapper
|
||||
|
||||
`Jackson2HashMapper` provides Redis Hash mapping for domain objects by using https://github.com/FasterXML/jackson[FasterXML Jackson].
|
||||
`Jackson2HashMapper` can map top-level properties as Hash field names and, optionally, flatten the structure.
|
||||
Simple types map to simple values. Complex types (nested objects, collections, maps, and so on) are represented as nested JSON.
|
||||
|
||||
Flattening creates individual hash entries for all nested properties and resolves complex types into simple types, as far as possible.
|
||||
|
||||
Consider the following class and the data structure it contains:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class Person {
|
||||
String firstname;
|
||||
String lastname;
|
||||
Address address;
|
||||
Date date;
|
||||
LocalDateTime localDateTime;
|
||||
}
|
||||
|
||||
public class Address {
|
||||
String city;
|
||||
String country;
|
||||
}
|
||||
----
|
||||
|
||||
The following table shows how the data in the preceding class would appear in normal mapping:
|
||||
|
||||
.Normal Mapping
|
||||
[width="80%",cols="<1,<2",options="header"]
|
||||
|====
|
||||
|Hash Field
|
||||
|Value
|
||||
|
||||
|firstname
|
||||
|`Jon`
|
||||
|
||||
|lastname
|
||||
|`Snow`
|
||||
|
||||
|address
|
||||
|`{ "city" : "Castle Black", "country" : "The North" }`
|
||||
|
||||
|date
|
||||
|`1561543964015`
|
||||
|
||||
|localDateTime
|
||||
|`2018-01-02T12:13:14`
|
||||
|====
|
||||
|
||||
The following table shows how the data in the preceding class would appear in flat mapping:
|
||||
|
||||
.Flat Mapping
|
||||
[width="80%",cols="<1,<2",options="header"]
|
||||
|====
|
||||
|Hash Field
|
||||
|Value
|
||||
|
||||
|firstname
|
||||
|`Jon`
|
||||
|
||||
|lastname
|
||||
|`Snow`
|
||||
|
||||
|address.city
|
||||
|`Castle Black`
|
||||
|
||||
|address.country
|
||||
|`The North`
|
||||
|
||||
|date
|
||||
|`1561543964015`
|
||||
|
||||
|localDateTime
|
||||
|`2018-01-02T12:13:14`
|
||||
|====
|
||||
|
||||
NOTE: Flattening requires all property names to not interfere with the JSON path. Using dots or brackets in map keys or as property names is not supported when you use flattening. The resulting hash cannot be mapped back into an Object.
|
||||
|
||||
NOTE: `java.util.Date` and `java.util.Calendar` are represented with milliseconds. JSR-310 Date/Time types are serialized to their `toString` form if `jackson-datatype-jsr310` is on the class path.
|
||||
|
||||
:leveloffset: 2
|
||||
include::{referenceDir}/redis-messaging.adoc[]
|
||||
|
||||
include::{referenceDir}/redis-streams.adoc[]
|
||||
|
||||
include::{referenceDir}/redis-transactions.adoc[]
|
||||
|
||||
include::{referenceDir}/pipelining.adoc[]
|
||||
|
||||
include::{referenceDir}/redis-scripting.adoc[]
|
||||
|
||||
include::{referenceDir}/redis-cache.adoc[]
|
||||
|
||||
:leveloffset: 1
|
||||
[[redis:support]]
|
||||
== Support Classes
|
||||
|
||||
Package `org.springframework.data.redis.support` offers various reusable components that rely on Redis as a backing store. Currently, the package contains various JDK-based interface implementations on top of Redis, such as https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/atomic/package-summary.html[atomic] counters and JDK https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collection.html[Collections].
|
||||
|
||||
The atomic counters make it easy to wrap Redis key incrementation while the collections allow easy management of Redis keys with minimal storage exposure or API leakage. In particular, the `RedisSet` and `RedisZSet` interfaces offer easy access to the set operations supported by Redis, such as `intersection` and `union`. `RedisList` implements the `List`, `Queue`, and `Deque` contracts (and their equivalent blocking siblings) on top of Redis, exposing the storage as a FIFO (First-In-First-Out), LIFO (Last-In-First-Out) or capped collection with minimal configuration. The following example shows the configuration for a bean that uses a `RedisList`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
class MyConfig {
|
||||
|
||||
// …
|
||||
|
||||
@Bean
|
||||
RedisList<String> stringRedisTemplate(RedisTemplate<String, String> redisTemplate) {
|
||||
return new DefaultRedisList<>(template, "queue-key");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<?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 https://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>
|
||||
----
|
||||
====
|
||||
|
||||
The following example shows a Java configuration example for a `Deque`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class AnotherExample {
|
||||
|
||||
// injected
|
||||
private Deque<String> queue;
|
||||
|
||||
public void addTag(String tag) {
|
||||
queue.push(tag);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
As shown in the preceding example, the consuming code is decoupled from the actual storage implementation. In fact, there is no indication that Redis is used underneath. This makes moving from development to production environments transparent and highly increases testability (the Redis implementation can be replaced with an in-memory one).
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
[[why-spring-redis]]
|
||||
= Why Spring Data Redis?
|
||||
|
||||
The Spring Framework is the leading full-stack Java/JEE application framework. It provides a lightweight container and a non-invasive programming model enabled by the use of dependency injection, AOP, and portable service abstractions.
|
||||
|
||||
https://en.wikipedia.org/wiki/NoSQL[NoSQL] storage systems provide an alternative to classical RDBMS for horizontal scalability and speed. In terms of implementation, key-value stores represent one of the largest (and oldest) members in the NoSQL space.
|
||||
|
||||
The Spring Data Redis (SDR) framework makes it easy to write Spring applications that use the Redis key-value store by eliminating the redundant tasks and boilerplate code required for interacting with the store through Spring's excellent infrastructure support.
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.examples;
|
||||
|
||||
// tag::file[]
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
public class ReactiveRedisApplication {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(ReactiveApplication.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory();
|
||||
connectionFactory.afterPropertiesSet();
|
||||
|
||||
ReactiveRedisTemplate<String, String> template = new ReactiveRedisTemplate<>(connectionFactory,
|
||||
RedisSerializationContext.string());
|
||||
|
||||
Mono<Boolean> set = template.opsForValue().set("foo", "bar");
|
||||
set.block(Duration.ofSeconds(10));
|
||||
|
||||
LOG.info("Value at foo:" + template.opsForValue().get("foo").block(Duration.ofSeconds(10)));
|
||||
|
||||
connectionFactory.destroy();
|
||||
}
|
||||
}
|
||||
// end::file[]
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.examples;
|
||||
|
||||
// tag::file[]
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
public class RedisApplication {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(RedisApplication.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory();
|
||||
connectionFactory.afterPropertiesSet();
|
||||
|
||||
RedisTemplate<String, String> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
template.setDefaultSerializer(StringRedisSerializer.UTF_8);
|
||||
template.afterPropertiesSet();
|
||||
|
||||
template.opsForValue().set("foo", "bar");
|
||||
|
||||
LOG.info("Value at foo:" + template.opsForValue().get("foo"));
|
||||
|
||||
connectionFactory.destroy();
|
||||
}
|
||||
}
|
||||
// end::file[]
|
||||
Reference in New Issue
Block a user