From 5a25e80bbfb10d722b23d9dd807bad9ccea6a1d8 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 8 Feb 2016 08:59:27 +0100 Subject: [PATCH] #149 - Add Redis repository support sample. Bumped Spring Data Redis version to 1.7 RC1. Added JUnit Rule for Embedded Redis Server. Original pull request: #162. --- README.md | 1 + redis/pom.xml | 1 + redis/repository/README.md | 124 ++++++++++ redis/repository/pom.xml | 50 ++++ .../redis/repositories/Address.java | 32 +++ .../ApplicationConfiguration.java | 55 +++++ .../springdata/redis/repositories/Gender.java | 23 ++ .../springdata/redis/repositories/Person.java | 117 ++++++++++ .../redis/repositories/PersonRepository.java | 39 ++++ .../repositories/PersonRepositoryTests.java | 216 ++++++++++++++++++ .../repository/src/test/resources/logback.xml | 14 ++ redis/util/pom.xml | 6 + .../redis/test/util/EmbeddedRedisServer.java | 87 +++++++ 13 files changed, 765 insertions(+) create mode 100644 redis/repository/README.md create mode 100644 redis/repository/pom.xml create mode 100644 redis/repository/src/main/java/example/springdata/redis/repositories/Address.java create mode 100644 redis/repository/src/main/java/example/springdata/redis/repositories/ApplicationConfiguration.java create mode 100644 redis/repository/src/main/java/example/springdata/redis/repositories/Gender.java create mode 100644 redis/repository/src/main/java/example/springdata/redis/repositories/Person.java create mode 100644 redis/repository/src/main/java/example/springdata/redis/repositories/PersonRepository.java create mode 100644 redis/repository/src/test/java/example/springdata/redis/repositories/PersonRepositoryTests.java create mode 100644 redis/repository/src/test/resources/logback.xml create mode 100644 redis/util/src/main/java/example/springdata/redis/test/util/EmbeddedRedisServer.java diff --git a/README.md b/README.md index f58b60a1..1623f389 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ We have separate folders for the samples of individual modules: * `example` - Example for basic Spring Data Redis setup. * `cluster-sentinel` - Example for Redis cluster and Sentinel support. +* `repository` - Example demonstrating Spring Data repository abstraction on top of Redis. ## Spring Data Elasticsearch diff --git a/redis/pom.xml b/redis/pom.xml index 52f8a4a1..4afcbd3b 100644 --- a/redis/pom.xml +++ b/redis/pom.xml @@ -20,6 +20,7 @@ cluster-sentinel example cluster + repository diff --git a/redis/repository/README.md b/redis/repository/README.md new file mode 100644 index 00000000..5357838d --- /dev/null +++ b/redis/repository/README.md @@ -0,0 +1,124 @@ +# Spring Data Redis - Repository Examples # + +This project contains examples for Spring Data specific repository abstraction on top of Redis. + +## Repository Suport ## + +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. + +```java +@RedisHash("persons") +class Person { + + @Id String id; + + @Indexed String firstname; + @Indexed String lastname; + + Gender gender; + Address address; + + @Reference List children; +} +``` + +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`. + +```properties +_class=example.springdata.redis.domain.Person <1> +id=9b0ed8ee-14be-46ec-b5fa-79570aadb91d +firstname=eddard <2> +lastname=stark +gender=MALE +address.city=winterfell <3> +address.country=the north +children.[0]=persons:41436096-aabe-42fa-bd5a-9a517fbf0260 <4> +children.[1]=persons:1973d8e7-fbd4-4f93-abab-a2e3a00b3f53 +children.[2]=persons:440b24c6-ede2-495a-b765-2d8b8d6e3995 +``` +``` +<1> The _class attribute is used to store the actual type and is required for object/hash conversion. +<2> Values are also included in Secondary Index when annotated with @Indexed. +<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. +<4> Using @Reference stores only the key of a referenced object without embedding values like in <3>. +``` + +Additionally indexes are created for `firstname`, `lastname` and `address.city` containing the `id` of the actual entity. + +```bash +redis/src $ ./redis-cli keys * + 1) "persons" <1> + 2) "persons:9b0ed8ee-14be-46ec-b5fa-79570aadb91d" <2> + 3) "persons:9b0ed8ee-14be-46ec-b5fa-79570aadb91d:idx" <3> + 4) "persons:41436096-aabe-42fa-bd5a-9a517fbf0260" + 5) "persons:41436096-aabe-42fa-bd5a-9a517fbf0260:idx" + 6) "persons:1973d8e7-fbd4-4f93-abab-a2e3a00b3f53" + 7) "persons:1973d8e7-fbd4-4f93-abab-a2e3a00b3f53:idx" + 8) "persons:440b24c6-ede2-495a-b765-2d8b8d6e3995" + 9) "persons:440b24c6-ede2-495a-b765-2d8b8d6e3995:idx" + 10) "persons:firstname:eddard" <4> + 11) "persons:firstname:robb" + 12) "persons:firstname:sansa" + 13) "persons:firstname:arya" + 14) "persons:lastname:stark" <5> + 15) "persons:address.city:winterfell" <6> +``` +``` +<1> SET holding all ids known in the keyspace "persons". +<2> HASH holding property values for id "9b0ed8ee-14be-46ec-b5fa-79570aadb91d" in keyspace "persons". +<3> SET holding index information for CRUD operations. +<4> SET used for indexing entities with "firstname" equal to "eddard" within keyspace "persons". +<5> SET used for indexing entities with "lastname" equal to "stark" within keyspace "persons". +<6> SET used for indexing entities with "address.city" equal to "winterfell" within keyspace "persons". +``` + +## Configuration ## + +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. + +```java +@Configuration +@EnableRedisRepositories +class AppConfig { + + @Bean + RedisConnectionFactory connectionFactory() { + return new JedisConnectionFactory(); + } + + @Bean + RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + + RedisTemplate template = new RedisTemplate(); + template.setConnectionFactory(connectionFactory); + + return template; + } +} +``` + +Having the infrastructure in place you can go on declaring and using the `Repository` interface. + +```java +interface PersonRepository extends CrudRepository { + + List findByLastname(String lastname); + + Page findByLastname(String lastname, Pageable page); + + List findByFirstnameAndLastname(String firstname, String lastname); + + List findByFirstnameOrLastname(String firstname, String lastname); + + List findByAddress_City(String city); +} +``` + +## One Word of Caution ## + +Maintaining complex types and index structures stands and falls with its usage. Please consider the following: + +* Nested document structures increase object <> hash conversion overhead. +* Consider having only those indexes you really need instead of indexing each and every property. +* Pagination is a costly operation since the total number of elements is calculated before returning just a slice of data. +* Pagination gives no guarantee on information as elements might be added, moved or removed. diff --git a/redis/repository/pom.xml b/redis/repository/pom.xml new file mode 100644 index 00000000..25976e00 --- /dev/null +++ b/redis/repository/pom.xml @@ -0,0 +1,50 @@ + + 4.0.0 + + spring-data-redis-repository-example + Spring Data Redis - Repository Support Example + + + org.springframework.data.examples + spring-data-redis-examples + 1.0.0.BUILD-SNAPSHOT + ../pom.xml + + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + ${project.groupId} + spring-data-redis-example-utils + ${project.version} + test + + + com.github.kstyrc + embedded-redis + 0.6 + test + + + org.springframework.data + spring-data-redis + 1.7.0.RC1 + + + org.springframework.data + spring-data-keyvalue + 1.1.0.RC1 + + + org.springframework.data + spring-data-commons + 1.12.0.RC1 + + + + \ No newline at end of file diff --git a/redis/repository/src/main/java/example/springdata/redis/repositories/Address.java b/redis/repository/src/main/java/example/springdata/redis/repositories/Address.java new file mode 100644 index 00000000..0081e898 --- /dev/null +++ b/redis/repository/src/main/java/example/springdata/redis/repositories/Address.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis.domain; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import org.springframework.data.redis.core.index.Indexed; + +/** + * @author Christoph Strobl + */ +@Data +@EqualsAndHashCode +class Address { + + private @Indexed String city; + private String country; +} diff --git a/redis/repository/src/main/java/example/springdata/redis/repositories/ApplicationConfiguration.java b/redis/repository/src/main/java/example/springdata/redis/repositories/ApplicationConfiguration.java new file mode 100644 index 00000000..27481177 --- /dev/null +++ b/redis/repository/src/main/java/example/springdata/redis/repositories/ApplicationConfiguration.java @@ -0,0 +1,55 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; + +/** + * @author Christoph Strobl + */ +@Configuration +@EnableRedisRepositories +public class AppConfig { + + /** + * {@link RedisConnectionFactory} to be used when connecting to redis. + * + * @return + */ + @Bean + RedisConnectionFactory connectionFactory() { + return new JedisConnectionFactory(); + } + + /** + * @param connectionFactory + * @return + */ + @Bean + RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + + RedisTemplate template = new RedisTemplate(); + template.setConnectionFactory(connectionFactory); + + return template; + } + +} diff --git a/redis/repository/src/main/java/example/springdata/redis/repositories/Gender.java b/redis/repository/src/main/java/example/springdata/redis/repositories/Gender.java new file mode 100644 index 00000000..7686d31a --- /dev/null +++ b/redis/repository/src/main/java/example/springdata/redis/repositories/Gender.java @@ -0,0 +1,23 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis.domain; + +/** + * @author Christoph Strobl + */ +enum Gender { + FEMALE, MALE +} diff --git a/redis/repository/src/main/java/example/springdata/redis/repositories/Person.java b/redis/repository/src/main/java/example/springdata/redis/repositories/Person.java new file mode 100644 index 00000000..256e99da --- /dev/null +++ b/redis/repository/src/main/java/example/springdata/redis/repositories/Person.java @@ -0,0 +1,117 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis.domain; + +import java.lang.reflect.Field; +import java.util.List; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +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.index.Indexed; + +/** + * {@link Person} object stored inside a Redis {@literal HASH}.
+ *
+ * Sample (key = persons:9b0ed8ee-14be-46ec-b5fa-79570aadb91d): + * + *
+ * 
+ * _class := example.springdata.redis.domain.Person
+ * id := 9b0ed8ee-14be-46ec-b5fa-79570aadb91d
+ * firstname := eddard
+ * lastname := stark
+ * gender := MALE
+ * address.city := winterfell
+ * address.country := the north
+ * children.[0] := persons:41436096-aabe-42fa-bd5a-9a517fbf0260
+ * children.[1] := persons:1973d8e7-fbd4-4f93-abab-a2e3a00b3f53
+ * children.[2] := persons:440b24c6-ede2-495a-b765-2d8b8d6e3995
+ * children.[3] := persons:85f0c1d1-cef6-40a4-b969-758ebb68dd7b
+ * children.[4] := persons:73cb36e8-add9-4ec0-b5dd-d820e04f44f0
+ * children.[5] := persons:9c2461aa-2ef2-469f-83a2-bd216df8357f
+ * 
+ * 
+ * + * @author Christoph Strobl + */ +@Data +@EqualsAndHashCode(exclude = { "children" }) +@RedisHash("persons") +class Person { + + /** + * The {@literal id} and {@link RedisHash#toString()} build up the {@literal key} for the Redis {@literal HASH}.
+ * + *
+	 * 
+	 * {@link RedisHash#value()} + ":" + {@link Person#id}
+	 * //eg. persons:9b0ed8ee-14be-46ec-b5fa-79570aadb91d
+	 * 
+	 * 
+ * + * Note: empty {@literal id} fields are automatically assigned during save operation. + */ + private @Id String id; + + /** + * Using {@link Indexed} marks the property as for indexing which uses Redis {@literal SET} to keep track of + * {@literal ids} for objects with matching values.
+ * + *
+	 * 
+	 * {@link RedisHash#value()} + ":" + {@link Field#getName()} +":" + {@link Field#get(Object)}
+	 * //eg. persons:firstname:eddard
+	 * 
+	 * 
+ */ + private @Indexed String firstname; + private @Indexed String lastname; + + private Gender gender; + + /** + * Since {@link Indexed} is used on {@link Address#getCity()} index structures for {@code persons:address:city} are be + * maintained. + */ + private Address address; + + /** + * Using {@link Reference} allows to link to existing objects via their {@literal key}. The values stored in the + * objects {@literal HASH} looks like: + * + *
+	 * 
+	 * children.[0] := persons:41436096-aabe-42fa-bd5a-9a517fbf0260
+	 * children.[1] := persons:1973d8e7-fbd4-4f93-abab-a2e3a00b3f53
+	 * children.[2] := persons:440b24c6-ede2-495a-b765-2d8b8d6e3995
+	 * 
+	 * 
+ */ + private @Reference List children; + + public Person() {} + + public Person(String firstname, String lastname, Gender gender) { + this.firstname = firstname; + this.lastname = lastname; + this.gender = gender; + } + +} diff --git a/redis/repository/src/main/java/example/springdata/redis/repositories/PersonRepository.java b/redis/repository/src/main/java/example/springdata/redis/repositories/PersonRepository.java new file mode 100644 index 00000000..a9c4c128 --- /dev/null +++ b/redis/repository/src/main/java/example/springdata/redis/repositories/PersonRepository.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis.domain; + +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.CrudRepository; + +/** + * @author Christoph Strobl + */ +interface PersonRepository extends CrudRepository { + + List findByLastname(String lastname); + + Page findPersonByLastname(String lastname, Pageable page); + + List findByFirstnameAndLastname(String firstname, String lastname); + + List findByFirstnameOrLastname(String firstname, String lastname); + + List findByAddress_City(String city); + +} diff --git a/redis/repository/src/test/java/example/springdata/redis/repositories/PersonRepositoryTests.java b/redis/repository/src/test/java/example/springdata/redis/repositories/PersonRepositoryTests.java new file mode 100644 index 00000000..23bc6884 --- /dev/null +++ b/redis/repository/src/test/java/example/springdata/redis/repositories/PersonRepositoryTests.java @@ -0,0 +1,216 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis.domain; + +import static org.hamcrest.collection.IsIterableContainingInAnyOrder.*; +import static org.hamcrest.core.Is.*; +import static org.hamcrest.core.IsCollectionContaining.*; +import static org.hamcrest.core.IsNot.*; +import static org.junit.Assert.*; + +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.core.RedisOperations; +import org.springframework.data.redis.core.index.Indexed; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import example.springdata.redis.AppConfig; +import example.springdata.redis.test.util.EmbeddedRedisServer; +import example.springdata.redis.test.util.RequiresRedisServer; + +/** + * @author Christoph Strobl + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = AppConfig.class) +public class PersonRepositoryTests { + + /** + * We need to have a Redis server instance available.
+ * 1) Start/Stop an embedded instance or reuse an already running local installation
+ * 2) Ignore tests if startup failed and no server running locally. + */ + public static @ClassRule RuleChain rules = RuleChain.outerRule( + EmbeddedRedisServer.runningAt(6379).suppressExceptions()).around(RequiresRedisServer.onLocalhost()); + + /** {@link Charset} for String conversion **/ + private static final Charset CHARSET = Charset.forName("UTF-8"); + + @Autowired RedisOperations ops; + @Autowired PersonRepository repo; + + /* + * Set of test users + */ + Person eddard = new Person("eddard", "stark", Gender.MALE); + Person robb = new Person("robb", "stark", Gender.MALE); + Person sansa = new Person("sansa", "stark", Gender.FEMALE); + Person arya = new Person("arya", "stark", Gender.FEMALE); + Person bran = new Person("bran", "stark", Gender.MALE); + Person rickon = new Person("rickon", "stark", Gender.MALE); + Person jon = new Person("jon", "snow", Gender.MALE); + + @Before + @After + public void setUp() { + + ops.execute((RedisConnection connection) -> { + connection.flushDb(); + return "OK"; + }); + } + + /** + * Save a single entity and verify that a key for the given keyspace/prefix exists.
+ * Print out the hash structure within Redis. + */ + @Test + public void saveSingleEntity() { + + repo.save(eddard); + + assertThat(ops.execute((RedisConnection connection) -> connection.exists(new String("persons:" + eddard.getId()) + .getBytes(CHARSET))), is(true)); + } + + /** + * Find entity by a single {@link Indexed} property value. + */ + @Test + public void findBySingleProperty() { + + flushTestUsers(); + + List starks = repo.findByLastname(eddard.getLastname()); + + assertThat(starks, containsInAnyOrder(eddard, robb, sansa, arya, bran, rickon)); + assertThat(starks, not(hasItem(jon))); + } + + /** + * Find entities by multiple {@link Indexed} properties using {@literal AND}. + */ + @Test + public void findByMultipleProperties() { + + flushTestUsers(); + + List aryaStark = repo.findByFirstnameAndLastname(arya.getFirstname(), arya.getLastname()); + + assertThat(aryaStark, hasItem(arya)); + assertThat(aryaStark, not(hasItems(eddard, robb, sansa, bran, rickon, jon))); + } + + /** + * Find entities by multiple {@link Indexed} properties using {@literal OR}. + */ + @Test + public void findByMultiplePropertiesUsingOr() { + + flushTestUsers(); + + List aryaAndJon = repo.findByFirstnameOrLastname(arya.getFirstname(), jon.getLastname()); + + assertThat(aryaAndJon, containsInAnyOrder(arya, jon)); + assertThat(aryaAndJon, not(hasItems(eddard, robb, sansa, bran, rickon))); + } + + /** + * Find entities in range defined by {@link Pageable}. + */ + @Test + public void findByReturingPage() { + + flushTestUsers(); + + Page page1 = repo.findPersonByLastname(eddard.getLastname(), new PageRequest(0, 5)); + + assertThat(page1.getNumberOfElements(), is(5)); + assertThat(page1.getTotalElements(), is(6L)); + + Page page2 = repo.findPersonByLastname(eddard.getLastname(), new PageRequest(1, 5)); + + assertThat(page2.getNumberOfElements(), is(1)); + assertThat(page2.getTotalElements(), is(6L)); + } + + /** + * Find entity by a single {@link Indexed} property on an embedded entity. + */ + @Test + public void findByEmbeddedProperty() { + + Address winterfell = new Address(); + winterfell.setCountry("the north"); + winterfell.setCity("winterfell"); + + eddard.setAddress(winterfell); + + flushTestUsers(); + + List eddardStark = repo.findByAddress_City(winterfell.getCity()); + + assertThat(eddardStark, hasItem(eddard)); + assertThat(eddardStark, not(hasItems(robb, sansa, arya, bran, rickon, jon))); + } + + /** + * Store references to other entites without embedding all data.
+ * Print out the hash structure within Redis. + */ + @Test + public void useReferencesToStoreDataToOtherObjects() { + + flushTestUsers(); + + eddard.setChildren(Arrays.asList(jon, robb, sansa, arya, bran, rickon)); + + repo.save(eddard); + + Person laoded = repo.findOne(eddard.getId()); + assertThat(laoded.getChildren(), hasItems(jon, robb, sansa, arya, bran, rickon)); + + /* + * Deceased: + * + * - Robb was killed by Roose Bolton during the Red Wedding. + * - Jon was stabbed by brothers or the Night's Watch. + */ + repo.delete(Arrays.asList(robb, jon)); + + laoded = repo.findOne(eddard.getId()); + assertThat(laoded.getChildren(), hasItems(sansa, arya, bran, rickon)); + assertThat(laoded.getChildren(), not(hasItems(robb, jon))); + } + + private void flushTestUsers() { + repo.save(Arrays.asList(eddard, robb, sansa, arya, bran, rickon, jon)); + } +} diff --git a/redis/repository/src/test/resources/logback.xml b/redis/repository/src/test/resources/logback.xml new file mode 100644 index 00000000..61c86dd9 --- /dev/null +++ b/redis/repository/src/test/resources/logback.xml @@ -0,0 +1,14 @@ + + + + + + %d %5p %40.40c:%4L - %m%n + + + + + + + + \ No newline at end of file diff --git a/redis/util/pom.xml b/redis/util/pom.xml index 7d72cf68..89b17291 100644 --- a/redis/util/pom.xml +++ b/redis/util/pom.xml @@ -21,6 +21,12 @@ org.springframework.boot spring-boot-starter-data-redis + + com.github.kstyrc + embedded-redis + 0.6 + true +
\ No newline at end of file diff --git a/redis/util/src/main/java/example/springdata/redis/test/util/EmbeddedRedisServer.java b/redis/util/src/main/java/example/springdata/redis/test/util/EmbeddedRedisServer.java new file mode 100644 index 00000000..8720ec05 --- /dev/null +++ b/redis/util/src/main/java/example/springdata/redis/test/util/EmbeddedRedisServer.java @@ -0,0 +1,87 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis.test.util; + +import java.io.IOException; + +import org.junit.rules.ExternalResource; + +import redis.embedded.RedisServer; + +/** + * JUnit rule implementation to start and shut down an embedded Redis instance. + * + * @author Christoph Strobl + * @author Oliver Gierke + */ +public class EmbeddedRedisServer extends ExternalResource { + + private static final int DEFAULT_PORT = 6379; + private RedisServer server; + private int port = DEFAULT_PORT; + private boolean suppressExceptions = false; + + public EmbeddedRedisServer() { + + } + + protected EmbeddedRedisServer(int port) { + this.port = port; + } + + public static EmbeddedRedisServer runningAt(Integer port) { + return new EmbeddedRedisServer(port != null ? port : DEFAULT_PORT); + } + + public EmbeddedRedisServer suppressExceptions() { + this.suppressExceptions = true; + return this; + } + + /* + * (non-Javadoc) + * @see org.junit.rules.ExternalResource#before() + */ + @Override + protected void before() throws IOException { + + try { + + this.server = new RedisServer(this.port); + this.server.start(); + } catch (Exception e) { + if (!suppressExceptions) { + throw e; + } + } + } + + /* + * (non-Javadoc) + * @see org.junit.rules.ExternalResource#after() + */ + @Override + protected void after() { + + try { + this.server.stop(); + } catch (Exception e) { + if (!suppressExceptions) { + throw e; + } + } + } +}