NoSQL Zone is brought to you in partnership with:

Shekhar Gulati is a Java Consultant with over 5 years experience.He is currently working with Xebia India an Agile Software Development company.The postings on this site and on his blog are his own and do not necessarily represent opinion of his employer. His own blog is at http://whyjava.wordpress.com/ and you can follow him on twitter here. Shekhar has posted 25 posts at DZone. View Full User Profile

Creating an Application using Spring Data with Redis as Datastore -- Part 1

02.01.2011
| 22299 views |
  • submit to reddit

In the first part of the series on Redis I talked about what is Redis datastore, data types supported by Redis and how you can get started with Redis.  In this article, I am going to talk about how a Java developer can programmatically access Redis and perform operations using Spring Data. Spring Data is a new project to build spring based applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services. Spring Data has a lot of sub-projects that are specific to a given datastore . We will only take a look at spring-data-keyvalue sub-project and will only talk about support of Redis key-value store. The spring-data-keyvalue store also provide support for another key-value store called Riak but I will restrict this article to only Redis.

The SDKV(spring-data-keyvalue) project provides an abstraction over the existing Redis client libraries like Jedis, JRedis. It makes it very easy to work with Redis key-value datastore by eliminating the boiler plate code required for interacting with Redis. The SDKV provides a generified template class named RedisTemplate which is very similar to JDBCTemplate or HibernateTemplate to interact with Redis. This relieves developers from learning low-level  API.

Prerequisites

Before you start working, make sure you have the following

  1. Redis (Windows user can download Redis from dmajkic git repository)
  2. Java V6 JDK
  3. Apache Maven V2.0.9 or above
  4. SpringSource Tool Suite
  5. Git

If you face any issue in installing Redis server please refer to my previous article.

Build spring-data-keyvalue source code

This article will use the current development version(1.0.0.M2) of spring-data-keyvalue project. To get the latest source code  you have to checkout the spring-data-keyvalue project using Git by typing following command

git clone git://github.com/SpringSource/spring-data-keyvalue.git

This command will create a folder named spring-data-keyvalue which will have all the source code. We have to build this source code so the artifacts are available in your maven repository. Before you build the project, you have to start the Redis using redis-server command. Once Redis is running, please do mvn clean install over the project. This will build the project and put the required artifacts in your local maven repository.

Create a Template project using STS

We need to create a Spring template project so that we can use it for our simple application. To do that open STS and go to File -> New -> Spring Template Project -> Simple Spring Utility Project --> press Yes when prompted. Please provide project name and the top-level package name.  I am using project name as dictionary and package name as "com.redis.dictionary".  This will create a sample project named dictionary in your STS workspace.

Modify pom.xml

The project that we created above does not have dependencies related to SDKV project. In order to do that please replace the pom.xml of the template project we just created above with the pom.xml shown below.

<?xml version="1.0" encoding="UTF-8"?>
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.shekhar</groupId>
<artifactId>redis</artifactId>
<name>redis-dictionary</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>1.6</java-version>
<org.springframework-version>3.0.5.RELEASE</org.springframework-version>
<org.springframework.roo-version>1.0.2.RELEASE</org.springframework.roo-version>
<org.aspectj-version>1.6.9</org.aspectj-version>
<redis.version>1.0.0.M2-SNAPSHOT</redis.version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of <span class="hiddenSpellError" pre="of ">SLF4j</span> -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- <span class="hiddenSpellError" pre="">AspectJ</span> -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>

<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency>

<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>

<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>${redis.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-keyvalue-core</artifactId>
<version>${redis.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${org.springframework-version}</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework-version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-maven-milestone</id>
Springframework Maven Repository
<url>http://maven.springframework.org/milestone</url>
</repository>
<repository>
<id>spring-maven-snapshot</id>
<snapshots>
<enabled>true</enabled>
</snapshots>
Springframework Maven SNAPSHOT Repository
<url>http://maven.springframework.org/snapshot</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java-version}</source>
<target>${java-version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>install</id>
<phase>install</phase>
<goals>
<goal>sources</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

 

Creating a Dictionary Application

Now that we have completed all the preliminary stuff required for working with Redis and Spring data. Let's build a small application which will help us understand how we can use SDKV API with Redis.

The application that we are going to build is a simple dictionary application which will let us perform CRUD operations on the Redis datastore. A dictionary is a collection of words where each word can have multiple meanings. The dictionary application can be very easily modeled to Redis List datatype with word as the key of list and meanings as its value. Instead of List you can also use Set if you want meanings should be unique. For example, we can have a word "astonishing" as a key and "astounding", "staggering" as its values. Let's create a simple word meaning list using redis-cli.  Please start Redis using redis-server command and client using redis-cli command.

redis> RPUSH astonishing astounding
(integer) 1
redis> RPUSH astonishing staggering
(integer) 2
redis> LRANGE astonishing 0 -1
1) "astounding"
2) "staggering"

In the above redis commands snippet, we created a list named astonishing and we pushed meanings astounding and staggering in the end of astonishing list. To get all the meanings for astonishing we using LRANGE command.

Now that we have seen how it works using redis-cli lets create a class called DictionaryDao which will let us perform CRUD operations on Redis using SDKV API. As I have talked above that the core class in SDKV project is RedisTemplate we will inject RedisTemplate in our class.

import org.springframework.data.keyvalue.redis.core.RedisTemplate;

public class DictionaryDao {

private RedisTemplate<String, String> template;

public DictionaryDao(RedisTemplate template) {
this.template = template;
}

public Long addWordWithItsMeaningToDictionary(String word, String meaning) {
Long index = template.opsForList().rightPush(word, meaning);
return index;
}

}

RedisTemplate provides key type operations like ValueOperations,ListOperations, SetOperations, HashOperations, ZSetOperations. In the code shown above I have used ListOperations to store a new word in the Redis datastore. As we are using rightPush operation meanings will get added to end of the list. This method will return the index to which the element is added in the list. Lets write JUnit test case for this method

@Test
public void shouldAddWordWithItsMeaningToDictionary() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setUsePool(true);
factory.setPort(6379);
factory.setHostName("localhost");
factory.afterPropertiesSet();
RedisTemplate<String, String> template = new RedisTemplate<String, String>(
factory);

DictionaryDao dao = new DictionaryDao(template);

Long index = dao.addWordWithItsMeaningToDictionary("lollop",
"To move forward with a bounding, drooping motion.");
assertThat(index, is(notNullValue()));
assertThat(index, is(equalTo(1L)));
}

In the test shown above, we first created the JedisConnectionFactory because RedisTemplate requires a connection factory which it will use for connecting to Redis. There is also another connection factory called JRedisConnectionFactory which can also be used for connecting with Redis. When you run this test, this test will pass and word will get stored in Redis. But when you will run this test again, this test will fail because Redis will add the same meaning again and index returned will be 2. So, we have to clean the Redis datastore after each run. To clean up the Redis datastore we have to use either flushAll() or flushDb server commands. The difference between flushAll() and flushDb is that flushAll will remove all keys from all databases where as flushDb() will remove keys from only current database. So, we can modify our test as shown below

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.keyvalue.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.keyvalue.redis.core.RedisTemplate;

public class DictionaryDaoIntegrationTest {

private RedisTemplate<String, String> template;
private DictionaryDao dao;

@Before
public void setUp() throws Exception {
this.template = getRedisTemplate();
this.template.afterPropertiesSet();
dao = new DictionaryDao(template);
}

protected JedisConnectionFactory getConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setUsePool(true);
factory.setPort(6379);
factory.setHostName("localhost");
factory.afterPropertiesSet();
return factory;
}

protected RedisTemplate<String, String> getRedisTemplate() {
return new RedisTemplate(getConnectionFactory());
}

@After
public void tearDown() throws Exception {
template.getConnectionFactory().getConnection().flushAll();
template.getConnectionFactory().getConnection().close();
}

@Test
public void shouldAddWordWithItsMeaningToDictionary() {
Long index = dao.addWordWithItsMeaningToDictionary("lollop",
"To move forward with a bounding, drooping motion.");
assertThat(index, is(notNullValue()));
assertThat(index, is(equalTo(1L)));
}

@Test
public void shouldAddMeaningToAWordIfItExists() {
Long index = dao.addWordWithItsMeaningToDictionary("lollop",
"To move forward with a bounding, drooping motion.");
assertThat(index, is(notNullValue()));
assertThat(index, is(equalTo(1L)));
index = dao.addWordWithItsMeaningToDictionary("lollop",
"To hang loosely; droop; dangle.");
assertThat(index, is(equalTo(2L)));
}

}

 Now that we have the functionality to store the word in Redis datastore it makes sense to have the functionality to get all the meanings for a word. This can be handled very easily using List's range operation.The range() method takes three arguments - name of the key, start and end of range. In order to get all the meanings for a word we can use start as 0 and end as -1.

public List getAllTheMeaningsForAWord(String word) {
List<String> meanings = template.opsForList().range(word, 0, -1);
return meanings;
}

The last feature that I would like to add in this article is deleting an existing word. This can be done using delete operation of RedisTemplate class. The delete operation takes a collection of keys.

public void removeWords(String... words) {
template.delete(Arrays.asList(words));
}

Lets write JUnit test cases for the Read and Delete operations that we have added above.

@Test
public void shouldGetAllTheMeaningForAWord() {
setupOneWord();
List allMeanings = dao.getAllTheMeaningsForAWord("lollop");
assertThat(allMeanings.size(), is(equalTo(2)));
assertThat(
allMeanings,
hasItems("To move forward with a bounding, drooping motion.",
"To hang loosely; droop; dangle."));
}

@Test
public void shouldDeleteAWordFromDictionary() throws Exception {
setupOneWord();
dao.removeWords("lollop");
List allMeanings = dao.getAllTheMeaningsForAWord("lollop");
assertThat(allMeanings.size(), is(equalTo(0)));
}

private void setupOneWord() {
dao.addWordWithItsMeaningToDictionary("lollop",
"To move forward with a bounding, drooping motion.");
dao.addWordWithItsMeaningToDictionary("lollop",
"To hang loosely; droop; dangle.");
}

Conclusion to First Part

In this article, we have just started with Spring Data Redis support. We have just looked at some of the List operations provided by SDKV project. In the future parts, I will talk about other datatypes, pub-sub support, using MULTI-EXEC block. You can get the source code for this series on my github repository.

Published at DZone with permission of its author, Shekhar Gulati.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Manuel Jordan replied on Tue, 2011/02/01 - 1:37pm

Thank you for share this, waiting the second part

Shiju Thomas replied on Wed, 2011/02/23 - 9:52pm

Hi Shekhar,

Thanks for a great post. I was trying to use the "Hash" based operations.

I was trying to persist hash values something like this

template.opsForHash().put("WL", "docId1", fileContentAsString);

 where docId and fileCOntent are strings and when I retirieve them like this

template.opsForHash().get("WL", "docId1"); I get null.

I am using 2.1.18 version of redis on windows. Anyhelp is greatly appreciated.

Thanks.

 -Joe

 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.