Spring Boot: Spring Data Redis, Redis OM & Custom Queries
|
This content was generated with the assistance of AI and should be verified against the official Redis documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Examples on this page target Redis Open Source 8.10 (8.10.2), Spring Boot 4.1, Spring Data Redis 4.1, Redis OM
Spring 1.0, Spring Session 4.1, and the Jedis 8 and Lettuce 7.7 client libraries. Spring Data Redis is the
general-purpose Spring Data module for Redis — RedisTemplate, opsFor*, and @RedisHash repositories — covered in the module table in Spring Data Overview. Redis OM Spring is a
separate, higher-level project layered on top of it that maps @Document entities onto the Query Engine index
covered in Query Engine: Indexing & Data Modeling, giving a Spring Data repository
real filtering, sorting, and aggregation instead of the derived-finder limits Spring Data Redis’s own
repositories have. This page covers both, how they relate, and where an application still needs to drop down to
a raw client.
RedisTemplate and StringRedisTemplate
RedisTemplate<K, V> is Spring Data Redis’s low-level access point, the same role MongoTemplate or
Neo4jClient play for their own stores in Spring Data Overview: it wraps a
RedisConnectionFactory, serializes keys and values through configurable RedisSerializer instances, and
exposes the full command surface through a set of opsFor* accessors rather than one flat method list.
StringRedisTemplate is a RedisTemplate<String, String> pre-configured with StringRedisSerializer on every
slot, the right choice whenever both keys and values are plain strings and no other serializer is needed.
package com.example.bookshop.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisTemplateConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(RedisSerializer.json());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(RedisSerializer.json());
return template;
}
}
Spring Boot’s auto-configuration already provides both beans against spring.data.redis.* properties once
spring-boot-starter-data-redis is on the classpath; the bean above is only needed to override the default
JDK serializer with a JSON one, the usual choice once values are domain objects rather than plain strings.
RedisSerializer.json() returns the Jackson 3 GenericJacksonJsonRedisSerializer. It replaces
GenericJackson2JsonRedisSerializer, which Spring Data Redis 4.0 deprecated for removal. Its trade-offs are
covered in Serializer choices. This
derives from
Spring Data Redis: template API.
The opsFor* APIs
Each opsFor* accessor scopes the template to one Redis data type’s commands, mirroring the raw command
families the rest of this section documents:
-
opsForValue()— strings, covered in Strings, Bitmaps & Bitfields:get,set,increment, and so on. -
opsForHash()— hashes, covered in Hashes:get,put,entries. -
opsForList(),opsForSet(),opsForZSet()— the collection types covered in Lists, Sets, Sorted Sets, Arrays & Geospatial:leftPush/rightPop,add/members,add/rangeByScore. -
opsForStream()— streams, covered in Streams, Pub/Sub & Keyspace Notifications:add,read,createGroup. The template-level API is enough for one-shot reads; the section below onStreamMessageListenerContainercovers the continuous-consumption path most Bookshop workers actually need. -
opsForGeo()— the geospatial commands, also in Lists, Sets, Sorted Sets, Arrays & Geospatial:add,radius,distance.
package com.example.bookshop.inventory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class StockService {
private final RedisTemplate<String, Object> redisTemplate;
public StockService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public Long decrementStock(String isbn, long quantity) {
return redisTemplate.opsForHash()
.increment("bookshop:bookhash:" + isbn, "stock", -quantity);
}
}
This derives from Spring Data Redis: template API.
@RedisHash repositories
Spring Data Redis’s repository abstraction persists a Java type as a hash, one field per property, the same programming model Spring Data Overview describes for JPA/MongoDB/Couchbase/ Neo4j repositories — but with a much narrower query surface, described below.
package com.example.bookshop.catalog;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Reference;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.TimeToLive;
import org.springframework.data.redis.core.index.Indexed;
import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.TimeUnit;
@RedisHash("book")
public class BookEntity {
@Id
private String isbn;
@Indexed
private String author;
private String title;
private List<String> genres;
private BigDecimal price;
private int year;
@Reference
private PublisherEntity publisher;
@TimeToLive(unit = TimeUnit.SECONDS)
private Long ttlSeconds;
// getters and setters omitted
}
-
@RedisHash("book")— names the hash-key prefix (book:<id>) and registers the type for repository support; Spring Data Redis additionally maintains a Set of every ID underbookforfindAll(). -
@Id— the property mapped to the key’s ID segment; generated automatically (as aStringUUID) if left unset. -
@Indexed— adds a secondary index (a Redis Set per distinct value) for that property, the only thing that makes a property queryable by a derived finder — see below. -
@TimeToLive— expires the whole hash automatically, the sameEXPIREsemantics Architecture, Keyspace & Eviction covers at the command level, applied declaratively per entity instead of a separateEXPIREcall. -
@Reference— stores only the referenced entity’s key and resolves it lazily on read, rather than embedding the referenced hash’s fields inline.
package com.example.bookshop.catalog;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
public interface BookRepository extends CrudRepository<BookEntity, String> {
List<BookEntity> findByAuthor(String author);
}
This derives from Spring Data Redis: repositories.
The limits of derived finders
findByAuthor above works only because author carries @Indexed; Spring Data Redis has no query engine of
its own to fall back on, so a derived finder against a non-indexed property, a range condition (findByYear
GreaterThan), sorting, or a @Query with a custom filter expression are all unsupported — every derived
method compiles, but only exact-match lookups against @Indexed properties actually work at runtime. This is
the sharpest contrast with the query and aggregation surface Query Engine: Search & Aggregations
covers for a Query Engine index: a @RedisHash repository’s indexes are plain Redis Sets built and maintained
by Spring Data itself, not FT.CREATE schema fields, so there is no TEXT relevance ranking, no NUMERIC
range query, and no aggregation pipeline behind it. An application that needs any of that — full-text search,
range filters, sorting by a non-ID field, vector similarity — reaches for Redis OM Spring instead, covered
below, which maps entities onto an actual Query Engine index rather than plain indexed Sets.
SessionCallback, executePipelined, and @Transactional
RedisTemplate exposes three ways to run more than one command as a unit, mirroring the raw primitives
Pipelining, Transactions & Scripting covers at the protocol level:
-
execute(SessionCallback<T> action)— runs the callback against a single, dedicated connection, so a hand-writtenMULTI/EXECblock (or aWATCH-based optimistic-locking retry, the same pattern that page builds in Lettuce) sees a consistent connection throughout instead of the template borrowing one connection per call. See Spring Data Redis: transactions. -
executePipelined(RedisCallback<?> action)— runs the callback’s commands as a client-side pipeline (the same transport-level batching that page’s pipelining section describes) and returns the collected replies as aList<Object>, in call order. -
setEnableTransactionSupport(true)plus@Transactional— binds the template to Spring’s ordinary declarative-transaction machinery: a@Transactionalmethod’s Redis writes are queued viaMULTIand onlyEXEC’d if the method returns normally, mirroring a JPA `@Transactionalmethod’s commit/rollback shape even though Redis transactions themselves have no rollback, as that page explains.
package com.example.bookshop.inventory;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class OrderService {
private final RedisTemplate<String, Object> redisTemplate;
public OrderService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public List<Object> reserveStock(String isbn, long quantity) {
return redisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
@SuppressWarnings("unchecked")
public List<Object> execute(org.springframework.data.redis.core.RedisOperations operations) {
operations.multi();
operations.opsForHash().increment("bookshop:bookhash:" + isbn, "stock", -quantity);
operations.opsForValue().increment("bookshop:stats:orders_total");
return operations.exec();
}
});
}
public List<Object> bulkLoad(List<String> isbns) {
return redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
for (String isbn : isbns) {
connection.hashCommands().hIncrBy(
("bookshop:bookhash:" + isbn).getBytes(), "stock".getBytes(), 0);
}
return null;
});
}
}
This derives from Spring Data Redis: transactions and Spring Data Redis: pipelining.
Custom queries: Lua, Functions, Pub/Sub, Streams, and keyspace events
RedisScript and classpath-resource scripts
RedisScript<T> wraps a Lua script’s source and its expected return type for RedisTemplate.execute; the Lua
syntax itself, KEYS/ARGV, and redis.call/redis.pcall semantics are covered in
Pipelining, Transactions & Scripting and not repeated here. The usual pattern loads
the script body from a classpath resource rather than an inline Java string, so a .lua file stays syntax-
highlighted and diffable on its own:
package com.example.bookshop.inventory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ReservationService {
private final RedisTemplate<String, Object> redisTemplate;
private final RedisScript<Long> reserveStockScript;
public ReservationService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
this.reserveStockScript = RedisScript.of(
new ClassPathResource("scripts/reserve-stock.lua"), Long.class);
}
public Long reserve(String isbn, long quantity) {
return redisTemplate.execute(reserveStockScript,
List.of("bookshop:bookhash:" + isbn), String.valueOf(quantity));
}
}
redisTemplate.execute transparently uses EVALSHA once the script is cached server-side and falls back to
EVAL on a NOSCRIPT reply, the same fallback that page describes at the command level, without application
code handling it explicitly. FCALL-invoked Functions have no dedicated Spring Data Redis wrapper and go
through the same execute(RedisCallback<T>)/low-level connection API the pipelining example above uses. This
derives from
Spring Data Redis: scripting.
Pub/Sub with RedisMessageListenerContainer
RedisMessageListenerContainer manages the dedicated subscriber connection Pub/Sub needs (covered at the
protocol level in Streams, Pub/Sub & Keyspace Notifications) and dispatches incoming messages to
registered MessageListener beans, rather than an application managing a raw subscribed connection and its own
listener thread.
package com.example.bookshop.cache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
@Configuration
public class InvalidationListenerConfig {
@Bean
public RedisMessageListenerContainer invalidationListenerContainer(
RedisConnectionFactory connectionFactory, CacheInvalidationListener listener) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(
new MessageListenerAdapter(listener, "onMessage"),
new ChannelTopic("bookshop:invalidate"));
return container;
}
}
This derives from Spring Data Redis: Pub/Sub messaging.
Streams with StreamMessageListenerContainer
StreamMessageListenerContainer is the continuous-consumption counterpart to opsForStream(): it polls
XREADGROUP in a background thread (or reactively) and dispatches each delivered record to a
StreamListener, taking care of the read loop the raw XREADGROUP examples in
Streams, Pub/Sub & Keyspace Notifications otherwise write by hand. The Bookshop fulfilment workers,
worker-1 and worker-2, both read from the fulfilment consumer group on bookshop:orders:
package com.example.bookshop.fulfilment;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.stream.Consumer;
import org.springframework.data.redis.connection.stream.MapRecord;
import org.springframework.data.redis.connection.stream.StreamOffset;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.stream.StreamMessageListenerContainer;
import org.springframework.data.redis.stream.StreamMessageListenerContainer.StreamMessageListenerContainerOptions;
import java.time.Duration;
public class FulfilmentWorkerConfig {
public StreamMessageListenerContainer<String, MapRecord<String, String, String>> fulfilmentContainer(
RedisConnectionFactory connectionFactory, StringRedisTemplate redisTemplate) {
StreamMessageListenerContainerOptions<String, MapRecord<String, String, String>> options =
StreamMessageListenerContainerOptions.builder()
.pollTimeout(Duration.ofSeconds(2))
.build();
StreamMessageListenerContainer<String, MapRecord<String, String, String>> container =
StreamMessageListenerContainer.create(connectionFactory, options);
container.receive(
Consumer.from("fulfilment", "worker-1"),
StreamOffset.create("bookshop:orders", org.springframework.data.redis.connection.stream.ReadOffset.lastConsumed()),
message -> {
fulfilOrder(message.getValue());
redisTemplate.opsForStream().acknowledge("bookshop:orders", "fulfilment", message.getId());
});
container.start();
return container;
}
private void fulfilOrder(java.util.Map<String, String> orderFields) {
// process order_id / isbn / qty
}
}
worker-2 registers a second receive(…) call against Consumer.from("fulfilment", "worker-2") on the same
container, so both consumers share the group’s work as the diagram below shows. Acknowledgment is left to the
listener itself (as above) rather than left to StreamMessageListenerContainer’s optional auto-ack mode, since
the Bookshop worker only wants an entry removed from the PEL once `fulfilOrder has genuinely completed. This
derives from
Spring Data Redis: Streams.
Keyspace-event listeners
A keyspace-event listener is an ordinary RedisMessageListenerContainer subscription against the
keyevent@<db>:* pattern channels Streams, Pub/Sub & Keyspace Notifications describes, with
notify-keyspace-events configured the same way:
package com.example.bookshop.cache;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
@Component
public class ExpiredKeyListener implements MessageListener {
@Override
public void onMessage(Message message, byte[] pattern) {
String expiredKey = message.toString();
// evict any local (near-cache) copy tied to expiredKey
}
}
Registered against the pattern topic keyevent@0:expired on the same
RedisMessageListenerContainer shown above, via
container.addMessageListener(listener, new PatternTopic("keyevent@0:expired")). This derives from
Spring Data Redis: Pub/Sub messaging and
Redis keyspace notifications.
ReactiveRedisTemplate
ReactiveRedisTemplate<K, V> mirrors RedisTemplate’s `opsFor* shape over Mono/Flux instead of blocking
calls, backed by Lettuce’s native reactive command support — Jedis has no reactive driver, so a reactive
Bookshop path needs Lettuce as its connection factory. Near-Far Caches already
builds and uses one in depth for a reactive near/far cache; this page only notes it as the reactive counterpart
of everything above rather than repeating that setup.
package com.example.bookshop.catalog;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import reactor.core.publisher.Mono;
public class ReactiveStockService {
private final ReactiveRedisTemplate<String, String> reactiveRedisTemplate;
public ReactiveStockService(ReactiveRedisTemplate<String, String> reactiveRedisTemplate) {
this.reactiveRedisTemplate = reactiveRedisTemplate;
}
public Mono<Long> decrementStock(String isbn, long quantity) {
return reactiveRedisTemplate.opsForHash()
.increment("bookshop:bookhash:" + isbn, "stock", -quantity);
}
}
This derives from Spring Data Redis: reactive template API.
Redis OM Spring
Redis OM Spring layers object mapping and a real Query Engine-backed repository on top of Spring Data Redis,
closing the derived-finder gap noted above by building and maintaining an FT.CREATE index
(Query Engine: Indexing & Data Modeling) from annotated entity classes, instead of
plain Redis Sets.
@Document and field annotations
package com.example.bookshop.catalog;
import com.redis.om.spring.annotations.Document;
import com.redis.om.spring.annotations.Indexed;
import com.redis.om.spring.annotations.Searchable;
import com.redis.om.spring.annotations.TagIndexed;
import com.redis.om.spring.annotations.NumericIndexed;
import com.redis.om.spring.annotations.GeoIndexed;
import org.springframework.data.annotation.Id;
import java.math.BigDecimal;
import java.util.List;
@Document
public record Book(
@Id String isbn,
@Searchable String title,
@Searchable String author,
@TagIndexed List<String> genres,
@NumericIndexed BigDecimal price,
@NumericIndexed int year,
@GeoIndexed String warehouseLocation) {
}
-
@Document— maps the type onto a Query Engine index over JSON documents, the JSON-backed indexing style Query Engine: Indexing & Data Modeling documents at theFT.CREATElevel. -
@Indexed— the general-purpose annotation for an exact-match indexed field, roughly Redis OM Spring’s counterpart to a plainTAG/NUMERICfield. -
@Searchable— a full-textTEXTfield, tokenized and scored the waytitle/authorare on theidx:booksschema. -
@TagIndexed— an explicitTAGfield, the annotationgenresabove uses to match the plural, multi-valuedgenresfield on that same schema. -
@NumericIndexed— aNUMERICfield, usable for range queries and sorting. -
@GeoIndexed— aGEOfield, matchingwarehouse_locationon that schema.
Redis OM Spring 1.0’s exact annotation set and package names should be checked against its current reference docs before relying on them in a new project; the names above are its well-known, long-standing annotation vocabulary.
RedisDocumentRepository, @Query, @Aggregation, and EntityStream
package com.example.bookshop.catalog;
import com.redis.om.spring.annotations.Aggregation;
import com.redis.om.spring.annotations.Query;
import com.redis.om.spring.repository.RedisDocumentRepository;
import java.util.List;
public interface BookDocumentRepository extends RedisDocumentRepository<Book, String> {
List<Book> findByGenres(String genre);
@Query("(@genres:{$genre})=>[KNN 5 @embedding $vec AS score]")
List<Book> findSimilarInGenre(String genre, byte[] vec);
@Aggregation("FT.AGGREGATE idx:books * GROUPBY 1 @genres REDUCE COUNT 0 AS book_count")
List<Object> countByGenre();
}
findByGenres is an ordinary derived finder, but — unlike the @RedisHash case above — it works against
any field the @Document schema indexes, with the full range/sort/pagination vocabulary Spring Data’s derived
finders support elsewhere, because it compiles down to a real FT.SEARCH query rather than a Set lookup.
@Query drops to a raw query string for anything a derived finder can’t express, including the KNN vector
syntax Vector Search: Query Engine & Vector Sets covers; @Aggregation does the same for the FT.AGGREGATE
pipeline Query Engine: Search & Aggregations covers.
EntityStream is Redis OM Spring’s fluent, type-safe alternative to writing those query strings by hand,
generating FT.SEARCH calls from a Java Stream-like pipeline over metamodel fields instead:
package com.example.bookshop.catalog;
import com.redis.om.spring.search.stream.EntityStream;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.List;
@Service
public class BookSearchService {
private final EntityStream entityStream;
public BookSearchService(EntityStream entityStream) {
this.entityStream = entityStream;
}
public List<Book> cheapFictionBooks(BigDecimal maxPrice) {
return entityStream.of(Book.class)
.filter(Book$.GENRES.eq("fiction"))
.filter(Book$.PRICE.le(maxPrice))
.sorted(Book$.PRICE)
.collect(java.util.stream.Collectors.toList());
}
}
Book$ is a metamodel class Redis OM Spring generates at build time (one static field per @Document
property), the same annotation-processor pattern the Query DSL/JPA metamodel world uses elsewhere in this
site’s Spring Data Overview. This section derives from
Redis OM Spring.
Dropping down to UnifiedJedis for raw FT./JSON./TS.*
Not every Query Engine or JSON/Time Series feature has a Redis OM Spring wrapper — an uncommon FT.CREATE
option, a TS.* command (Probabilistic Structures & Time Series), or a brand-new command a
given Redis OM Spring release hasn’t caught up to yet. UnifiedJedis is the base type of the Jedis 8
RedisClient that Vector Search: Query Engine & Vector Sets and
Query Engine: Indexing & Data Modeling use directly. It is available as an injectable bean and covers the raw command surface with no abstraction between
the call and the wire protocol:
package com.example.bookshop.search;
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.search.Query;
import redis.clients.jedis.search.SearchResult;
import org.springframework.stereotype.Service;
@Service
public class RawQueryEngineService {
private final UnifiedJedis jedis;
public RawQueryEngineService(UnifiedJedis jedis) {
this.jedis = jedis;
}
public SearchResult rawSearch(String queryString) {
return jedis.ftSearch("idx:books", new Query(queryString));
}
}
This is the same escape hatch Vector Search: Query Engine & Vector Sets uses for FT.SEARCH/KNN calls that
have no higher-level wrapper; Redis OM Spring auto-configures a UnifiedJedis bean alongside its own
repositories precisely so an application never has to stand up a second, unmanaged client just to reach a
command it hasn’t wrapped yet.
Spring Session Data Redis
Spring Session Data Redis backs HttpSession with Redis instead of an in-memory or sticky-session store, the
session store option Logout and Session Management references for a BFF deployment
that needs to look up and delete a local session by sid/sub on a back-channel logout. The Bookshop
storefront keys its sessions under namespace bookshop:session, so a session lands at
bookshop:session:sessions:<id>:
spring:
session:
store-type: redis
redis:
namespace: bookshop:session
flush-mode: on-save
save-mode: on-set-attribute
-
namespace— the key prefix Spring Session uses for every session-related key,bookshop:sessionabove, producingbookshop:session:sessions:<id>per session and a companion expiration-tracking key. -
flush-mode—on-save(the default) writes session changes back to Redis once per request, at the end of the servlet filter chain;immediatewrites on every attribute mutation instead, at the cost of more round trips. -
save-mode—on-set-attribute(the default) tracks only attributes explicitly set on the session for the next save,alwaysre-persists every attribute on each save regardless of whether it changed.
Enabling it needs only @EnableRedisHttpSession (or, with Spring Boot auto-configuration and
spring-session-data-redis on the classpath, no explicit annotation at all once store-type: redis is set):
package com.example.bookshop.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@Configuration
@EnableRedisHttpSession(redisNamespace = "bookshop:session")
public class SessionConfig {
}
This derives from Spring Session: Redis.
@DataRedisTest and Testcontainers
@DataRedisTest is Spring Boot’s test slice for Redis: it configures a RedisConnectionFactory and every
Spring Data Redis bean the slice needs (RedisTemplate, repositories) without loading the rest of the
application context, the same test-slice pattern used elsewhere in
Spring Data Overview for other stores. Paired with the
Testcontainers Redis module, it runs against a real, disposable
Redis server rather than an embedded fake:
package com.example.bookshop.catalog;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.beans.factory.annotation.Autowired;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import com.redis.testcontainers.RedisContainer;
import org.testcontainers.utility.DockerImageName;
import static org.assertj.core.api.Assertions.assertThat;
@DataRedisTest
@Testcontainers
class BookRepositoryIT {
@Container
@ServiceConnection
static RedisContainer redis = new RedisContainer(DockerImageName.parse("redis:8.10.2"));
@Autowired
private BookRepository bookRepository;
@Test
void savesAndFindsByAuthor() {
BookEntity book = new BookEntity();
book.setIsbn("9780000000001");
book.setAuthor("Martin Kleppmann");
bookRepository.save(book);
assertThat(bookRepository.findByAuthor("Martin Kleppmann")).hasSize(1);
}
}
@ServiceConnection wires the container’s connection details into the test context automatically, the same
mechanism Spring Boot’s other Testcontainers integrations use, so no manual spring.data.redis.host/port
property override is needed. This derives from
Spring
Boot: auto-configured Data Redis tests and
Testcontainers: Redis module.
Stream consumer-group read/ack flow
The diagram below shows StreamMessageListenerContainer’s continuous read loop against `bookshop:orders,
group fulfilment, with both worker-1 and worker-2 receiving and acknowledging entries independently:
Related pages
-
Pipelining, Transactions & Scripting — the raw
MULTI/EXEC/WATCHand LuaEVAL/Functions semanticsSessionCallback,@Transactional, andRedisScriptbuild on above. -
Streams, Pub/Sub & Keyspace Notifications — the raw
XADD/XREADGROUP/XACKandPUBLISH/SUBSCRIBEcommand semanticsStreamMessageListenerContainerandRedisMessageListenerContainerbuild on above. -
Query Engine: Indexing & Data Modeling — the
idx:booksschema Redis OM Spring’s@Documentmapping andEntityStreamqueries run against. -
Vector Search: Query Engine & Vector Sets — the
KNN/VECTOR_RANGEquery syntax used in the@Queryexample above. -
Spring Data Overview — the general Spring Data programming model (repository vs. template/client, derived finders, auditing) that Spring Data Redis and Redis OM Spring both specialize.
-
Logout and Session Management — the session-store role Spring Session Data Redis plays in a back-channel-logout / BFF topology.