From 19cf46330500f7fa2b1f22a1efc21845214cfa52 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 30 Nov 2017 15:03:49 -0800 Subject: [PATCH] #327 - Add Jackson example for Reactive Redis use. --- redis/reactive/pom.xml | 5 + .../springdata/redis/EmailAddress.java | 34 +++++ .../java/example/springdata/redis/Person.java | 32 +++++ .../redis/RedisTestConfiguration.java | 37 +++++ .../redis/operations/JacksonJsonTests.java | 136 ++++++++++++++++++ 5 files changed, 244 insertions(+) create mode 100644 redis/reactive/src/test/java/example/springdata/redis/EmailAddress.java create mode 100644 redis/reactive/src/test/java/example/springdata/redis/Person.java create mode 100644 redis/reactive/src/test/java/example/springdata/redis/operations/JacksonJsonTests.java diff --git a/redis/reactive/pom.xml b/redis/reactive/pom.xml index 2b89bca7..670f7293 100644 --- a/redis/reactive/pom.xml +++ b/redis/reactive/pom.xml @@ -29,6 +29,11 @@ lettuce-core + + com.fasterxml.jackson.core + jackson-databind + + io.projectreactor reactor-test diff --git a/redis/reactive/src/test/java/example/springdata/redis/EmailAddress.java b/redis/reactive/src/test/java/example/springdata/redis/EmailAddress.java new file mode 100644 index 00000000..6a8e2d49 --- /dev/null +++ b/redis/reactive/src/test/java/example/springdata/redis/EmailAddress.java @@ -0,0 +1,34 @@ +/* + * Copyright 2017 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 lombok.RequiredArgsConstructor; +import lombok.Value; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; + +/** + * Example value object. + * + * @author Mark Paluch + */ +@Value +@RequiredArgsConstructor +@JsonTypeInfo(use = Id.CLASS, property = "_type") +public class EmailAddress { + final String address; +} diff --git a/redis/reactive/src/test/java/example/springdata/redis/Person.java b/redis/reactive/src/test/java/example/springdata/redis/Person.java new file mode 100644 index 00000000..39697286 --- /dev/null +++ b/redis/reactive/src/test/java/example/springdata/redis/Person.java @@ -0,0 +1,32 @@ +/* + * Copyright 2017 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 lombok.Data; +import lombok.RequiredArgsConstructor; + +/** + * Example value object. + * + * @author Mark Paluch + */ +@Data +@RequiredArgsConstructor +public class Person { + + final String firstname; + final String lastname; +} diff --git a/redis/reactive/src/test/java/example/springdata/redis/RedisTestConfiguration.java b/redis/reactive/src/test/java/example/springdata/redis/RedisTestConfiguration.java index 41edf738..fc594303 100644 --- a/redis/reactive/src/test/java/example/springdata/redis/RedisTestConfiguration.java +++ b/redis/reactive/src/test/java/example/springdata/redis/RedisTestConfiguration.java @@ -24,7 +24,11 @@ import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.ReactiveRedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.RedisSerializationContext.RedisSerializationContextBuilder; +import org.springframework.data.redis.serializer.StringRedisSerializer; /** * @author Mark Paluch @@ -44,6 +48,39 @@ public class RedisTestConfiguration { return new ReactiveRedisTemplate<>(connectionFactory, RedisSerializationContext.string()); } + /** + * Configures a {@link ReactiveRedisTemplate} with {@link String} keys and a typed + * {@link Jackson2JsonRedisSerializer}. + */ + @Bean + public ReactiveRedisTemplate reactiveJsonPersonRedisTemplate( + ReactiveRedisConnectionFactory connectionFactory) { + + Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(Person.class); + RedisSerializationContextBuilder builder = RedisSerializationContext + .newSerializationContext(new StringRedisSerializer()); + + RedisSerializationContext serializationContext = builder.value(serializer).build(); + + return new ReactiveRedisTemplate<>(connectionFactory, serializationContext); + } + + /** + * Configures a {@link ReactiveRedisTemplate} with {@link String} keys and {@link GenericJackson2JsonRedisSerializer}. + */ + @Bean + public ReactiveRedisTemplate reactiveJsonObjectRedisTemplate( + ReactiveRedisConnectionFactory connectionFactory) { + + RedisSerializationContextBuilder builder = RedisSerializationContext + .newSerializationContext(new StringRedisSerializer()); + + RedisSerializationContext serializationContext = builder + .value(new GenericJackson2JsonRedisSerializer("_type")).build(); + + return new ReactiveRedisTemplate<>(connectionFactory, serializationContext); + } + /** * Clear database before shut down. */ diff --git a/redis/reactive/src/test/java/example/springdata/redis/operations/JacksonJsonTests.java b/redis/reactive/src/test/java/example/springdata/redis/operations/JacksonJsonTests.java new file mode 100644 index 00000000..752e79c5 --- /dev/null +++ b/redis/reactive/src/test/java/example/springdata/redis/operations/JacksonJsonTests.java @@ -0,0 +1,136 @@ +/* + * Copyright 2017 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.operations; + +import example.springdata.redis.EmailAddress; +import example.springdata.redis.Person; +import example.springdata.redis.RedisTestConfiguration; +import example.springdata.redis.test.util.RequiresRedisServer; +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Flux; +import reactor.test.StepVerifier; + +import java.nio.ByteBuffer; + +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; +import org.springframework.data.redis.core.ReactiveRedisOperations; +import org.springframework.data.redis.util.ByteUtils; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Show usage of reactive Template API on Redis lists using {@link ReactiveRedisOperations} with Jackson serialization. + * + * @author Mark Paluch + */ +@Slf4j +@RunWith(SpringRunner.class) +@SpringBootTest(classes = RedisTestConfiguration.class) +public class JacksonJsonTests { + + // we only want to run this tests when redis is up an running + public static @ClassRule RequiresRedisServer requiresServer = RequiresRedisServer.onLocalhost(); + + @Autowired ReactiveRedisOperations typedOperations; + + @Autowired ReactiveRedisOperations genericOperations; + + /** + * {@link ReactiveRedisOperations} using {@link String} keys and {@link Person} values serialized via + * {@link org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer} to JSON without additional type + * hints. + * + * @see RedisTestConfiguration#reactiveJsonPersonRedisTemplate(ReactiveRedisConnectionFactory) + */ + @Test + public void shouldWriteAndReadPerson() { + + StepVerifier.create(typedOperations.opsForValue().set("homer", new Person("Homer", "Simpson"))) // + .expectNext(true) // + .verifyComplete(); + + Flux get = typedOperations.execute(conn -> conn.stringCommands().get(ByteBuffer.wrap("homer".getBytes()))) // + .map(ByteUtils::getBytes) // + .map(String::new); + + StepVerifier.create(get) // + .expectNext("{\"firstname\":\"Homer\",\"lastname\":\"Simpson\"}") // + .verifyComplete(); + + StepVerifier.create(typedOperations.opsForValue().get("homer")) // + .expectNext(new Person("Homer", "Simpson")) // + .verifyComplete(); + } + + /** + * {@link ReactiveRedisOperations} using {@link String} keys and {@link Object} values serialized via + * {@link org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer} to JSON with additional type + * hints. This example uses the non-final type {@link Person} using its FQCN as type identifier. + * + * @see RedisTestConfiguration#reactiveJsonObjectRedisTemplate(ReactiveRedisConnectionFactory) + */ + @Test + public void shouldWriteAndReadPersonObject() { + + StepVerifier.create(genericOperations.opsForValue().set("homer", new Person("Homer", "Simpson"))) // + .expectNext(true) // + .verifyComplete(); + + Flux get = genericOperations.execute(conn -> conn.stringCommands().get(ByteBuffer.wrap("homer".getBytes()))) // + .map(ByteUtils::getBytes) // + .map(String::new); + + StepVerifier.create(get) // + .expectNext("{\"_type\":\"example.springdata.redis.Person\",\"firstname\":\"Homer\",\"lastname\":\"Simpson\"}") // + .verifyComplete(); + + StepVerifier.create(genericOperations.opsForValue().get("homer")) // + .expectNext(new Person("Homer", "Simpson")) // + .verifyComplete(); + } + + /** + * {@link ReactiveRedisOperations} using {@link String} keys and {@link Object} values serialized via + * {@link org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer} to JSON with additional type + * hints. This example uses the final type {@link example.springdata.redis.EmailAddress} using configuration from + * {@link com.fasterxml.jackson.annotation.JsonTypeInfo} as type identifier. + * + * @see RedisTestConfiguration#reactiveJsonObjectRedisTemplate(ReactiveRedisConnectionFactory) + */ + @Test + public void shouldWriteAndReadEmailObject() { + + StepVerifier.create(genericOperations.opsForValue().set("mail", new EmailAddress("homer@the-simpsons.com"))) // + .expectNext(true) // + .verifyComplete(); + + Flux get = genericOperations.execute(conn -> conn.stringCommands().get(ByteBuffer.wrap("mail".getBytes()))) // + .map(ByteUtils::getBytes) // + .map(String::new); + + StepVerifier.create(get) // + .expectNext("{\"_type\":\"example.springdata.redis.EmailAddress\",\"address\":\"homer@the-simpsons.com\"}") // + .verifyComplete(); + + StepVerifier.create(genericOperations.opsForValue().get("mail")) // + .expectNext(new EmailAddress("homer@the-simpsons.com")) // + .verifyComplete(); + } +}