Polishing.

Reformat documentation.

See #2181
This commit is contained in:
Mark Paluch
2021-10-13 14:30:46 +02:00
parent 29acd79ee6
commit e1c1970629

View File

@@ -3,7 +3,8 @@
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 <<tx.spring,disabled transaction support>>.
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 <<tx.spring,disabled transaction support>>.
[[redis.repositories.usage]]
== Usage
@@ -25,9 +26,12 @@ public class Person {
----
====
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.
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.
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:
@@ -41,7 +45,8 @@ 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:
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
====
@@ -89,6 +94,7 @@ public void basicCrudOperations() {
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`.
@@ -99,7 +105,10 @@ include::../{spring-data-commons-include}/object-mapping.adoc[leveloffset=+1]
[[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[]`.
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:
@@ -113,6 +122,7 @@ lastname = althor
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.
@@ -120,7 +130,7 @@ address.country = andor
The following table describes the default mapping rules:
[cols="1,2,3", options="header"]
[cols="1,2,3",options="header"]
.Default Mapping Rules
|===
| Type
@@ -157,18 +167,21 @@ atts.[hair-color] = "...
of Complex Type
| List<Address> addresses = asList(new Address("em...
| addresses.[0].city = "emond's field", +
addresses.[1].city = "...
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 = "...
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.
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.
@@ -215,6 +228,7 @@ public class BytesToAddressConverter implements Converter<byte[], Address> {
====
Using the preceding byte array `Converter` produces output similar to the following:
====
[source,text]
----
@@ -269,7 +283,9 @@ NOTE: Custom conversions have no effect on index resolution. <<redis.repositorie
=== 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`.
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:
@@ -326,8 +342,11 @@ class SampleRedisConfiguration {
[[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.
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:
@@ -383,7 +402,9 @@ public class ApplicationConfig {
[[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 <<redis.repositories.expirations,expire>>.
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 <<redis.repositories.expirations,expire>>.
[[redis.repositories.indexes.simple]]
=== Simple Property Index
@@ -405,7 +426,8 @@ public class Person {
----
====
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:
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]
@@ -415,7 +437,9 @@ 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:
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]
@@ -439,6 +463,7 @@ public class Person {
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`
@@ -502,7 +527,8 @@ public class ApplicationConfig {
[[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:
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]
@@ -535,13 +561,15 @@ 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.
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.
@@ -550,9 +578,14 @@ include::query-by-example.adoc[leveloffset=+1]
[[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 <<redis.repositories.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:
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 <<redis.repositories.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
====
@@ -584,22 +617,35 @@ NOTE: Annotating a property explicitly with `@TimeToLive` reads back the actual
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.
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/{revnumber}/api/org/springframework/data/redis/core/RedisKeyValueAdapter.EnableKeyspaceEvents.html[`EnableKeyspaceEvents`] for possible values.
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/{revnumber}/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: 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: 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: 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.
[[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:
@@ -613,16 +659,21 @@ firstname = rand
lastname = althor
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.
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:
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
====
@@ -647,8 +698,10 @@ update = new PartialUpdate<Person>("e2c7dcee", Person.class)
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.
<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.
@@ -677,7 +730,8 @@ 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:
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
====
@@ -729,6 +783,7 @@ interface PersonRepository extends RedisRepository<Person, String> {
List<Person> findByFirstname(String firstname, Sort sort); <2>
}
----
<1> Static sorting derived from method name.
<2> Dynamic sorting using a method argument.
====
@@ -736,7 +791,9 @@ interface PersonRepository extends RedisRepository<Person, String> {
[[redis.repositories.cluster]]
== Redis Repositories Running on a Cluster
You can use the Redis repository support in a clustered Redis environment. See the "`<<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.
You can use the Redis repository support in a clustered Redis environment.
See the "`<<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):
@@ -748,9 +805,13 @@ The following table shows the details of data on a cluster (based on previous ex
|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):
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"]
|===============
@@ -767,11 +828,14 @@ TIP: Define and pin keyspaces by using `@RedisHash("{yourkeyspace}")` to specifi
[[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.
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]
[source,java]
----
class RedisOperationsProducer {
@@ -808,9 +872,10 @@ class RedisOperationsProducer {
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:
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]
[source,java]
----
class RepositoryClient {
@@ -823,16 +888,16 @@ class RepositoryClient {
}
----
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`.
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`.
[[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.
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.
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:
@@ -860,18 +925,19 @@ public class Address {
=== Insert new
====
[source, java]
[source,java]
----
repository.save(new Person("rand", "al'thor"));
----
[source, text]
[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.
@@ -882,12 +948,12 @@ SADD "people:19315449-cda2-4f5c-b696-9cb8018fa1f9:idx" "people:firstname:rand"
=== Replace existing
====
[source, java]
[source,java]
----
repository.save(new Person("e82908cf-e7d3-47c2-9eec-b4e0967ad0c9", "Dragon Reborn", "al'thor"));
----
[source, text]
[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>
@@ -899,6 +965,7 @@ DEL "people:e82908cf-e7d3-47c2-9eec-b4e0967ad0c9:idx"
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.
@@ -913,15 +980,16 @@ SADD "people:e82908cf-e7d3-47c2-9eec-b4e0967ad0c9:idx" "people:firstname:Dr
[[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:
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]
[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.
====
@@ -930,17 +998,18 @@ SADD "people:76900e94-b057-44bc-abcf-8126d51a621b:idx" "people:hometown:locati
=== Find using simple index
====
[source, java]
[source,java]
----
repository.findByFirstname("egwene");
----
[source, text]
[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.
====
@@ -949,17 +1018,18 @@ HGETALL ...
=== Find using Geo Index
====
[source, java]
[source,java]
----
repository.findByHometownLocationNear(new Point(15, 37), new Distance(200, KILOMETERS));
----
[source, text]
[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.
====