Building Applications with Spring Data Redis

Exclusive offer: get 50% off this eBook here
Spring Data

Spring Data — Save 50%

Implement JPA repositories with less code and harness the performance of Redis in your applications with this book and ebook.

$17.99    $9.00
by Petri Kainulainen | December 2012 | Open Source

The Spring Data project is essentially a parent project that collects data storage specific subprojects under a single brand. It teaches you how you can use Redis in your Spring powered applications.

In this article by Petri Kainulainen, author of Spring Data, we will cover:

  • The basic design principles of a Redis data model
  • The key components of Spring Data Redis
  • How we can implement a CRUD application
  • How we can use the publish/subscribe messaging pattern
  • How we can use Spring Data Redis as an implementation of the cache abstraction provided by Spring Framework 3.1

(For more resources related on Spring, see here.)

Designing a Redis data model

The most important rules of designing a Redis data model are: Redis does not support ad hoc queries and it does not support relations in the same way than relational databases. Thus, designing a Redis data model is a total different ballgame than designing the data model of a relational database. The basic guidelines of a Redis data model design are given as follows:

  • Instead of simply modeling the information stored in our data model, we have to also think how we want to search information from it. This often leads to a situation where we have to duplicate data in order to fulfill the requirements given to us. Don't be afraid to do this.
  • We should not concentrate on normalizing our data model. Instead, we should combine the data that we need to handle as an unit into an aggregate.
  • Since Redis does not support relations, we have to design and implement these relations by using the supported data structures. This means that we have to maintain these relations manually when they are changed. Because this might require a lot of effort and code, it could be wise to simply duplicate the information instead of using relations.
  • It is always wise to spend a moment to verify that we are using the correct tool for the job.

NoSQL Distilled, by Martin Fowler contains explanations of different NoSQL databases and their use cases, and can be found at http://martinfowler.com/books/nosql.html.

Redis supports multiple data structures. However, one question remained unanswered: which data structure should we use for our data? This question is addressed in the following table:

Data type

Description

String

A string is good choice for storing information that is already converted to a textual form. For instance, if we want to store HTML, JSON, or XML, a string should be our weapon of choice.

List

A list is a good choice if we will access it only near the start or end. This means that we should use it for representing queues or stacks.

Set

We should use a set if we need to get the size of a collection or check if a certain item belongs to it. Also, if we want to represent relations, a set is a good choice (for example, "who are John's friends?").

Sorted set

Sorted sets should be used in the same situations as sets when the ordering of items is important to us.

Hash

A hash is a perfect data structure for representing complex objects.

Key components

Spring Data Redis provides certain components that are the cornerstones of each application that uses it. This section provides a brief introduction to the components that we will later use to implement our example applications.

Atomic counters

Atomic counters are for Redis what sequences are for relational databases. Atomic counters guarantee that the value received by a client is unique. This makes these counters a perfect tool for creating unique IDs to our data that is stored in Redis. At the moment, Spring Data Redis offers two atomic counters: RedisAtomicInteger and RedisAtomicLong . These classes provide atomic counter operations for integers and longs.

RedisTemplate

The RedisTemplate<K,V> class is the central component of Spring Data Redis. It provides methods that we can use to communicate with a Redis instance. This class requires that two type parameters are given during its instantiation: the type of used Redis key and the type of the Redis value.

Operations

The RedisTemplate class provides two kinds of operations that we can use to store, fetch, and remove data from our Redis instance:

  1. Operations that require that the key and the value are given every time an operation is performed. These operations are handy when we have to execute a single operation by using a key and a value.
  2. Operations that are bound to a specific key that is given only once. We should use this approach when we have to perform multiple operations by using the same key.

The methods that require that a key and value is given every time an operation is performed are described in following list:

  • HashOperations<K,HK,HV> opsForHash(): This method returns the operations that are performed on hashes
  • ListOperations<K,V> opsForList(): This method returns the operations performed on lists
  • SetOperations<K,V> opsForSet(): This method returns the operations performed on sets
  • ValueOperations<K,V> opsForValue(): This method returns the operations performed on simple values
  • ZSetOperations<K,HK,HV> opsForZSet(): This method returns the operations performed on sorted sets

The methods of the RedisTemplate class that allow us to execute multiple operations by using the same key are described in following list:

  • BoundHashOperarations<K,HK,HV> boundHashOps(K key): This method returns hash operations that are bound to the key given as a parameter
  • BoundListOperations<K,V> boundListOps(K key): This method returns list operations bound to the key given as a parameter
  • BoundSetOperations<K,V> boundSetOps(K key):: This method returns set operations, which are bound to the given key
  • BoundValueOperations<K,V> boundValueOps(K key): This method returns operations performed to simple values that are bound to the given key
  • BoundZSetOperations<K,V> boundZSetOps(K key): This method returns operations performed on sorted sets that are bound to the key that is given as a parameter

The differences between these operations become clear to us when we start building our example applications.

Serializers

Because the data is stored in Redis as bytes, we need a method for converting our data to bytes and vice versa. Spring Data Redis provides an interface called RedisSerializer<T>, which is used in the serialization process. This interface has one type parameter that describes the type of the serialized object. Spring Data Redis provides several implementations of this interface. These implementations are described in the following table:

Serializer

Description

GenericToStringSerializer<T>

Serializes strings to bytes and vice versa. Uses the Spring ConversionService to transform objects to strings and vice versa.

JacksonJsonRedisSerializer<T>

Converts objects to JSON and vice versa.

JdkSerializationRedisSerializer

Provides Java based serialization to objects.

OxmSerializer

Uses the Object/XML mapping support of Spring Framework 3.

StringRedisSerializer 

Converts strings to bytes and vice versa.

We can customize the serialization process of the RedisTemplate class by using the described serializers. The RedisTemplate class provides flexible configuration options that can be used to set the serializers that are used to serialize value keys, values, hash keys, hash values, and string values.

The default serializer of the RedisTemplate class is JdkSerializationRedisSerializer. However, the string serializer is an exception to this rule. StringRedisSerializer is the serializer that is by default used to serialize string values.

Implementing a CRUD application

This section describes two different ways for implementing a CRUD application that is used to manage contact information. First, we will learn how we can implement a CRUD application by using the default serializer of the RedisTemplate class. Second, we will learn how we can use value serializers and implement a CRUD application that stores our data in JSON format.

Both of these applications will also share the same domain model. This domain model consists of two classes: Contact and Address.

  • We removed the JPA specific annotations from them
  • We use these classes in our web layer as form objects and they no longer have any other methods than getters and setters

The domain model is not the only thing that is shared by these examples. They also share the interface that declares the service methods for the Contact class. The source code of the ContactService interface is given as follows:

public interface ContactService {
public Contact add(Contact added);
public Contact deleteById(Long id) throws NotFoundException;
public List&lt;Contact&gt; findAll();
public Contact findById(Long id) throws NotFoundException;
public Contact update(Contact updated) throws NotFoundException;
}

Both of these applications will communicate with the used Redis instance by using the Jedis connector.

Regardless of the user's approach, we can implement a CRUD application with Spring Data Redis by following these steps:

  1. Configure the application context.
  2. Implement the CRUD functions.

Let's get started and find out how we can implement the CRUD functions for contact information.

Using default serializers

This subsection describes how we can implement a CRUD application by using the default serializers of the RedisTemplate class. This means that StringRedisSerializer is used to serialize string values, and JdkSerializationRedisSerializer serializes other objects.

Configuring the application context

We can configure the application context of our application by making the following changes to the ApplicationContext class:

  1. Configuring the Redis template bean.
  2. Configuring the Redis atomic long bean.

Configuring the Redis template bean

We can configure the Redis template bean by adding a redisTemplate() method to the ApplicationContext class and annotating this method with the @Bean annotation. We can implement this method by following these steps:

  1. Create a new RedisTemplate object.
  2. Set the used connection factory to the created RedisTemplate object.
  3. Return the created object.

The source code of the redisTemplate() method is given as follows:

@Bean
public RedisTemplate redisTemplate() {
RedisTemplate&lt;String, String&gt; redis = new RedisTemplate&lt;String,
String&gt;();
redis.setConnectionFactory(redisConnectionFactory());
return redis;
}

Configuring the Redis atomic long bean

We start the configuration of the Redis atomic long bean by adding a method called redisAtomicLong() to the ApplicationContext class and annotating the method with the @Bean annotation. Our next task is to implement this method by following these steps:

  1. Create a new RedisAtomicLong object. Pass the name of the used Redis counter and the Redis connection factory as constructor parameters.
  2. Return the created object.

The source code of the redisAtomicLong() method is given as follows:

@Bean
public RedisAtomicLong redisAtomicLong() {
return new RedisAtomicLong("contact", redisConnectionFactory());
}

If we need to create IDs for instances of different classes, we can use the same Redis counter. Thus, we have to configure only one Redis atomic long bean.

Spring Data Implement JPA repositories with less code and harness the performance of Redis in your applications with this book and ebook.
Published: November 2012
eBook Price: $17.99
Book Price: $24.99
See more
Select your format and quantity:

CRUD

Before we can start implementing the CRUD functions for the Contact class, we have to discuss a bit about the Redis data model of our application. We use two different data types for storing contact information to Redis. The information of a single contact is stored in a hash because as we know, a hash is a great structure for storing the information of complex objects. Also, we store the key of each contact in a set because a set provides us a fast capability to check if a contact exists. We also use this set when we are fetching a list of all contacts from Redis.

Our next step is to implement the ContactService interface that declares CRUD operations for contacts. Let's start by creating a dummy service implementation and add the actual CRUD methods later. The implementation of this class includes the following steps:

  1. Implementing the ContactService interface.
  2. Annotating the created class with the @Service annotation.
  3. Adding the required dependencies as private members of the created class and annotating these members with the @Resource annotation. We need to have a reference to both the RedisTemplate and RedisAtomicLong objects.

The source code of our dummy implementation is given as follows:

@Service public class RedisContactService implements ContactService { @Resource private RedisAtomicLong contactIdCounter; @Resource private RedisTemplate<String, String> redisTemplate; //Add methods here. }

The next step is to implement common methods that are used by the methods declared by the ContactService interface. These private methods are described in the following table:

Method

Description

String buildKey(Long contactId)

Returns a key for a contact.

Contact buildContact(String key)

Fetches the information of a contact and returns the found contact.

Contact buildContact(Long id)

Fetches the information of a contact and returns the found contact.

boolean contactDoesNotExist(Long id)

Returns false if a contact is found with the given ID and true otherwise.

String persist(Contact persisted)

Saves the contact information and returns the key of the contact.

First, we have to implement the method that is used to build keys for our contacts. Our implementation of the buildKey() method is quite simple. We build the key by appending the contact ID given as a parameter to a string contact and returning the resulting string. The source code of the buildKey() method is given as follows:

private String buildKey(Long contactId) { return "contact" + contactId; }

Second, we have to implement the method that is used to fetch contact information by using the key of the contact. We can implement the buildContact(String key) method by following these steps:

  1. Create a new Contact object.
  2. Fetch the information of the contact from the hash.

    We use bound hash operations because this way we have to provide the key only once.

  3. Return the created object.

The source code of the implemented method is given as follows:

private Contact buildContact(String key) { Contact contact = new Contact(); BoundHashops ops = redisTemplate.boundHashOps(key); contact.setId((Long) ops.get("id")); contact.setEmailAddress((String) ops.get("emailAddress")); contact.setFirstName((String) ops.get("firstName")); contact.setLastName((String) ops.get("lastName")); contact.setPhoneNumber((String) ops.get("phoneNumber")); Address address = new Address(); address.setStreetAddress((String) ops.get("streetAddress")); address.setPostCode((String) ops.get("postCode")); address.setPostOffice((String) ops.get("postOffice")); address.setState((String) ops.get("state")); address.setCountry((String) ops.get("country")); contact.setAddress(address); return contact; }

Third, we have to implement the method that fetches contact information by using the ID of the contact. Our implementation of the buildContact(Long id) method is rather simple, and it includes the following steps:

  1. Build the key of the contact.
  2. Get the contact by using the created key.
  3. Return the found contact.

The source code of this method is given as follows:

private Contact buildContact(Long id) { String key = buildKey(id); return buildContact(key); }

Fourth, we have to implement the method used to verify whether a contact in question exists or not. Our implementation of the contactDoesNotExist() method consists of the following steps:

  1. Create the key of the contact.
  2. Check if the key is found from the contacts set by calling the isMember() method of the SetOperations class, and passing the name of the set and the key as parameters.

    We use setOperations because we execute only one command.

  3. Inverse the return value of the isMember() method and return the inverted value.

The source code of this method is given as follows:

private boolean contactDoesNotExist(Long id) { String key = buildKey(id); return !redisTemplate.opsForSet().isMember("contacts", key); }

Fifth, we have to implement the method that saves the information of a single contact. Our implementation of the persist() method includes the following steps:

  1. If the persisted Contact object does not have an ID, create one calling the incrementAndGet() method of the RedisAtomicLong class and set the received Long object as the contact ID.
  2. Build a key for the persisted contact.
  3. Save the contact in the hash.
  4. Return the persisted contact.

The source code of the persist() method is given as follows:

private String persist(Contact persisted) { Long id = persisted.getId(); if (id == null) { id = contactIdCounter.incrementAndGet(); persisted.setId(id); } String contactKey = buildKey(id); BoundHashops ops = redisTemplate.boundHashOps(contactKey); ops.put("id", persisted.getId()); ops.put("emailAddress", persisted.getEmailAddress()); ops.put("firstName", persisted.getFirstName()); ops.put("lastName", persisted.getLastName()); ops.put("phoneNumber", persisted.getPhoneNumber()); Address address = persisted.getAddress(); ops.put("streetAddress", address.getStreetAddress()); ops.put("postCode", address.getPostCode()); ops.put("postOffice", address.getPostOffice()); ops.put("state", address.getState()); ops.put("country", address.getCountry()); return contactKey; }

We have now implemented the common methods of the RedisContactService class. Let's move on and find out how we can provide the CRUD operations for the contact information.

Create

We can create a new contact by following these steps:

  1. Save the added contact to the hash.
  2. Add the key of the contact to our contact set.
  3. Return the added contact.

The source code of the add() method is given as follows:

@Override public Contact add(Contact added) { String key = persist(added); redisTemplate.opsForSet().add("contacts", key); return added; }

Read

We have to provide two methods that are used to fetch contact information from Redis. The first method is used to return a list of existing contacts and the second one is used to find the information of a single contact.

First, we have to implement a method that is used to return a list of existing contacts. We can implement the findAll() method by following these steps:

  1. Create a new ArrayList object that is used to store the found Contact objects.
  2. Get the keys of existing contacts from the contact set.
  3. Get the information of each existing contact from the hash and add them to the created ArrayList object.
  4. Return the list of contacts.

The source code of the implemented method is given as follows:

@Override public List<Contact> findAll() { List<Contact> contacts = new ArrayList<Contact>(); Collection<String> keys = redisTemplate.opsForSet(). members("contacts"); for (String key: keys) { Contact contact = buildContact(key); contacts.add(contact); } return contacts; }

Second, we have to implement a method that is used to return the information of a single contact. We can implement the findById() method by following these steps:

  1. Check that the contact exists. If contact does not exist, throw NotFoundException.
  2. Get the contact from the hash.
  3. Return the found contact.

The source code of our method is given as follows:

@Override public Contact findById(Long id) throws NotFoundException { if (contactDoesNotExist(id)) { throw new NotFoundException("No contact found with id: " + id); } return buildContact(id); }

Update

We can update the information of an existing contact by following these steps:

  1. Check if that contact exists. If no contact is found, throw a NotFoundException.
  2. Save the updated contact information in the hash.
  3. Return the updated contact.

The source code of the update() method is given as follows:

@Override public Contact update(Contact updated) throws NotFoundException { if (contactDoesNotExist(updated.getId())) { throw new NotFoundException("No contact found with id: " + updated.getId()); } persist(updated); return updated; }

Delete

We can delete the information of a contact by following these steps:

  1. Get a reference of the deleted contact.

    We use the findById() method because it throws NotFoundException if the contact is not found.

  2. Build a key of the deleted contact.
  3. Remove the contact from our contact set.
  4. Remove the information of a contact from the hash.
  5. Return the deleted contact.

The source code of the deleteById() method is given as follows:

@Override public Contact deleteById(Long id) throws NotFoundException { Contact deleted = findById(id); String key = buildKey(id); redisTemplate.opsForSet().remove("contacts", key); BoundHashOperations operations = redisTemplate.boundHashOps(key); operations.delete("id"); operations.delete("emailAddress"); operations.delete("firstName"); operations.delete("lastName"); operations.delete("phoneNumber"); operations.delete("streetAddress"); operations.delete("postCode"); operations.delete("postOffice"); operations.delete("state"); operations.delete("country"); return deleted; }

Storing data in JSON

If we store object information in a hash, we have to write a lot of boilerplate code that is used to save, read, and delete contact information. This subsection describes how we reduce the amount of required code and implement a CRUD application that stores the contact information in JSON format. This means that StringRedisSerializer is used to serialize string values and that JacksonJsonRedisSerializer transforms our Contact objects into JSON.

Configuring the application context

We can configure the application context of our application by following these steps:

  1. Configure the value serializer bean.
  2. Configure the Redis template.
  3. Configure the Redis atomic long bean.

Configuring the value serializer bean

We can configure the value serializer bean by adding a contactSerializer() method to the ApplicationContext class and annotating it with the @Bean annotation. We can implement this method by following these steps:

  1. Create a new JacksonJsonRedisSerializer object and pass the type of the Contact class as a constructor parameter.
  2. Return the created object.

The source code of the contactSerializer() method is given as follows:

@Bean public RedisSerializer<Contact> valueSerializer() { return new JacksonJsonRedisSerializer<Contact>(Contact.class); }

Configuring the Redis template bean

We can configure the Redis template by adding a redisTemplate() method to the ApplicationContext class, annotating it with the @Bean annotation and configuring the Redis template in its implementation. We can implement this method by following these steps:

  1. Create a new RedisTemplate object and give the type of our key and value as type parameters.
  2. Set the used connection factory.
  3. Set the used value serializer.
  4. Return the created object.

The source code of the redisTemplate() method is given as follows:

@Bean public RedisTemplate redisTemplate() { RedisTemplate<String, Contact> redisTemplate = new RedisTemplate<String, Contact>(); redisTemplate.setConnectionFactory(redisConnectionFactory()); redisTemplate.setValueSerializer(valueSerializer()); return redisTemplate; }

Configuring the Redis atomic long bean

We will start the configuration of the Redis atomic long bean by adding a redisAtomicLong() method to the ApplicationContext class and annotating it with the @Bean annotation. Our next step is to implement this method by following these steps:

  1. Create a new RedisAtomicLong object. Pass the name of the used Redis counter and the Redis connection factory as constructor parameters.
  2. Return the created object.

The source code of the redisAtomicLong() method is given as follows:

@Bean public RedisAtomicLong redisAtomicLong() { return new RedisAtomicLong("contact", redisConnectionFactory()); }

CRUD

First we have to talk about our Redis data model. We store the contact information to Redis, using two different data types. We store the information of a single contact to Redis as a string value. This makes sense since the contact information is transformed to the JSON format before it is saved. We will also use a set thatcontains the JSON representations of the Contact objects. We have to duplicate the information because otherwise we would not be able to show a list of contacts.

We can provide the CRUD operations for the Contact objects by implementing theContactService interface. Let's start by creating a dummy service implementation and adding or implementing the actual CRUD operations later. The steps needed to create a dummy service implementation are described as follows:

  1. Implement the ContactService interface.
  2. Annotate the created class with the @Service annotation.
  3. Add the required dependencies as private members of the created class and annotate these members with the @Resource annotation. We need to have a reference to both the RedisTemplate and RedisAtomicLong objects.

The source code of our dummy service implementation is given as follows:

@Service public class RedisContactService implements ContactService { @Resource private RedisAtomicLong contactIdCounter; @Resource private RedisTemplate<String, Contact> redisTemplate; //Add methods here }

We also have to implement some utility methods that are used by the methods declared by the ContactService interface. These private methods are described in the following table:

Method

Description

String buildKey(Long contactId)

Returns a key for a contact.

void persist(Contact persisted)

Saves the contact information to a string

value.

First, we have to implement a method that is used to build keys for the persisted Contact objects. The implementation of the buildKey() method is simple. We build the key by appending the contact ID given as a parameter to a string contact and return the resulting string. The source code of the buildKey() method is given as follows:

private String buildKey(Long contactId) { return "contact" + contactId; }

Second, we have to implement a persist() method that saves the contact information. We can do this by performing the following steps:

  1. If the contact ID is null, get a new ID and set the received Long object as an ID of the Contact object.
  2. Create a key for the contact.
  3. Save the contact information as a string value.

We use value operations because we need to execute only one operation.

The source code of the persist() method is given as follows:

private void persist(Contact persisted) { Long id = persisted.getId(); if (id == null) { id = contactIdCounter.incrementAndGet(); persisted.setId(id); } String key = buildKey(persisted.getId()); redisTemplate.opsForValue().set(key, persisted); }

We are now ready to start implementing the CRUD operations for contacts. Let's move on and find out how it is done.

Create

We can implement a method that adds new contacts by following these steps:

  1. Save the added contact.
  2. Add the contact information into the contact set.
  3. Return the added contact.

The source code of the add() method is given as follows:

@Override public Contact add(Contact added) { persist(added); redisTemplate.opsForSet().add("contacts", added); return added; }

Read

Our application has two views that present contact information: the first one shows a list of contacts and the second one shows the information of a single contact.

First, we have to implement a method that fetches all the contacts from Redis. We can implement the findAll() method by following these steps:

  1. Fetch all the contacts from the contact set.
  2. Create a new ArrayList object and return the created object.

The source code of the findAll() method is given as follows:

@Override public List<Contact> findAll() { Collection<Contact> contacts = redisTemplate.opsForSet(). members("contacts"); return new ArrayList<Contact>(contacts); }

Second, we have to implement a method that returns the information of a single contact. Our implementation of the findById() method includes the following steps:

  1. Create the key of the contact.
  2. Get the Contact object from Redis.
  3. If no contact is found, throw NotFoundException.
  4. Return the found object.

The source code of the findById() method is given as follows:

@Override public Contact findById(Long id) throws NotFoundException { String key = buildKey(id); Contact found = redisTemplate.opsForValue().get(key); if (found == null) { throw new NotFoundException("No contact found with id: {}" + id); } return found; }

Update

We can update the information of an existing contact by following these steps:

  1. Get the old contact information from Redis.
  2. Save the updated contact information.
  3. Remove the old contact information from the contact set. This ensures that our set does not contain duplicate entries for the same contact.
  4. Add the updated contact information to the contact set.
  5. Return the updated contact.

The source code of the update() method is given as follows:

@Override public Contact update(Contact updated) throws NotFoundException { Contact old = findById(updated.getId()); persist(updated); redisTemplate.opsForSet().remove("contacts", old); redisTemplate.opsForSet().add("contacts", updated); return updated; }

Delete

We can delete contact information by following these steps:

  1. Find the deleted contact by calling the findById() method. This ensures that NotFoundException is thrown if the contact is not found.
  2. Build a key used to get the contact information.
  3. Remove the deleted contact from the contact set.
  4. Remove the JSON representation of the deleted contact.
  5. Return the updated contact.

The source code of the delete() method is given as follows:

@Override public Contact deleteById(Long id) throws NotFoundException { Contact deleted = findById(id); String key = buildKey(id); redisTemplate.opsForSet().remove("contacts", deleted); redisTemplate.opsForValue().set(key, null); return deleted; }

The publish/subscribe messaging pattern

Redis also includes an implementation of the publish/subscribe messaging pattern. This section demonstrates how we can use Spring Data Redis for the purpose of sending and receiving messages. As an example, we will modify the CRUD application that stores the contact information as JSON to send notifi cations when a new contact is added, contact information is updated, and a contact is deleted.

We can implement this requirement by performing the following steps:

  1. Create message listeners that process the received messages.
  2. Configure the application context of our application.
  3. Send messages by using the RedisTemplate class.

This section also describes how we can ensure that our implementation is working correctly.

Creating message listeners

There are two ways to create message listeners by using Spring Data Redis: we can implement the MessageListener interface or we can create a POJO message listener and use the MessageListenerAdapter class to delegate messages to it. Both of these approaches are discussed in this subsection.

Implementing the MessageListener interface

The first way to create a message listener is to implement the MessageListener interface. Our implementation includes the following steps:

  1. Create a new Logger object that is used to log the received messages.
  2. Create a new StringRedisSerializer object that is used to transform byte arrays to String objects.
  3. Implement the onMessage() method declared by the MessageListener interface. This method simply logs the received message.

The source code of the ContactListener class is given as follows:

public class ContactMessageListener implements MessageListener { private final static Logger LOGGER = LoggerFactory.getLogger(Conta ctMessageListener.class); private RedisSerializer<String> stringSerializer = new StringRedisSerializer(); @Override public void onMessage(Message message, byte[] pattern) { LOGGER.debug("MessageListener - received message: {} on channel: {}", stringSerializer.deserialize(message.getBody()), stringSerializer.deserialize(message.getChannel())); } }

Creating a POJO message listener

The second way to create message listeners is to create a normal Java class. We can do this by following these steps:

  1. Create a new Logger object that is used to log the received messages.
  2. Create a message handler method called handleMessage() that takes the Contact object and a String object as parameters.
  3. Implement the handleMessage() method . This method logs the received message.

The source code of the ContactPOJOMessageListener class is given as follows:

public class ContactPOJOMessageListener { private static final Logger LOGGER = LoggerFactory.getLogger(Conta ctPOJOMessageListener.class); public void handleMessage(Contact contact, String channel) { LOGGER.debug("Received contact: {} on channel: {}", contact, channel); } }

Spring Data Implement JPA repositories with less code and harness the performance of Redis in your applications with this book and ebook.
Published: November 2012
eBook Price: $17.99
Book Price: $24.99
See more
Select your format and quantity:

Configuring the application context

We have to make the following changes to the application context configuration:

  1. Configure the message listener beans.
  2. Configure a message listener adapter bean.
  3. Configure a message listener container bean.

Configuring the message listener beans

First, we have to configure our message listener beans. The configuration is rather simple. We just create new message listener objects and return the created objects. The source code of the message listener bean configuration is given as follows:

@Bean public ContactMessageListener contactMessageListener() { return new ContactMessageListener(); } @Bean public ContactPOJOMessageListener contactPOJOMessageListener() { return new ContactPOJOMessageListener(); }

Configuring the message listener adapter bean

Next we have to configure the message listener adapter bean that is used to delegate the messages forward to our POJO message listener. We can configure this bean by following these steps:

  1. Create a new MessageListenerAdapter object and pass the ContactPOJOMessageListener object as a constructor parameter.
  2. Set the serializer that is used to transform the received message to a Contact object.
  3. Return the created object.

The source code of the messageListenerAdapter() method is given as follows:

@Bean public MessageListenerAdapter messageListenerAdapter() { MessageListenerAdapter adapter = new MessageListenerAdapter(contac tPOJOMessageListener()); adapter.setSerializer(contactSerializer()); return adapter; }

The defaultListenerMethod property of the MessageListenerAdapter class is used to configure the name of the message handler method. The default value of this property is handleMessage.

Configuring the message listener container bean

The message listener container is a component that listens to the messages that are sent through the different channels and forwards these messages to the registered message listeners. We can configure this component by following these steps:

  1. Create a new RedisMessageListenerContainer object..
  2. Set the used Redis connection factory.
  3. Register the message listeners and specify the subscribed channels.
  4. Return the created object.

The source code of our configuration is given as follows:

@Bean public RedisMessageListenerContainer redisMessageListenerContainer()
{ RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(redisConnectionFactory()); container.addMessageListener(messageListenerAdapter(), Arrays.asList( new ChannelTopic("newContacts"), new ChannelTopic("updatedContacts"), new ChannelTopic("removedContacts") )); container.addMessageListener(contactMessageListener(), Arrays.asList( new ChannelTopic("newContacts"), new ChannelTopic("updatedContacts"), new ChannelTopic("removedContacts") )); return container; }

Sending messages with RedisTemplate

We can send publish messages to different channels by using the convertAndSend(String channel, Object message) method of the RedisTemplate class. This subsection describes how we can send notifications about new contacts, updated contacts, and removed contacts by using this method.

Create

In order to send change notifications about new contacts, we have to modify the add() method of the RedisContactService class to call the convertAndSend() method of the RedisTemplate class after the information of a new contact is saved successfully. The source code of our new add() method is given as follows:

@Override
public Contact add(Contact added) {
persist(added);
redisTemplate.opsForSet().add("contacts", added);
redisTemplate.convertAndSend("newContacts", added);
return added;
}

Update

We can send notifications about updated contacts by modifying the update() method of the RedisContactService class. We simply call the convertAndSend() method of the RedisTemplate class after the contact information is updated. The source code of the new update() method is given as follows:

@Override
public Contact update(Contact updated) throws NotFoundException {
Contact old = findById(updated.getId());
persist(updated);
redisTemplate.opsForSet().remove("contacts", old);
redisTemplate.opsForSet().add("contacts", updated);
redisTemplate.convertAndSend("updatedContacts", updated);
return updated;
}

Delete

We can send a notification about the deleted contacts by making a small change in the deleteById() method of the RedisContactService class. After the contact information is deleted, we will call the convertAndSend() method of the RedisTemplate class, which sends the notifi cation message. The source code of the modified deleteById() method is given as follows:

@Override
public Contact deleteById(Long id) throws NotFoundException {
Contact deleted = findById(id);
String key = buildKey(id);
redisTemplate.opsForSet().remove("contacts", deleted);
redisTemplate.opsForValue().set(key, null);
redisTemplate.convertAndSend("removedContacts", deleted);
return deleted;
}

Verifying the wanted behaviour

We have now implemented our message listeners and modified our application to send a notification message every time the contact information is changed. Our next step is to verify that our implementation is working as expected.

We can confirm this by making changes to the contact information and making sure that log lines written by our message listeners appear in the application's log. The loglines that are written when a new contact is added are given as follows:

DEBUG - ContactMessageListener - Received message: {"id":9,"add ress":{"country":"","streetAddress":"","postCode":"","postOffice":" ","state":""},"emailAddress":"","firstName":"Foo","lastName":"Bar"- ,"phoneNumber":""} on channel: newContacts DEBUG - ContactPOJOMessageListener - Received contact: com.packtpub. springdata.redis.model.Contact@543d8ee8[id=9,address=com.packtpub. springdata.redis.model.Address@15714c8d[country=,streetAddress=,postCo de=,postOffice=,state=],emailAddress=,firstName=Foo,lastName=Bar,phone Number=] on channel: null

Note that the channel information passed to a POJO message handler is always null. This is a known bug of Spring Data Redis. More information about this is available at https://jira.springsource.org/browse/DATAREDIS-98.

Using Spring cache abstraction with Spring Data Redis

The cache abstraction of Spring Framework 3.1 applies caching to Java methods. When a cached method is called, the cache abstraction will check from the cache if the method has been called earlier by using the same parameters. If this is the case, then the return value is fetched from the cache and the method is not executed. Otherwise, the method is executed and its return value is stored in the cache.

The cache abstraction of Spring Framework 3.1 is explained in more detail at http://static.springsource.org/spring/ docs/3.1.x/spring-framework-reference/html/cache.html.

Spring Data Redis provides an implementation of the Spring cache abstraction. Using Redis as a cache has two benefits over using local caching implementations such as Ehcache:

  • It can be used as a centralized cache that is shared by each servlet container or application server that runs our application. This reduces the overall number of database queries, which reduces the load of our database server and increases the performance of all the servers.
  • The cache will not be emptied until we empty it. This means that we can restart our servlet container or application server without losing the information stored in the cache. After our server is restarted, it can take full advantage of the cached information right away. There is no need to warm up the cache.

This section describes how we can use Spring Data Redis for the purpose of adding a caching support to an application that uses the JPA Criteria API. The requirements of our caching example are as follows:

  • The method calls to the method that finds the information of a single contact from the database must be cached
  • When the information of a contact is updated, the information stored in the cache must be updated as well
  • When a contact is deleted, the deleted contact must be removed from the cache

We can add caching support to our example application by following these steps:

  1. Configure the Spring cache abstraction.
  2. Identify the cached methods.

We will also learn how we can verify that the Spring cache abstraction is working correctly.

Configuring the Spring cache abstraction

We can configure the Spring cache abstraction by making the following changes to the application context configuration of our application:

  1. Enable the caching annotations.
  2. Configure the host and port of the used Redis instance in the used properties file.
  3. Configure the Redis connection factory bean.
  4. Configure the Redis template bean.
  5. Configure the cache manager bean.

Enabling caching annotations

We can enable the caching annotations by annotating our application context configuration class with the @EnableCaching annotation . The relevant part of the ApplicationContext class is given as follows:

@Configuration
@ComponentScan(basePackages = {
"com.packtpub.springdata.jpa.controller",
"com.packtpub.springdata.jpa.service"
})
@EnableCaching
@EnableTransactionManagement
@EnableWebMvc
@EnableJpaRepositories("com.packtpub.springdata.jpa.repository")
@PropertySource("classpath:application.properties")
public class ApplicationContext extends WebMvcConfigurerAdapter {
@Resource
private Environment env;
//Bean declarations
}

Configuring the host and port of the used Redis instance

In order to configure the host and port of the used Redis instance, we have to add the following lines to the application.properties file:

redis.host = localhost redis.port = 6379

Configuring the Redis connection factory bean

We can configure the Redis connection factory bean by adding a redisConnectionFactory() method to the ApplicationContext class and annotating this method with the @Bean annotation. We can implement this method by following these steps:

  1. Create a new JedisConnectionFactory object.
  2. Configure the host and port of the used Redis instance.
  3. Return the created object.

The source code of the redisConnectionFactory() method is given as follows:

@Bean public RedisConnectionFactory redisConnectionFactory() { JedisConnectionFactory cf = new JedisConnectionFactory(); cf.setHostName(env.getRequiredProperty("redis.host")); cf.setPort(Integer.parseInt(env.getRequiredProperty("redis. port"))); return cf; }

Configuring the Redis template bean

In order to configure the Redis template bean, we have to add a redisTemplate() method to the ApplicationContext class and annotate this method with the @Bean annotation. Our implementation of this method includes the following steps:

  1. Create a new RedisTemplate object.
  2. Set the used Redis connection factory.
  3. Return the created object.

The source code of the redisTemplate() method is given as follows:

@Bean public RedisTemplate redisTemplate() { RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>(); redisTemplate.setConnectionFactory(redisConnectionFactory()); return redisTemplate; }

Configuring the cache manager bean

Our last step is to configure the cache manager bean. We can do this by adding the cacheManager() method to the ApplicationContext class and annotating this method with the @Bean annotation. We can implement this method by following these steps:

  1. Create a new RedisCacheManager object and provide the used Redis template as a constructor parameter.
  2. Return the created object.

The source code of the cacheManager() method is given as follows:

@Bean public RedisCacheManager cacheManager() { return new RedisCacheManager(redisTemplate()); }

Identifying the cached methods

We have now configured the Spring cache abstraction and we are ready to identify the cached methods. This subsection describes how we can add contact information in the cache, update contact information that is already stored in the cache, and delete the contact information from the cache.

Adding contact information to the cache

In order to add contact information to the cache, we must cache the method calls to the findById() method of the RepositoryContactService class. We can do this by annotating the method with the @Cacheable annotation and providing the name of the cache. This tells the cache abstraction that the returned contact should be added to the contacts cache by using the provided ID as a key. The source code of the findById() method is given as follows:

@Cacheable("contacts")
@Transactional(readOnly = true)
@Override
public Contact findById(Long id) throws NotFoundException {
//Implementation remains unchanged.
}

Updating the contact information to the cache

We can update the contact information that is stored in the cache by annotating the update() method of the RepositoryContactService class with the @CachePut annotation . We will also have to provide the name of the cache and specify that the id property of the ContactDTO object is used as a key when the return value of this method is updated to the cache. The source code of the update() method is given as follows:

@CachePut(value = "contacts", key="#p0.id")
@Transactional(rollbackFor = NotFoundException.class)
@Override
public Contact update(ContactDTO updated) throws NotFoundException {
//Implementation remains unchanged.
}

Deleting contact information from the cache

We can delete contact information from the cache by annotating the deleteById() method with the @CacheEvict annotation and providing the name of the cache as its value. This means that the cache abstraction removes the deleted contact from the cache after the method has been executed. The removed contact is identified by the ID given as a method parameter. The source code of the deleteById() method is given as follows:

@CacheEvict("contacts")
@Transactional(rollbackFor = NotFoundException.class)
@Override
public Contact deleteById(Long id) throws NotFoundException {
//Implementation remains unchanged
}

Verifying that the Spring cache abstraction is working

We have now successfully added caching to our example application. We can verify that the Spring cache abstraction is working properly by using the cached methods and looking for the following lines from the log file of our application:

DEBUG - RedisConnectionUtils - Opening Redis Connection DEBUG - RedisConnectionUtils - Closing Redis Connection

If these lines are found from the log file it can mean that:

  • The contact information is fetched from the cache instead of the used database
  • The contact information is updated to the cache
  • The contact information is removed from the cache

Summary

In this article, we have learned that:

  • Designing a Redis data model is totally different from designing a data model of a relational database
  • We can use Redis as data storage of a web application
  • Spring Data Redis provides a clean integration with the Redis publish/ subscribe implementation
  • We can use Redis as a centralized cache of our application by using the Spring cache abstraction

Resources for Article :


Further resources on this subject:


About the Author :


Petri Kainulainen

Petri Kainulainen is a software developer living in Tampere, Finland. He is specialized in application development with the Java programming language and the Spring framework. Petri has over 10 years of experience in software development, and during his career he has participated in the development projects of Finland's leading online market places as a software architect. He is currently working at Vincit Oy as a passionate software developer.

Books From Packt


Spring 2.5 Aspect Oriented Programming
Spring 2.5 Aspect Oriented Programming

Spring Security 3.1
Spring Security 3.1

Spring Roo 1.1 Cookbook
Spring Roo 1.1 Cookbook

Spring Web Flow 2 Web Development
Spring Web Flow 2 Web Development

Learning Vaadin
Learning Vaadin

Spring Python 1.1
Spring Python 1.1

Spring Security 3
Spring Security 3

Spring Web Services 2 Cookbook
Spring Web Services 2 Cookbook


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software