Object Mapping (and more) for Redis!
Redis OM Spring extends Spring Data Redis to take full advantage of the power of Redis.
Stage | Release | Snapshot | Issues | Resolution | Code QL | License | SDR Ver. |
---|---|---|---|---|---|---|---|
Learn / Discuss / Collaborate
Discord | Twitch | YouTube | |
---|---|---|---|
Table of contents
๐ก Why Redis OM?
The Redis OM family of projects aim is to provide high-level abstractions idiomatically implemented for your language/platform of choice. We currently cater to the Node, Python, .Net and Spring communities.
๐ Redis OM Spring
Redis OM Spring provides powerful repository and custom object-mapping abstractions built on top of the powerful Spring Data Redis (SDR) framework.
This preview release provides all of SDRs capabilities plus:
@Document
annotation to map Spring Data models to Redis JSON documents- Enhances SDRs
@RedisHash
via@EnableRedisEnhancedRepositories
to:- uses Redis' native search engine (RediSearch) for secondary indexing
- uses ULID for
@Id
annotated fields
RedisDocumentRepository
with automatic implementation of Repository interfaces for complex querying capabilities using@EnableRedisDocumentRepositories
- Declarative Search Indices via
@Indexed
- Full-text Search Indices via
@Searchable
EntityStream
s: Streams-based Query and Aggregations Builder@Bloom
annotation to determine very fast, with and with high degree of certainty, whether a value is in a collection.@Vectorize
annotation to generate embeddings for text and images for use in Vector Similarity Searches- Vector Similarity Search API (See Redis Stack Vectors)
Note: Redis OM Spring depends on Jedis.
๐ Getting Started
Here is a quick teaser of an application using Redis OM Spring to map a Spring Data model using a RedisJSON document.
๐ Launch Redis
Redis OM Spring relies on the power of the RediSearch and RedisJSON modules. Before writing any code you'll need a Redis instance with the appropriate Redis modules! The quickest way to get this is with Docker:
docker run -p 6379:6379 -p 8001:8001 redis/redis-stack
This launches the redis-stack an extension of Redis that adds all manner of modern data
structures to Redis. You'll also notice that if you open up http://localhost:8001
you'll have access to the
redis-insight GUI, a GUI you can use to visualize and work with your data in Redis.
We have also provided a docker compose YAML file for you to quickly get started
using Redis Stack.
To launch the docker compose application, on the command line (or via Docker Desktop), clone this repository and run (from the root folder):
docker compose up
Configuring your Redis Connection
By default, Redis OM Spring connects to localhost
at port 6379
. If
your instance is running somewhere else, you can configure the connection
in your application.properties
or application.yaml
:
In applications.properties
:
spring.data.redis.host=your.cloud.db.redislabs.com |
spring.data.redis.port=12345
spring.data.redis.password=xxxxxxxx
spring.data.redis.username=default
In applications.yaml
:
spring:
data:
redis:
host: your.cloud.db.redislabs.com
port: 12345
password: xxxxxxxx
username: default
The SpringBoot App
Use the @EnableRedisDocumentRepositories
annotation to scan for @Document
annotated Spring models,
Inject repositories beans implementing RedisDocumentRepository
which you can use for CRUD operations and custom queries (all by declaring Spring Data Query Interfaces):
package com.redis.om.documents;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.geo.Point;
import com.redis.om.documents.domain.Company;
import com.redis.om.documents.repositories.CompanyRepository;
@SpringBootApplication
@Configuration
@EnableRedisDocumentRepositories(basePackages = "com.redis.om.documents.*")
public class RomsDocumentsApplication {
@Autowired
CompanyRepository companyRepo;
@Bean
CommandLineRunner loadTestData() {
return args -> {
// remove all companies
companyRepo.deleteAll();
// Create a couple of `Company` domain entities
Company redis = Company.of(
"Redis", "https://redis.com", new Point(-122.066540, 37.377690), 526, 2011 //
);
redis.setTags(Set.of("fast", "scalable", "reliable"));
Company microsoft = Company.of(
"Microsoft", "https://microsoft.com", new Point(-122.124500, 47.640160), 182268, 1975 //
);
microsoft.setTags(Set.of("innovative", "reliable"));
// save companies to the database
companyRepo.save(redis);
companyRepo.save(microsoft);
};
}
public static void main(String[] args) {
SpringApplication.run(RomsDocumentsApplication.class, args);
}
}
๐โโ๏ธ The Mapped Model
Like many other Spring Data projects, an annotation at the class level determines how instances
of the class are persisted. Redis OM Spring provides the @Document
annotation to persist models as JSON documents using RedisJSON:
package com.redis.om.documents.domain;
import java.util.HashSet;
import java.util.Set;
import org.springframework.data.annotation.Id;
import org.springframework.data.geo.Point;
import com.redis.om.spring.annotations.Document;
import com.redis.om.spring.annotations.Searchable;
import lombok.*;
@Data @NoArgsConstructor @RequiredArgsConstructor(staticName = "of") @AllArgsConstructor(access = AccessLevel.PROTECTED) @Document public class Company {
@Id private String id;
@Searchable private String name;
@Indexed private Point location;
@Indexed private Set<String> tags = new HashSet<>();
@Indexed private Integer numberOfEmployees;
@Indexed private Integer yearFounded;
private String url;
private boolean publiclyListed;
// ...
}
Redis OM Spring, replaces the conventional UUID
primary key strategy generation with a ULID
(Universally Unique Lexicographically Sortable Identifier) which is faster to generate and easier on the eyes.
๐งฐ The Repository
Redis OM Spring data repository's goal, like other Spring Data repositories, is to significantly reduce the amount of boilerplate code required to implement data access. Simply create a Java interface
that extends RedisDocumentRepository
that takes the domain class to manage as well as the ID type of the domain class as type arguments. RedisDocumentRepository
extends the Spring Data class PagingAndSortingRepository
.
Declare query methods on the interface. You can both, expose CRUD methods or create declarations for complex queries that Redis OM Spring will fulfill at runtime:
package com.redis.om.documents.repositories;
import java.util.*;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
import org.springframework.data.repository.query.Param;
import com.redis.om.documents.domain.Company;
import com.redis.om.spring.annotations.Query;
import com.redis.om.spring.repository.RedisDocumentRepository;
public interface CompanyRepository extends RedisDocumentRepository<Company, String> {
// find one by property
Optional<Company> findOneByName(String name);
// geospatial query
Iterable<Company> findByLocationNear(Point point, Distance distance);
// find by tag field, using JRediSearch "native" annotation
@Query("@tags:{$tags}")
Iterable<Company> findByTags(@Param("tags") Set<String> tags);
// find by numeric property
Iterable<Company> findByNumberOfEmployees(int noe);
// find by numeric property range
Iterable<Company> findByNumberOfEmployeesBetween(int noeGT, int noeLT);
// starting with/ending with
Iterable<Company> findByNameStartingWith(String prefix);
}
The repository proxy has two ways to derive a store-specific query from the method name:
- By deriving the query from the method name directly.
- By using a manually defined query using the
@Query
or@Aggregation
annotations.
๐ค Querying with Entity Streams
Redis OM Spring Entity Streams provides a Java 8 Streams interface to Query Redis JSON documents using RediSearch. Entity Streams allow you to process data in a type safe declarative way similar to SQL statements. Streams can be used to express a query as a chain of operations.
Entity Streams in Redis OM Spring provides the same semantics as Java 8 streams. Streams can be made of Redis Mapped entities (@Document
) or one or more properties of an Entity. Entity Streams progressively build the query until a terminal operation is invoked (such as collect
). Whenever a Terminal operation is applied to a Stream, the Stream cannot accept additional operations to its pipeline and it also means that the Stream is started.
Let's start with a simple example, a Spring @Service
which includes EntityStream
to query for instances of the mapped class Person
:
package com.redis.om.skeleton.services;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.redis.om.skeleton.models.Person;
import com.redis.om.skeleton.models.Person$;
import com.redis.om.spring.search.stream.EntityStream;
@Service
public class PeopleService {
@Autowired
EntityStream entityStream;
// Find all people
public Iterable<Person> findAllPeople() {
return entityStream //
.of(Person.class) //
.collect(Collectors.toList());
}
}
The EntityStream
is injected into the PeopleService
using @Autowired
. We can then get a stream for Person
objects by using entityStream.of(Person.class)
. At this point the stream represents the equivalent of a SELECT * FROM Person
on a relational database. The call to collect
will then execute the underlying query and return a collection of all Person
objects in Redis.
๐ญ Entity Meta-model
To produce more elaborate queries, you're provided with a generated metamodel, which is a class with the same name as your model but ending with a dollar sign. In the
example below, our entity model is Person
therefore we get a metamodel named Person$
. With the metamodel you have access to the operations related to the
underlying search engine field. For example, in the example we have an age
property which is an integer. Therefore, our metamodel has an AGE
property which has
numeric operations we can use with the stream's filter
method such as between
.
// Find people by age range
public Iterable<Person> findByAgeBetween(int minAge, int maxAge) {
return entityStream //
.of(Person.class) //
.filter(Person$.AGE.between(minAge, maxAge)) //
.sorted(Person$.AGE, SortOrder.ASC) //
.collect(Collectors.toList());
}
In this example we also make use of the Streams sorted
method to declare that our stream will be sorted by the Person$.AGE
in ASC
ending order.
Check out the full set of tests for EntityStreams
๐ฏโ๏ธ Querying by Example (QBE)
Query by Example (QBE) is a user-friendly querying technique with a simple interface. It allows dynamic query creation and does not require you to write queries that contain field names. In fact, Query by Example does not require you to write queries by using store-specific query languages at all.
QBE Usage
The Query by Example API consists of four parts:
- Probe: The actual example of a domain object with populated fields.
- ExampleMatcher: The
ExampleMatcher
carries details on how to match particular fields. It can be reused across multipleExamples
. - Example: An Example consists of the probe and the ExampleMatcher. It is used to create the query.
- FetchableFluentQuery: A
FetchableFluentQuery
offers a fluent API, that allows further customization of a query derived from anExample
. Using the fluent API lets you specify ordering projection and result processing for your query.
Query by Example is well suited for several use cases:
- Querying your data store with a set of static or dynamic constraints.
- Frequent refactoring of the domain objects without worrying about breaking existing queries.
- Working independently of the underlying data store API.
For example, if you have an @Document
or @RedisHash
annotated entity you can create an instance, partially populate its
properties, create an Example
from it, and used the findAll
method to query for similar entities:
MyDoc template = new MyDoc();
template.setTitle("hello world");
template.setTag(Set.of("artigo"));
Example<MyDoc> example = Example.of(template, ExampleMatcher.matchingAny());
Iterable<MyDoc> allMatches = repository.findAll(example);
๐ป Maven configuration
Official Releases
<dependency>
<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring</artifactId>
<version>${version}</version>
</dependency>
Explicitly configuring OM as an annotation processor
For Maven, things normally just work, when you run ./mvnw spring-boot:run
. Some users have experienced this not being
the case, in which I recommend to explicitly declaring the maven-compiler-plugin
in the case below it is paired with
an app created with start.spring.io
with Spring Boot v3.0.4
(all other versions can be
inherited from the parent poms):
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>3.1.2</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring</artifactId>
<version>0.8.7-SNAPSHOT</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
Snapshots
<repositories>
<repository>
<id>snapshots-repo</id>
<url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
</repository>
</repositories>
and
<dependency>
<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring</artifactId>
<version>${version}-SNAPSHOT</version>
</dependency>
Ready to learn more? Check out the getting started guide.
๐ Gradle configuration
Add Repository - Snapshots Only
repositories {
mavenCentral()
maven {
url 'https://s01.oss.sonatype.org/content/repositories/snapshots/'
}
}
Dependency
ext {
redisOmVersion = '0.8.7-SNAPSHOT'
}
dependencies {
implementation "com.redis.om:redis-om-spring:$redisOmVersion"
annotationProcessor "com.redis.om:redis-om-spring:$redisOmVersion"
}
๐ Documentation
The Redis OM documentation is available here.
Demos
Embedded Demos
These can be found in the /demos
folder:
-
roms-documents:
- Simple API example of
@Document
mapping, Spring Repositories and Querying. - Run with
./mvnw install -Dmaven.test.skip && ./mvnw spring-boot:run -pl demos/roms-documents
- Simple API example of
-
roms-hashes:
- Simple API example of
@RedisHash
, enhanced secondary indices and querying. - Run with
./mvnw install -Dmaven.test.skip && ./mvnw spring-boot:run -pl demos/roms-hashes
- Simple API example of
-
roms-permits:
- Port of Elena Kolevska's Quick Start: Using RediSearch with JSON [Demo][redisearch-wjso] to Redis OM Spring.
- Run with
./mvnw install -Dmaven.test.skip && ./mvnw spring-boot:run -pl demos/roms-permits
-
roms-vss:
- Port of Redis Vector Search Demo.
- Run with
./mvnw install -Dmaven.test.skip && ./mvnw spring-boot:run -pl demos/roms-vss
External Demos
-
redis-om-spring-skeleton-app:
- Redis OM Spring Skeleton App
- Repo: https://github.com/redis-developer/redis-om-spring-skeleton-app
-
redis-om-spring-react-todomvc:
- Redis OM Spring to build a RESTful API that satisfies the simple web API spec set by the Todo-Backend project using JSON Documents stored in Redis.
- Repo: https://github.com/redis-developer/redis-om-spring-react-todomvc
-
redis-om-autocomplete-demo:
- A Spring Boot demo of autocomplete functionality using Redis OM Spring.
- Repo: https://github.com/redis-developer/redis-om-autocomplete-demo
โ๏ธ Troubleshooting
If you run into trouble or have any questions, we're here to help!
First, check the FAQ. If you don't find the answer there, hit us up on the Redis Discord Server.
โจ So How Do You Get RediSearch and RedisJSON?
Redis OM relies on two source available Redis modules: RediSearch and RedisJSON.
You can run these modules in your self-hosted Redis deployment, or you can use Redis Enterprise, which includes both modules.
To learn more, read our documentation.
๐ Contributing
We'd love your contributions!
Bug reports are especially helpful at this stage of the project. You can open a bug report on GitHub.
You can also contribute documentation -- or just let us know if something needs more detail. Open an issue on GitHub to get started.
๐งโ๐คโ๐ง Sibling Projects
๐ License
Redis OM uses the MIT license.