One of the key-value stores supported by Spring Data is Redis. To quote the Redis project home page:
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.
An easy way to setting up a working environment is to create a Spring-based project in 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 filesdependencies
element:<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
<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:<repositories> <repository> <id>spring-milestone</id> <name>Spring Maven MILESTONE Repository</name> <url>https://repo.spring.io/libs-milestone</url> </repository> </repositories>
The repository is also browseable here.
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.
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.
RedisConnection
provides the core building block for Redis communication, as it handles the communication with the Redis back end. It also automatically translates the underlying connecting library exceptions to Spring’s consistent DAO exception hierarchy so that you can switch the 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 section in the Spring Framework documentation.
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:
Supported Feature | Lettuce | Jedis |
---|---|---|
Standalone Connections |
X |
X |
Master/Replica Connections |
X |
|
Redis Sentinel |
Master Lookup, Sentinel Authentication, Replica Reads |
Master Lookup |
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 |
X (using |
Other Connection Features |
Singleton-connection sharing for non-blocking commands |
|
SSL Support |
X |
X |
X |
X |
|
X |
X |
|
X |
X |
|
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 |
Lettuce is a Netty-based open-source connector supported by Spring Data Redis through the org.springframework.data.redis.connection.lettuce
package.
dependencies
element:<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:
@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 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
:
@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. |
Jedis is a community-driven connector supported by the Spring Data Redis module through the org.springframework.data.redis.connection.jedis
package.
dependencies
element:<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:
@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:
@Configuration
class RedisConfiguration {
@Bean
public JedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("server", 6379);
return new JedisConnectionFactory(config);
}
}
The Redis Master/Replica setup — without automatic failover (for automatic failover see: Sentinel) — not only allows data to be safely stored at more nodes. It also allows, by using 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:
@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.
|
For dealing with high-availability Redis, Spring Data Redis has support for Redis Sentinel, using RedisSentinelConfiguration
, as shown in the following example:
/**
* Jedis
*/
@Bean
public RedisConnectionFactory jedisConnectionFactory() {
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
.master("mymaster")
.sentinel("127.0.0.1", 26379)
.sentinel("127.0.0.1", 26380);
return new JedisConnectionFactory(sentinelConfig);
}
/**
* Lettuce
*/
@Bean
public RedisConnectionFactory lettuceConnectionFactory() {
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
.master("mymaster")
.sentinel("127.0.0.1", 26379)
.sentinel("127.0.0.1", 26380);
return new LettuceConnectionFactory(sentinelConfig);
}
Tip
|
Configuration Properties
|
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.
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 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:
Interface | Description |
---|---|
Key Type Operations |
|
|
Redis geospatial operations, such as |
|
Redis hash operations |
|
Redis HyperLogLog operations, such as |
|
Redis list operations |
|
Redis set operations |
|
Redis string (or value) operations |
|
Redis zset (or sorted set) operations |
Key Bound Operations |
|
|
Redis key bound geospatial operations |
|
Redis hash key bound operations |
|
Redis key bound operations |
|
Redis list key bound operations |
|
Redis set key bound operations |
|
Redis string (or value) key bound operations |
|
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 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:
<?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="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use-pool="true"/>
<!-- redis template definition -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="jedisConnectionFactory"/>
...
</beans>
public class Example {
// inject the actual template
@Autowired
private RedisTemplate<String, String> template;
// inject the template as ListOperations
@Resource(name="redisTemplate")
private ListOperations<String, String> listOps;
public void addLink(String userId, URL url) {
listOps.leftPush(userId, url.toExternalForm());
}
}
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:
<?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="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use-pool="true"/>
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" p:connection-factory-ref="jedisConnectionFactory"/>
...
</beans>
public class Example {
@Autowired
private StringRedisTemplate redisTemplate;
public void addLink(String userId, URL url) {
redisTemplate.opsForList().leftPush(userId, url.toExternalForm());
}
}
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:
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");
}
});
}
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 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
andRedisElementWriter
.
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 forRedisCache
andRedisTemplate
. -
the
StringRedisSerializer
.
However one can use OxmSerializer
for Object/XML mapping through Spring OXM support or Jackson2JsonRedisSerializer
or GenericJackson2JsonRedisSerializer
for storing data in 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, If you are concerned about security vulnerabilities due to Java serialization, consider the general-purpose serialization filter mechanism at the core JVM level, originally developed for JDK 9 but backported to JDK 8, 7, and 6: |
Data can be stored by using various data structures within Redis. Jackson2JsonRedisSerializer
can convert objects in 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 serializer -
Using [redis.repositories]
-
Using
HashMapper
andHashOperations
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 BeanUtils. -
ObjectHashMapper
using [redis.repositories.mapping].
The following example shows one way to implement hash mapping:
public class Person {
String firstname;
String lastname;
// …
}
public class HashMapping {
@Autowired
HashOperations<String, byte[], byte[]> hashOperations;
HashMapper<Object, byte[], byte[]> mapper = new ObjectHashMapper();
public void writeHash(String key, Person person) {
Map<byte[], byte[]> mappedHash = mapper.toHash(person);
hashOperations.putAll(key, mappedHash);
}
public Person loadHash(String key) {
Map<byte[], byte[]> loadedHash = hashOperations.entries("key");
return (Person) mapper.fromHash(loadedHash);
}
}
Jackson2HashMapper
provides Redis Hash mapping for domain objects by using 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:
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:
Hash Field | Value |
---|---|
firstname |
|
lastname |
|
address |
|
date |
|
localDateTime |
|
The following table shows how the data in the preceding class would appear in flat mapping:
Hash Field | Value |
---|---|
firstname |
|
lastname |
|
address.city |
|
address.country |
|
date |
|
localDateTime |
|
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.
|
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 atomic counters and JDK 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
:
<?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
:
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).