Redis using Spring Data – Part 2/3

This is the second part of tutorial for Redis using Spring Data series. In the first part we have covered installing redis server, using redis cli and two minute tutorial about redis data types. In case you have missed it you can revisit the first part of the tutorial.

In this article we will create a java application using step by step approach using maven and spring-data-redis to explore various data structures supported by redis. This article assumes that you have working installation of maven on your system.

Create the Project
Run the following command from your terminal to create a template java project using maven.
$ mvn archetype:generate -DgroupId=com.wordpress.omanandj -DartifactId=spring-data-redis-demo -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

This will generate the basic project structure in the directory spring-data-redis-demo. Open the pom.xml file and replace the content with following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.wordpress.omanandj</groupId>
  <artifactId>spring-data-redis-demo</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>spring-data-redis-demo</name>
 
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java-version>1.6</java-version>
        <org.springframework-version>3.2.1.RELEASE</org.springframework-version>
        <org.aspectj-version>1.6.9</org.aspectj-version>
        <redis.version>1.0.5.RELEASE</redis.version>
        <cglib.version>2.2.2</cglib.version>
        <slf4j.version>1.6.6</slf4j.version>
        <apache-log4j-extras.version>1.1</apache-log4j-extras.version>
    </properties>
  <dependencies>
      <dependency>
          <groupId>org.springframework.data</groupId>
          <artifactId>spring-data-redis</artifactId>
          <version>${redis.version}</version>
      </dependency>
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-core</artifactId>
          <version>${org.springframework-version}</version>
      </dependency>
      <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjrt</artifactId>
          <version>${org.aspectj-version}</version>
      </dependency>
      <dependency>
          <groupId>cglib</groupId>
          <artifactId>cglib</artifactId>
          <version>${cglib.version}</version>
      </dependency>
      <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
          <version>${slf4j.version}</version>
      </dependency>
      <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-log4j12</artifactId>
          <version>${slf4j.version}</version>
      </dependency>
      <dependency>
          <groupId>log4j</groupId>
          <artifactId>apache-log4j-extras</artifactId>
          <version>${apache-log4j-extras.version}</version>
      </dependency>
 
      <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Spring beans configuration
In this section we will use java based configuration to plugin spring beans for our demo project. We will use redis recommended java client library jedis for communicating with redis database. Spring-data-redis provides RedisTemplate. For most basic use case we will use string focused extension of Redis Template – StringRedisTemplate.
Create a class AppConfiguration in the package com.wordpress.omanandj.config to plugin spring beans. The AppConfiguration class content can be copied from following content :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.wordpress.omanandj.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
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.StringRedisTemplate;
import redis.clients.jedis.JedisPoolConfig;
 
@Configuration
@ComponentScan(&quot;com.wordpress.omanandj&quot;)
public class AppConfiguration {
    @Bean
    public RedisConnectionFactory getConnectionFactory() {
        JedisConnectionFactory jRedisConnectionFactory = new JedisConnectionFactory(new JedisPoolConfig());
        jRedisConnectionFactory.setHostName(&quot;localhost&quot;);
        jRedisConnectionFactory.setPort(6379);
        jRedisConnectionFactory.setPassword(&quot;&quot;);
        return jRedisConnectionFactory;
 
    }
    @Bean(name= &quot;stringRedisTemplate&quot;)
    public StringRedisTemplate getStringRedisTemplate(){
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(getConnectionFactory());
        return  stringRedisTemplate;
    }
 
}

Create a simple String based Key/Value cache implementation using Redis
Let’s create a persistent distributed caching storage using redis. Our cache repository interface has basic api requirement to be able to store a single key/value in datastore, multiple key/values in datastore in a single network call, retrieve a single key or multiple key, remove a key. Create an generic interface ICacheRepository for our API requirement as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.wordpress.omanandj.repository;
 
import java.util.Collection;
import java.util.List;
import java.util.Map;
 
public interface ICacheRepository<K,V> {
    void put(K key, V value);
 
    void multiPut(Map<K,V> keyValues);
 
    V get(K key);
 
    List<V> multiGet(Collection<K> keys);
 
    void delete(K key);
 
}

Now create a simple cache repository implementation for the interface ICacheRepository with string as key and value to explore . The implementation of saving an key/value can be done as:

1
2
3
public void put(String key, String value) {
        redisTemplate.opsForValue().set(key,value);
   }

Here opsForValue method provides the interface for saving simple key/value pair in the redis datastore. The set method is equivalent to $ set key value. Now implement other methods of the ICacheRepository as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.wordpress.omanandj.repository.impl;
 
import com.wordpress.omanandj.repository.ICacheRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
 
import java.util.Collection;
import java.util.List;
import java.util.Map;
 
 
@Component("simpleCacheRepository")
public class SimpleCacheRepository implements ICacheRepository<String,String> {
 
    @Autowired
    @Qualifier("stringRedisTemplate")
    private RedisTemplate<String,String> redisTemplate;
 
    @Override
    public void put(String key, String value) {
         redisTemplate.opsForValue().set(key,value);
    }
 
    @Override
    public void multiPut(Map<String, String> keyValues) {
         redisTemplate.opsForValue().multiSet(keyValues);
    }
 
    @Override
    public String get(String key) {
        return redisTemplate.opsForValue().get(key);
    }
 
    @Override
    public List<String> multiGet(Collection<String> keys) {
        return redisTemplate.opsForValue().multiGet(keys);
    }
 
    @Override
    public void delete(String key) {
        redisTemplate.delete(key);
    }
}

Now let us test our SimpleCacheRepository implementation. Open the App.java in the project and test the code by pasting following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.wordpress.omanandj;
 
import com.wordpress.omanandj.config.AppConfiguration;
import com.wordpress.omanandj.repository.ICacheRepository;
import com.wordpress.omanandj.repository.impl.SimpleCacheRepository;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
 
public class App
{
    public static void main( String[] args )
    {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfiguration.class);
 
       ICacheRepository<String,String> simpleCacheRepository =
                    (SimpleCacheRepository)context.getBean("simpleCacheRepository");
 
        //Delete the key if exists
        simpleCacheRepository.delete("blog.author.name");
        //Test simple put first.
        simpleCacheRepository.put("blog.author.name", "Omanand Jha Vatsa");
 
        //Let's retrieve the key to make sure that value is same.
        String name = simpleCacheRepository.get("blog.author.name");
        //Print the name
        System.out.println("Get Demo :: " + name);
 
        //Now to a multi put in the redis data store
 
        Map<String,String> fruits = new HashMap<String, String>();
 
        fruits.put("apple", "red");
        fruits.put("mango", "yellow");
        fruits.put("papaya", "green");
 
        simpleCacheRepository.multiPut(fruits);
 
        //Now get back fruit and its colors
 
        List<String> fruitsFromDB = simpleCacheRepository.multiGet(Arrays.asList("apple","mango","papaya","blog.author.name"));
 
        System.out.println("MultiGet demo :: " + fruitsFromDB);
    }
}

You should verify your results in the redis datastore using redis-cli.

Using Redis List Data Type
For operations on the list data type Redistemplate gives opsForList() method. Redis list support pushing an element to the left and right, popping an element from left and right, getting elements of the list, trimming the list etc. Based on these requirements create a demo interface for showcasing each of these functionality:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.wordpress.omanandj.repository;
 
import java.util.Collection;
 
 
public interface IRedisListDemo<K,V> {
 
    void push(K key, V value, boolean right);
 
    void multiAdd(K key,Collection<V> values, boolean right);
 
    Collection<V> get(K key);
 
    V pop(K key, boolean right);
 
    void delete(K key);
 
    void trim(K key, int start, int end);
 
    Long size(K key);
 
}

The IRedisListDemo can be implemented as :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.wordpress.omanandj.repository.impl;
 
import com.wordpress.omanandj.repository.IRedisListDemo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
 
import java.util.Collection;
 
 
@Component("redisListTypeRepository")
public class RedisListTypeRepository implements IRedisListDemo<String, String>{
 
    @Autowired
    @Qualifier("stringRedisTemplate")
    private RedisTemplate<String,String> redisTemplate;
 
    @Override
    public void push(String key, String value, boolean right) {
        if(right) {
             redisTemplate.opsForList().rightPush(key,value);
        }
        else {
            redisTemplate.opsForList().leftPush(key,value);
        }
    }
 
    @Override
    public void multiAdd(String key, Collection<String> values, boolean right) {
     //  redisTemplate.multi();
        //Have to find better implementation for this
        for(String value : values) {
            push(key,value,right);
        }
       // redisTemplate.exec();
    }
 
    @Override
    public Collection<String> get(String key) {
        return redisTemplate.opsForList().range(key,0,-1);
    }
 
    @Override
    public String pop(String key, boolean right) {
       String value;
       if(right) {
           value = redisTemplate.opsForList().rightPop(key);
       }
       else {
           value = redisTemplate.opsForList().leftPop(key);
       }
       return  value;
    }
 
    @Override
    public void delete(String key) {
        redisTemplate.delete(key);
    }
 
    @Override
    public void trim(String key, int start, int end) {
        redisTemplate.opsForList().trim(key,start,end);
    }
 
    @Override
    public Long size(String key) {
           return redisTemplate.opsForList().size(key);
    }
}

Now let us test our RedisListTypeRepository by adding following code snippet into the App.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
IRedisListDemo<String,String> redisListDemo =
        (RedisListTypeRepository)context.getBean("redisListTypeRepository");
 
//Delete the key of exists
redisListDemo.delete("list:demo");
 
//Push the element to the right
redisListDemo.push("list:demo", "10",true);
redisListDemo.push("list:demo", "40",true);
//Push the element to the left
redisListDemo.push("list:demo", "30",false);
redisListDemo.push("list:demo", "20",false);
//Try multi add
Collection<String> valuesToTestMultiPush = Arrays.asList(new String[]{"40","50","60"});
//Add same collection to left and right push
redisListDemo.multiAdd("list:demo",valuesToTestMultiPush,true);
 
 
//Fetch the entire list for the list:demo
Collection<String> values = redisListDemo.get("list:demo");
 
System.out.println("list:demo :: " + values);
 
//Try right pop
String value = redisListDemo.pop("list:demo",true);
System.out.println("Right Pop Value :: " + value);
 
//Try left pop
value = redisListDemo.pop("list:demo",false);
System.out.println("Left Pop Value :: " + value);
 
 
//Now trim the list
redisListDemo.trim("list:demo", 0,2);
//fetch the values
values = redisListDemo.get("list:demo");
System.out.println("After trimming list:demo :: " + values);
 
//Get the size of the list
Long size = redisListDemo.size("list:demo");
System.out.println("size of list:demo :: " + size);

As you may have noticed implementation of the multiAdd is not optimal and I still need to find the same with RedisTemplate and Jedis client. However, redis-cli does support it.

$ LPUSH marks 10 20 30 40
(integer) 4
$ lrange marks 0 3
1) "40"
2) "30"
3) "20"
4) "10"

Using Redis Set Data Type
As described in first post redis set data type is an unordered collection of unique elements. Similar to list following classes demonstrate the adding an element to the set, getting members of the set, verify if a value is the member of the set.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.wordpress.omanandj.repository;
 
import java.util.Set;
 
public interface IRedisSetDemo<K,V> {
 
    void add(K key, V value);
 
    boolean isMemberOf(K key, V value);
 
    Set<V> members(K key);
 
    V pop(K key);
 
    void delete(K key);
 
}

The sample implementation of the IRedisSetDemo is defined in RedisSetTypeRepository as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.wordpress.omanandj.repository.impl;
 
import com.wordpress.omanandj.repository.IRedisSetDemo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
 
import java.util.Set;
 
 
@Component("redisSetTypeRepository")
public class RedisSetTypeRepository implements IRedisSetDemo<String,String> {
 
    @Autowired
    @Qualifier("stringRedisTemplate")
    private RedisTemplate<String,String> redisTemplate;
 
    @Override
    public void add(String key, String value) {
         redisTemplate.opsForSet().add(key,value);
    }
 
    @Override
    public boolean isMemberOf(String key, String value) {
        return redisTemplate.opsForSet().isMember(key,value);
    }
 
    @Override
    public Set<String> members(String key) {
        return redisTemplate.opsForSet().members(key);
    }
 
    @Override
    public String pop(String key) {
        return redisTemplate.opsForSet().pop(key);
    }
 
    @Override
    public void delete(String key) {
        redisTemplate.delete(key);
    }
}

Add following skeleton of the code to test RedisSetTypeRepository:

1
2
3
4
5
6
7
8
9
10
11
IRedisSetDemo<String,String> redisSetDemo =
         (RedisSetTypeRepository)context.getBean("redisSetTypeRepository");
 
 redisSetDemo.delete("set:demo");
 Set<String> names = new HashSet<String>();
 
 redisSetDemo.add("set:demo", "Omanand");
 redisSetDemo.add("set:demo", "Jha");
 redisSetDemo.add("set:demo", "Vatsa");
 names = redisSetDemo.members("set:demo");
 System.out.println("set:demo :: " +  names);

Using Redis Hashes Data Type
Redis Hashes are maps between string fields and string values. Redis Hashes are most suitable to represent objects. As per redis documentation hash with nearly 100 fields are stpred in a way that takes very little space, so that you can store millions of objects in a small Redis instance.
For demonstrating the hash data structure for redis, let’s create a model object User with fields- id, name, age, organization and password as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.wordpress.omanandj.model;
 
import java.io.Serializable;
 
public class User implements Serializable {
    public static final String OBJECT_KEY = &quot;USER&quot;;
 
    private String  id;
    private String  name;
    private String  password;
    private Integer age;
    private String  organization;
 
    public User(String id, String name, String password, Integer age, String organization) {
        this.id = id;
        this.name = name;
        this.password = password;
        this.age = age;
        this.organization = organization;
    }
 
    public String getId() {
        return id;
    }
 
    public void setId(String id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getPassword() {
        return password;
    }
 
    public void setPassword(String password) {
        this.password = password;
    }
 
    public Integer getAge() {
        return age;
    }
 
    public void setAge(Integer age) {
        this.age = age;
    }
 
    public String getOrganization() {
        return organization;
    }
 
    public void setOrganization(String organization) {
        this.organization = organization;
    }
}

Now create an Interface IHashRepository for describing of the redis commands for saving and retrieving objects from the redis datastore as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.wordpress.omanandj.repository;
 
import java.util.Collection;
import java.util.List;
 
 
public interface IHashRepository<V> {
    void put(V obj);
 
    void multiPut(Collection<V> keys);
 
    V get(V key);
 
    List<V> multiGet(Collection<V> keys);
 
    void delete(V key);
 
    List<V> getObjects();
 
    void delete();
}

Now let us implement the interface IHashRepository for user model object as UserRepository :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.wordpress.omanandj.repository.impl;
 
import com.wordpress.omanandj.model.User;
import com.wordpress.omanandj.repository.IHashRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
 
import java.util.*;
 
 
@Component("userRepository")
public class UserRepository implements IHashRepository<User> {
    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate<String,User> redisTemplate;
 
    @Override
    public void put(User user) {
        redisTemplate.opsForHash().put(User.OBJECT_KEY, user.getId(), user);
    }
 
    @Override
    public void multiPut(Collection<User> keys) {
        Map<Long,User> keyValues = new HashMap<Long, User>(keys.size());
        for(User user : keys)
        {
            keyValues.put(user.getId(), user);
        }
        redisTemplate.opsForHash().putAll(User.OBJECT_KEY, keyValues);
    }
 
    @Override
    public User get(User key) {
        return (User) redisTemplate.opsForHash().get(User.OBJECT_KEY,
                key.getId());
    }
 
    @Override
    public List<User> multiGet(Collection<User> users) {
        List<Object> keys = new ArrayList<Object>(users.size());
        for(User user : users)
        {
            keys.add(user.getId());
        }
        return (List<User>)(List<?>)redisTemplate.opsForHash().multiGet(User.OBJECT_KEY, keys);
    }
 
    @Override
    public void delete(User key) {
        redisTemplate.opsForHash().delete(User.OBJECT_KEY, key.getId());
    }
 
    @Override
    public List<User> getObjects() {
        List<User> users = new ArrayList<User>();
        for (Object user : redisTemplate.opsForHash().values(User.OBJECT_KEY) ){
            users.add((User) user);
        }
        return users;
    }
 
    @Override
    public void delete() {
        redisTemplate.delete(User.OBJECT_KEY);
    }
}

You may have noticed the difference of bean wiring for redisTemplate in UserRepository. Since the UserRepositry not uses String as values, we have to define a new bean for redisTemplate. Let us define a more generic bean in your AppConfiguration class as:

1
2
3
4
5
6
7
@Bean(name = "redisTemplate")
  public <String,V> RedisTemplate<String,V> getRedisTemplate(){
      RedisTemplate<String,V> redisTemplate =  new RedisTemplate<String, V>();
      redisTemplate.setConnectionFactory(getConnectionFactory());
      redisTemplate.setKeySerializer(new StringRedisSerializer());
      return redisTemplate;
  }

Now let us test the UserRepository by adding following code skelton to your App.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
IHashRepository<User> userRepository =
              (UserRepository)context.getBean("userRepository");
 
      userRepository.delete();
      //Try simple put of user in data store
      User user1 = new User(1000L,"user1","*****",25,"omanandj" );
      User user2 = new User(1001L,"user2","*****",25,"omanandj" );
 
      //Add user1 and user 2 to redis data store
      userRepository.put(user1);
      userRepository.put(user2);
 
      //Fetch current users
      List<User> users = userRepository.getObjects();
      System.out.println("Users : " + users);
 
      //Fetch user1
      User user1FromDb = userRepository.get(user1);
      System.out.println("User1 : " + user1FromDb);
 
 
      //delete a user
      userRepository.delete(user2);
      users = userRepository.getObjects();
      System.out.println("Users After deleting user2 : " + users);
 
      //Try multi put and multi get
 
      users = new ArrayList<User>();
      for(int i = 0; i < 20; ++i) {
          User user = new User(1100L + i, "user" + i, "*****",25+i, "omanandj");
          users.add(user);
 
      }
      userRepository.multiPut(users);
      List<User> fetchedUsers = userRepository.multiGet(users);
      System.out.println("Multi Get Users : " + fetchedUsers);

Thats it for this second part of this series. I hope this will help you more deeper into the thought of redis and spring-data-redis for different data structure.
We will continue developing our spring-data-redis-demo app in the third part of this series to understand the Publish subscribe messaging by Redis PubSub using spring. May be we will have a 2 minute introduction for the replication setup for redis.

1 thought on “Redis using Spring Data – Part 2/3

Leave a Reply

Your email address will not be published.