Skip to content

Commit 20adbca

Browse files
spring-projects#149 - Add Redis repository support sample.
1 parent 531aac9 commit 20adbca

File tree

10 files changed

+627
-0
lines changed

10 files changed

+627
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ We have separate folders for the samples of individual modules:
3434

3535
* `example` - Example for basic Spring Data Redis setup.
3636
* `cluster-sentinel` - Example for Redis cluster and Sentinel support.
37+
* `repository` - Example demonstrating Spring Data repository abstraction on top of Redis.
3738

3839
## Spring Data Elasticsearch
3940

redis/repository/README.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Spring Data Redis - Repository Examples #
2+
3+
This project contains examples for Spring Data specific repository abstraction on top of Redis.
4+
5+
## Repository Suport ##
6+
7+
Redis Repository support allows to convert, store, retrieve and index entities within Redis native data structures. To do, besides the `HASH` containing the actual properties several [Secondary Index](http://redis.io/topics/indexes) structures are set up and maintained.
8+
9+
```java
10+
@RedisHash("persons")
11+
class Person {
12+
13+
@Id String id;
14+
15+
@Indexed String firstname;
16+
@Indexed String lastname;
17+
18+
Gender gender;
19+
Address address;
20+
21+
@Reference List<Person> children;
22+
}
23+
```
24+
25+
The above entity would for example then be stored in a Redis [HASH](http://redis.io/topics/data-types#hashes) with key `persons:9b0ed8ee-14be-46ec-b5fa-79570aadb91d`.
26+
27+
```properties
28+
_class=example.springdata.redis.domain.Person <1>
29+
id=9b0ed8ee-14be-46ec-b5fa-79570aadb91d
30+
firstname=eddard <2>
31+
lastname=stark
32+
gender=MALE
33+
address.city=winterfell <3>
34+
address.country=the north
35+
children.[0]=persons:41436096-aabe-42fa-bd5a-9a517fbf0260 <4>
36+
children.[1]=persons:1973d8e7-fbd4-4f93-abab-a2e3a00b3f53
37+
children.[2]=persons:440b24c6-ede2-495a-b765-2d8b8d6e3995
38+
```
39+
```
40+
<1> The _class attribute is used to store the actual type and is required for object/hash conversion.
41+
<2> Values are also included in Secondary Index when annotated with @Indexed.
42+
<3> Complex types are flattened out and embedded into the HASH as long as there is no explicit Converter registered or a @Reference annotation present.
43+
<4> Using @Reference stores only the key of a referenced object without embedding values like in <3>.
44+
```
45+
46+
Additionally indexes are created for `firstname`, `lastname` and `address.city` containing the `id` of the actual entity.
47+
48+
```bash
49+
redis/src $ ./redis-cli keys *
50+
1) "persons" <1>
51+
2) "persons:9b0ed8ee-14be-46ec-b5fa-79570aadb91d" <2>
52+
3) "persons:9b0ed8ee-14be-46ec-b5fa-79570aadb91d:idx" <3>
53+
4) "persons:41436096-aabe-42fa-bd5a-9a517fbf0260"
54+
5) "persons:41436096-aabe-42fa-bd5a-9a517fbf0260:idx"
55+
6) "persons:1973d8e7-fbd4-4f93-abab-a2e3a00b3f53"
56+
7) "persons:1973d8e7-fbd4-4f93-abab-a2e3a00b3f53:idx"
57+
8) "persons:440b24c6-ede2-495a-b765-2d8b8d6e3995"
58+
9) "persons:440b24c6-ede2-495a-b765-2d8b8d6e3995:idx"
59+
10) "persons:firstname:eddard" <4>
60+
11) "persons:firstname:robb"
61+
12) "persons:firstname:sansa"
62+
13) "persons:firstname:arya"
63+
14) "persons:lastname:stark" <5>
64+
15) "persons:address.city:winterfell" <6>
65+
```
66+
```
67+
<1> SET holding all ids known in the keyspace "persons".
68+
<2> HASH holding property values for id "9b0ed8ee-14be-46ec-b5fa-79570aadb91d" in keyspace "persons".
69+
<3> SET holding index information for CRUD operations.
70+
<4> SET used for indexing entities with "firstname" equal to "eddard" within keyspace "persons".
71+
<5> SET used for indexing entities with "lastname" equal to "stark" within keyspace "persons".
72+
<6> SET used for indexing entities with "address.city" equal to "winterfell" within keyspace "persons".
73+
```
74+
75+
## Configuration ##
76+
77+
The below configuration uses [Jedis](https://github.com/xetorthio/jedis) to connect to Redis on its default port. Please note the usage of `@EnableRedisRepositories` to create `Repository` instances.
78+
79+
```java
80+
@Configuration
81+
@EnableRedisRepositories
82+
class AppConfig {
83+
84+
@Bean
85+
RedisConnectionFactory connectionFactory() {
86+
return new JedisConnectionFactory();
87+
}
88+
89+
@Bean
90+
RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory connectionFactory) {
91+
92+
RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
93+
template.setConnectionFactory(connectionFactory);
94+
95+
return template;
96+
}
97+
}
98+
```
99+
100+
Having the infrastructure in place you can go on declaring and using the `Repository` interface.
101+
102+
```java
103+
interface PersonRepository extends CrudRepository<Person, String> {
104+
105+
List<Person> findByLastname(String lastname);
106+
107+
Page<Person> findByLastname(String lastname, Pageable page);
108+
109+
List<Person> findByFirstnameAndLastname(String firstname, String lastname);
110+
111+
List<Person> findByFirstnameOrLastname(String firstname, String lastname);
112+
113+
List<Person> findByAddress_City(String city);
114+
}
115+
```
116+
117+
## One Word of Caution ##
118+
119+
Maintaining complex types and index structures stands and falls with its usage. Please consider the following:
120+
121+
* Nested document structures increase object <> hash conversion overhead.
122+
* Consider having only those indexes you really need instead of indexing each and every property.
123+
* Pagination is a costly operation since the total number of elements is calculated before returning just a slice of data.
124+
* Pagination gives no guarantee on information as elements might be added, moved or removed.

redis/repository/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
<version>${project.version}</version>
2525
<scope>test</scope>
2626
</dependency>
27+
<dependency>
28+
<groupId>com.github.kstyrc</groupId>
29+
<artifactId>embedded-redis</artifactId>
30+
<version>0.6</version>
31+
<scope>test</scope>
32+
</dependency>
2733
<dependency>
2834
<groupId>org.springframework.data</groupId>
2935
<artifactId>spring-data-redis</artifactId>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.redis;
17+
18+
import org.springframework.context.annotation.Bean;
19+
import org.springframework.context.annotation.Configuration;
20+
import org.springframework.data.redis.connection.RedisConnectionFactory;
21+
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
22+
import org.springframework.data.redis.core.RedisTemplate;
23+
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
24+
25+
/**
26+
* @author Christoph Strobl
27+
*/
28+
@Configuration
29+
@EnableRedisRepositories
30+
public class AppConfig {
31+
32+
/**
33+
* {@link RedisConnectionFactory} to be used when connecting to redis.
34+
*
35+
* @return
36+
*/
37+
@Bean
38+
RedisConnectionFactory connectionFactory() {
39+
return new JedisConnectionFactory();
40+
}
41+
42+
/**
43+
* @param connectionFactory
44+
* @return
45+
*/
46+
@Bean
47+
RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory connectionFactory) {
48+
49+
RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
50+
template.setConnectionFactory(connectionFactory);
51+
52+
return template;
53+
}
54+
55+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.redis.domain;
17+
18+
import lombok.Data;
19+
import lombok.EqualsAndHashCode;
20+
21+
import org.springframework.data.redis.core.index.Indexed;
22+
23+
/**
24+
* @author Christoph Strobl
25+
*/
26+
@Data
27+
@EqualsAndHashCode
28+
class Address {
29+
30+
private @Indexed String city;
31+
private String country;
32+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.redis.domain;
17+
18+
/**
19+
* @author Christoph Strobl
20+
*/
21+
enum Gender {
22+
FEMALE, MALE
23+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright 2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.redis.domain;
17+
18+
import java.lang.reflect.Field;
19+
import java.util.List;
20+
21+
import lombok.Data;
22+
import lombok.EqualsAndHashCode;
23+
24+
import org.springframework.data.annotation.Id;
25+
import org.springframework.data.annotation.Reference;
26+
import org.springframework.data.redis.core.RedisHash;
27+
import org.springframework.data.redis.core.index.Indexed;
28+
29+
/**
30+
* {@link Person} object stored inside a Redis {@literal HASH}. <br />
31+
* <br />
32+
* Sample (key = persons:9b0ed8ee-14be-46ec-b5fa-79570aadb91d):
33+
*
34+
* <pre>
35+
* <code>
36+
* _class := example.springdata.redis.domain.Person
37+
* id := 9b0ed8ee-14be-46ec-b5fa-79570aadb91d
38+
* firstname := eddard
39+
* lastname := stark
40+
* gender := MALE
41+
* address.city := winterfell
42+
* address.country := the north
43+
* children.[0] := persons:41436096-aabe-42fa-bd5a-9a517fbf0260
44+
* children.[1] := persons:1973d8e7-fbd4-4f93-abab-a2e3a00b3f53
45+
* children.[2] := persons:440b24c6-ede2-495a-b765-2d8b8d6e3995
46+
* children.[3] := persons:85f0c1d1-cef6-40a4-b969-758ebb68dd7b
47+
* children.[4] := persons:73cb36e8-add9-4ec0-b5dd-d820e04f44f0
48+
* children.[5] := persons:9c2461aa-2ef2-469f-83a2-bd216df8357f
49+
* </code>
50+
* </pre>
51+
*
52+
* @author Christoph Strobl
53+
*/
54+
@Data
55+
@EqualsAndHashCode(exclude = { "children" })
56+
@RedisHash("persons")
57+
class Person {
58+
59+
/**
60+
* The {@literal id} and {@link RedisHash#toString()} build up the {@literal key} for the Redis {@literal HASH}. <br />
61+
*
62+
* <pre>
63+
* <code>
64+
* {@link RedisHash#value()} + ":" + {@link Person#id}
65+
* //eg. persons:9b0ed8ee-14be-46ec-b5fa-79570aadb91d
66+
* </code>
67+
* </pre>
68+
*
69+
* <strong>Note:</strong> empty {@literal id} fields are automatically assigned during save operation.
70+
*/
71+
private @Id String id;
72+
73+
/**
74+
* Using {@link Indexed} marks the property as for indexing which uses Redis {@literal SET} to keep track of
75+
* {@literal ids} for objects with matching values. <br />
76+
*
77+
* <pre>
78+
* <code>
79+
* {@link RedisHash#value()} + ":" + {@link Field#getName()} +":" + {@link Field#get(Object)}
80+
* //eg. persons:firstname:eddard
81+
* </code>
82+
* </pre>
83+
*/
84+
private @Indexed String firstname;
85+
private @Indexed String lastname;
86+
87+
private Gender gender;
88+
89+
/**
90+
* Since {@link Indexed} is used on {@link Address#getCity()} index structures for {@code persons:address:city} are be
91+
* maintained.
92+
*/
93+
private Address address;
94+
95+
/**
96+
* Using {@link Reference} allows to link to existing objects via their {@literal key}. The values stored in the
97+
* objects {@literal HASH} looks like:
98+
*
99+
* <pre>
100+
* <code>
101+
* children.[0] := persons:41436096-aabe-42fa-bd5a-9a517fbf0260
102+
* children.[1] := persons:1973d8e7-fbd4-4f93-abab-a2e3a00b3f53
103+
* children.[2] := persons:440b24c6-ede2-495a-b765-2d8b8d6e3995
104+
* </code>
105+
* </pre>
106+
*/
107+
private @Reference List<Person> children;
108+
109+
public Person() {}
110+
111+
public Person(String firstname, String lastname, Gender gender) {
112+
this.firstname = firstname;
113+
this.lastname = lastname;
114+
this.gender = gender;
115+
}
116+
117+
}

0 commit comments

Comments
 (0)