[[redis.repositories]]
= Redis Repositories

Working with Redis Repositories allows to seamlessly convert and store domain objects in Redis Hashes, apply custom mapping strategies and make use of secondary indexes.

WARNING: Redis Repositories requires at least Redis Server version 2.8.0.

[[redis.repositories.usage]]
== Usage

To access domain entities stored in a Redis you can leverage repository support that eases implementing those quite significantly.

.Sample Person Entity
====
[source,java]
----
@RedisHash("persons")
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 property named `id` annotated with `org.springframework.data.annotation.Id` and a `@RedisHash` annotation on its type.
Those two 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.

.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 according Spring configuration.

.JavaConfig for Redis Repositories
====
[source,java]
----
@Configuration
@EnableRedisRepositories
public class ApplicationConfig {

  @Bean
  public RedisConnectionFactory connectionFactory() {
    return new JedisConnectionFactory();
  }

  @Bean
  public RedisTemplate<?, ?> redisTemplate() {

    RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
    return template;
  }
}
----
====

Given the setup above we can go on and inject `PersonRepository` into our components.

.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 current value is `null` or reuses an already set id value and stores properties of type `Person` inside the Redis Hash with key with pattern `keyspace:id` in this case eg. `persons: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 _persons_ defined by `@RedisHash` on `Person`.
<4> Removes the key for the given object from Redis.
====

[[redis.repositories.mapping]]
== Object to Hash Mapping
The Redis Repository support persists Objects in 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 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.
====

[cols="1,2,3", options="header"]
.Default Mapping Rules
|===
| Type
| Sample
| Mapped Value

| Simple Type +
(eg. String)
| String firstname = "rand";
| firstname = "rand"

| Complex Type +
(eg. Address)
| Address adress = 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  = "...
|===

Mapping behavior can be customized by registering the according `Converter` in `CustomConversions`. Those converters can take care of converting from/to a single `byte[]` as well as `Map<String,byte[]>` whereas the first one is suitable for eg. converting one complex type to eg. a binary JSON representation that still uses the default mappings hash structure. The second option offers full control over the resulting hash. Writing objects to a Redis hash will delete the content from the hash and re-create the whole hash, so not mapped data will be lost.

.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 above byte[] `Converter` produces eg.
====
[source,text]
----
_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al’thor
address = { city : "emond's field", country : "andor" }
----
====


.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<Address, Map<String, byte[]>> {

  @Override
  public Address convert(Map<String,byte[]> source) {
    return new Address(new String(source.get("ciudad")));
  }
}
----
====

Using the above Map `Converter` produces eg.

====
[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. <<redis.repositories.indexes>> will still be created even for custom converted types.

[[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()`. This default can be altered via `@RedisHash` on aggregate root level or by setting up a programmatic configuration. However, the annotated keyspace supersedes any other configuration.

.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, "persons"));
    }
  }
}
----
====

.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 MyKeyspaceConfiguration(), new IndexConfiguration()));
  }

  public static class MyKeyspaceConfiguration extends KeyspaceConfiguration {

    @Override
    protected Iterable<KeyspaceSettings> initialConfiguration() {
      return Collections.singleton(new KeyspaceSettings(Person.class, "persons"));
    }
  }
}
----
====

[[redis.repositories.indexes]]
== Secondary Indexes
http://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

Given the sample `Person` entity we can create an index for _firstname_ by annotating the property with `@Indexed`.

.Annotation driven indexing
====
[source,java]
----
@RedisHash("persons")
public class Person {

  @Id String id;
  @Indexed String firstname;
  String lastname;
  Address address;
}
----
====

Indexes are built up for actual property values. Saving two Persons eg. "rand" and "aviendha" results in setting up indexes like below.

====
[source,text]
----
SADD persons:firstname:rand e2c7dcee-b8cd-4424-883e-736ce564363e
SADD persons: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.

====
[source,text]
----
SADD persons:address.city:tear e2c7dcee-b8cd-4424-883e-736ce564363e
----
====

Further more the programmatic setup allows to define indexes on map keys and list properties.

====
[source,java]
----
@RedisHash("persons")
public class Person {

  // ... other properties omitted

  Map<String,String> attributes;      <1>
  Map<String Person> relatives;       <2>
  List<Address> addresses;            <3>
}
----
<1> `SADD persons:attributes.map-key:map-value e2c7dcee-b8cd-4424-883e-736ce564363e`
<2> `SADD persons:relatives.map-key.firstname:tam e2c7dcee-b8cd-4424-883e-736ce564363e`
<3> `SADD persons:addresses.city:tear e2c7dcee-b8cd-4424-883e-736ce564363e`
====

WARNING: Indexes will not be resolved on <<redis.repositories.references,References>>.

Same as with _keyspaces_ it is possible to configure indexes without the need of annotating the actual domain type.

.Index Setup via @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("persons", "firstname"));
    }
  }
}
----
====

.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("persons", "firstname"));
    }
  }
}
----
====

[[redis.repositories.indexes.geospatial]]
=== Geospatial Index

Assume the `Address` type contains a property `location` of type `Point` that holds the geo coordinates of the particular address. By annotating the property with `@GeoIndexed` those values will be added using Redis `GEO` commands.

====
[source,java]
----
@RedisHash("persons")
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 nested property using Point and Distance.
<2> Query method declaration on nested property using Circle to search within.
<3> `GEOADD persons:address:location 13.361389 38.115556 e2c7dcee-b8cd-4424-883e-736ce564363e`
<4> `GEORADIUS persons:address:location 15.0 37.0 200.0 km`
====

In the above example the lon/lat values are stored using `GEOADD` using the objects `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`/`within` with other criteria.


[[redis.repositories.expirations]]
== Time To Live
Objects stored in Redis may only be valid 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 reached their end of life.
The expiration time in seconds can be set via `@RedisHash(timeToLive=...)` as well as via `KeyspaceSettings` (see <<redis.repositories.keyspaces>>).

More flexible expiration times can be set by using the `@TimeToLive` annotation on either a numeric property or method. However do not apply `@TimeToLive` on both a method and a property within the same class.

.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` will read back the actual `TTL` or `PTTL` value from Redis. -1 indicates that the object has no expire associated.

The repository implementation ensures subscription to http://redis.io/topics/notifications[Redis keyspace notifications] via `RedisMessageListenerContainer`.

When the expiration is set to a positive value the according `EXPIRE` command is executed.
Additionally to persisting the original, a _phantom_ copy is persisted in Redis and set to expire 5 minutes after the original one. This is done to enable the Repository support to publish `RedisKeyExpiredEvent` holding the expired value via Springs `ApplicationEventPublisher` whenever a key expires even though the original values have already been gone. Expiry events
will be received on all connected applications using 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 `EnableKeyspaceEvents` for possible values.

The `RedisKeyExpiredEvent` will hold a copy of the actually expired domain object as well as the key.

NOTE: Delaying or disabling the expiry event listener startup impacts `RedisKeyExpiredEvent` publishing.
A disabled event listener will not publish expiry events. A delayed startup can cause loss of events because the delayed
listener initialization.

NOTE: The keyspace notification message listener will alter `notify-keyspace-events` settings in Redis if those are not already set. Existing settings will not be overridden, so it is left to the user to set those up correctly when not leaving them empty. Please note that `CONFIG` is disabled on AWS ElastiCache and enabling the listener leads to an error.

NOTE: Redis Pub/Sub messages are not persistent. If a key expires while the application is down the expiry event will not be processed which may lead to secondary indexes containing still references to the expired object.

[[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.

.Sample Property Reference
====
[source,text]
----
_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al’thor
mother = persons:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56      <1>
----
<1> Reference stores the whole key (`keyspace:id`) of the referenced object.
====

WARNING: Referenced Objects are not subject of persisting changes when saving the referencing object. Please make sure to persist changes on referenced objects separately, since only the reference will be stored.
Indexes set on properties of referenced types will not be resolved.

[[redis.repositories.partial-updates]]
== Persisting Partial Updates
In some cases it is not necessary to load and rewrite the entire entity just to set a new value within it. A session timestamp for last active time might be such a scenario where you just want to alter one property.
`PartialUpdate` allows to define `set` and `delete` actions on existing objects while taking care of updating potential expiration times of the entity itself as well as index structures.

.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 property _firstname_ to _mat_.
<2> Set the simple property _address.city_ 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 property _age_.
<4> Set complex property _address_.
<5> Set a map/collection of values removes the previously existing map/collection and replaces the values with the given ones.
<6> Automatically update the server expiration time when altering <<redis.repositories.expirations>>.
====

NOTE: Updating complex objects as well as map/collection structures requires further interaction with Redis to determine existing values which means that it might turn out that rewriting the entire entity might be faster.

[[redis.repositories.queries]]
== Queries and Query Methods
Query methods allow automatic derivation of simple finder queries from the method name.

.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 execute. `RedisCallback` offers more control over the actual matching of index structures or even custom added ones. All it takes is providing a `RedisCallback` that returns a single or `Iterable` set of _id_ values.

.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);
----
====

Here's 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`
|`Top,First`|`findFirst10ByFirstname`,`findTop5ByFirstname`|
|===============
====

[[redis.repositories.cluster]]
== Redis Repositories running on Cluster

Using the Redis repository support in a clustered Redis environment is fine. Please see the <<cluster, Redis Cluster>> section for `ConnectionFactory` configuration details.
Still some considerations have to be done as the default key distribution will spread entities and secondary indexes through out the whole cluster and its slots.

[options = "header, autowidth"]
|===============
|key|type|slot|node
|persons:e2c7dcee-b8cd-4424-883e-736ce564363e|id for hash|15171|127.0.0.1:7381
|persons:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56|id for hash|7373|127.0.0.1:7380
|persons:firstname:rand|index|1700|127.0.0.1:7379
|
|===============
====

Some commands like `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 be useful to pin keyspaces to a single slot which allows to make use of Redis serverside computation right away.

[options = "header, autowidth"]
|===============
|key|type|slot|node
|{persons}:e2c7dcee-b8cd-4424-883e-736ce564363e|id for hash|2399|127.0.0.1:7379
|{persons}:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56|id for hash|2399|127.0.0.1:7379
|{persons}:firstname:rand|index|2399|127.0.0.1:7379
|
|===============
====

TIP: Define and pin keyspaces via `@RedisHash("{yourkeyspace}") to specific slots when using Redis cluster.

[[redis.repositories.cdi-integration]]
== CDI integration

Instances of the repository interfaces are usually created by a container, which Spring is the most natural choice when working with Spring Data. There's sophisticated support to easily set up Spring to create bean instances. Spring Data Redis ships with a custom CDI extension that allows using the repository abstraction in CDI environments. The extension is part of the JAR so all you need to do to activate it is dropping the Spring Data Redis JAR into your classpath.

You can now set up the infrastructure by implementing a CDI Producer for the `RedisConnectionFactory` and `RedisOperations`:

[source, java]
----
class RedisOperationsProducer {


  @Produces
  RedisConnectionFactory redisConnectionFactory() {

    JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
    jedisConnectionFactory.setHostName("localhost");
    jedisConnectionFactory.setPort(6379);
    jedisConnectionFactory.afterPropertiesSet();

    return jedisConnectionFactory;
  }

  void disposeRedisConnectionFactory(@Disposes RedisConnectionFactory redisConnectionFactory) throws Exception {

    if (redisConnectionFactory instanceof DisposableBean) {
      ((DisposableBean) redisConnectionFactory).destroy();
    }
  }

  @Produces
  @ApplicationScoped
  RedisOperations<byte[], byte[]> redisOperationsProducer(RedisConnectionFactory redisConnectionFactory) {

    RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
    template.setConnectionFactory(redisConnectionFactory);
    template.afterPropertiesSet();

    return template;
  }

}
----

The necessary setup can vary depending on the JavaEE environment you run in.

The Spring Data Redis CDI extension will pick up all Repositories available as CDI beans and create 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:

[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`.



