Secondary 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 expire.
Simple Property Index
Given the sample Person
entity shown earlier, we can create an index for firstname
by annotating the property with @Indexed
, as shown in the following example:
@RedisHash("people")
public class Person {
@Id String id;
@Indexed String firstname;
String lastname;
Address address;
}
Indexes are built up for actual property values. Saving two Persons (for example, "rand" and "aviendha") results in setting up indexes similar to the following:
SADD people:firstname:rand e2c7dcee-b8cd-4424-883e-736ce564363e
SADD people:firstname:aviendha a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56
It is also possible to have indexes on nested elements. Assume Address
has a city
property that is annotated with @Indexed
. In that case, once person.address.city
is not null
, we have Sets for each city, as shown in the following example:
SADD people:address.city:tear e2c7dcee-b8cd-4424-883e-736ce564363e
Furthermore, the programmatic setup lets you define indexes on map keys and list properties, as shown in the following example:
@RedisHash("people")
public class Person {
// ... other properties omitted
Map<String,String> attributes; (1)
Map<String Person> relatives; (2)
List<Address> addresses; (3)
}
1 | SADD people:attributes.map-key:map-value e2c7dcee-b8cd-4424-883e-736ce564363e |
2 | SADD people:relatives.map-key.firstname:tam e2c7dcee-b8cd-4424-883e-736ce564363e |
3 | SADD people:addresses.city:tear e2c7dcee-b8cd-4424-883e-736ce564363e |
Indexes cannot be resolved on References. |
As with keyspaces, you can configure indexes without needing to annotate the actual domain type, as shown in the following example:
@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("people", "firstname"));
}
}
}
Again, as with keyspaces, you can programmatically configure indexes, as shown in the following example:
@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("people", "firstname"));
}
}
}
Geospatial Index
Assume the Address
type contains a location
property of type Point
that holds the geo coordinates of the particular address. By annotating the property with @GeoIndexed
, Spring Data Redis adds those values by using Redis GEO
commands, as shown in the following example:
@RedisHash("people")
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 a nested property, using Point and Distance . |
2 | Query method declaration on a nested property, using Circle to search within. |
3 | GEOADD people:address:location 13.361389 38.115556 e2c7dcee-b8cd-4424-883e-736ce564363e |
4 | GEORADIUS people:address:location 15.0 37.0 200.0 km |
In the preceding example the, longitude and latitude values are stored by using GEOADD
that use the object’s id
as the member’s name. The finder methods allow usage of Circle
or Point, Distance
combinations for querying those values.
It is not possible to combine near and within with other criteria.
|
Executing an Example
The following example uses Query by Example against a repository:
interface PersonRepository extends QueryByExampleExecutor<Person> {
}
class PersonService {
@Autowired PersonRepository personRepository;
List<Person> findPeople(Person probe) {
return personRepository.findAll(Example.of(probe));
}
}
Redis Repositories support, with their secondary indexes, a subset of Spring Data’s Query by Example features. In particular, only exact, case-sensitive, and non-null values are used to construct a query.
Secondary indexes use set-based operations (Set intersection, Set union) to determine matching keys. Adding a property to the query that is not indexed returns no result, because no index exists. Query by Example support inspects indexing configuration to include only properties in the query that are covered by an index. This is to prevent accidental inclusion of non-indexed properties.
Case-insensitive queries and unsupported StringMatcher
instances are rejected at runtime.
The following list shows the supported Query by Example options:
-
Case-sensitive, exact matching of simple and nested properties
-
Any/All match modes
-
Value transformation of the criteria value
-
Exclusion of
null
values from the criteria
The following list shows properties not supported by Query by Example:
-
Case-insensitive matching
-
Regex, prefix/contains/suffix String-matching
-
Querying of Associations, Collection, and Map-like properties
-
Inclusion of
null
values from the criteria -
findAll
with sorting