{{announcement.body}}
{{announcement.title}}

Introduction to Spring Data Redis

DZone 's Guide to

Introduction to Spring Data Redis

Want to make Redis easier to use in your Spring app?

· Database Zone ·
Free Resource


Redis is an in-memory data structure store with optional durability, used as a database, cache, and message broker. Currently, it is the most most popular tool in the key/value stores category: https://db-engines.com/en/ranking/key-value+store. The easiest way to integrate your application with Redis is through Spring Data Redis. You can use Spring  RedisTemplate directly for that, or you might as well use Spring Data Redis repositories. There are some limitations when you integrate with Redis via Spring Data Redis repositories. They require at least Redis Server version 2.8.0 and do not work with transactions. Therefore, you need to disable transaction support for RedisTemplate, which is leveraged by Redis repositories.

Redis is usually used for caching data stored in a relational database. In the current sample, it will be treated as a primary database — just for simplification.

Spring Data repositories do not require any deeper knowledge about Redis from a developer. You just need to annotate your domain class properly. As usual, we will examine main features of Spring Data Redis basing on the sample application. Supposing we have the system, which consists of three domain objects: Customer, Account, and Transaction, here's the picture that illustrates relationships between elements of that system. Transaction is always related with two accounts: sender ( fromAccountId) and receiver ( toAccountId). Each customer may have many accounts.

Although the picture visible above shows three independent domain models, customer and account is stored in the same, single structure. All customer's accounts are stored as a list inside customer object. Before proceeding to the sample application implementation details, let's begin by starting the Redis database.

1. Running Redis on Docker

We will run a Redis standalone instance locally using its Docker container. You can start it in in-memory mode or with the persistence store. Here's the command that runs a single, in-memory instance of Redis on a Docker container. It is exposed outside on default 6379 port.

$ docker run -d --name redis -p 6379:6379 redis


2. Enabling Redis Repositories and Configuring Connection

I'm using Docker Toolbox, so each container is available for me under the address 192.168.99.100. Here's the only one property that I need to override inside configuration settings ( application.yml).

spring:
  application:
    name: sample-spring-redis
  redis:
    host: 192.168.99.100


To enable Redis repositories for a Spring Boot application, we just need to include the single starter <code>spring-boot-starter-data-redis</code>.

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>


We may choose between two supported connectors: Lettuce and Jedis. For Jedis, I had to include one additional client's library to dependencies, so I decided to use simpler option — Lettuce, which does not require any additional libraries to work properly. To enable Spring Data Redis repositories, we also need to annotate the main or the configuration class with @EnableRedisRepositories and declare theRedisTemplate bean. Although we do not use RedisTemplate directly, we still need to declare it while it is used by CRUD repositories for integration with Redis.

@Configuration
@EnableRedisRepositories
public class SampleSpringRedisConfiguration {

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory();
    }

    @Bean
    public RedisTemplate<?, ?> redisTemplate() {
        RedisTemplate<byte[], byte[]> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory());
        return template;
    }

}


3. Implementing Domain Entities

Each domain entity, at least, has to be annotated with @RedisHash and contain property annotated with @Id. Those two items are responsible for creating the actual key used to persist the hash. Besides identifier properties annotated with @Id, you may also use secondary indices. The good news about it is that it can be not only with dependent single objects but also on lists and maps. Here's the definition of Customer entity. It is available on Redis under customer key. It contains a list of Account entities.

@RedisHash("customer")
public class Customer {

    @Id private Long id;
    @Indexed private String externalId;
    private String name;
    private List<Account> accounts = new ArrayList<>();

    public Customer(Long id, String externalId, String name) {
        this.id = id;
        this.externalId = externalId;
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getExternalId() {
        return externalId;
    }

    public void setExternalId(String externalId) {
        this.externalId = externalId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }

    public void addAccount(Account account) {
        this.accounts.add(account);
    }

}


Account does not have its own hash. It is contained by Customer has as list of objects. The property id is indexed on Redis in order to speed-up the search based on the property.

public class Account {

    @Indexed private Long id;
    private String number;
    private int balance;

    public Account(Long id, String number, int balance) {
        this.id = id;
        this.number = number;
        this.balance = balance;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

}


Finally, let's take a look at the Transaction entity implementation. It uses only account ids, not the whole objects.

@RedisHash("transaction")
public class Transaction {

    @Id
    private Long id;
    private int amount;
    private Date date;
    @Indexed
    private Long fromAccountId;
    @Indexed
    private Long toAccountId;

    public Transaction(Long id, int amount, Date date, Long fromAccountId, Long toAccountId) {
        this.id = id;
        this.amount = amount;
        this.date = date;
        this.fromAccountId = fromAccountId;
        this.toAccountId = toAccountId;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public int getAmount() {
        return amount;
    }

    public void setAmount(int amount) {
        this.amount = amount;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public Long getFromAccountId() {
        return fromAccountId;
    }

    public void setFromAccountId(Long fromAccountId) {
        this.fromAccountId = fromAccountId;
    }

    public Long getToAccountId() {
        return toAccountId;
    }

    public void setToAccountId(Long toAccountId) {
        this.toAccountId = toAccountId;
    }

}


4. Implementing Repositories

The implementation of repositories is the most pleasant part of our exercise. As usual, with Spring Data projects, the most common methods like save, delete, or findById are already implemented. So, we only have to create our custom find methods if needed. Since the usage and implementation of thefindByExternalId method is rather obvious, the method findByAccountsId may not be. Let's move back to a model definition to clarify usage of that method. Transaction contains only account ids; it does not have direct relationship with Customer. What if we need to learn the details about customers besides just a given transaction? We can find a customer by one of its accounts from the list.

public interface CustomerRepository extends CrudRepository {

    Customer findByExternalId(String externalId);
    List findByAccountsId(Long id);

}


Here's the implementation of the repository for the Transaction entity.

public interface TransactionRepository extends CrudRepository {

    List findByFromAccountId(Long fromAccountId);
    List findByToAccountId(Long toAccountId);

}


5. Building Repository Tests

We can easily test Redis repositories functionality using Spring Boot Test project with @DataRedisTest. This test assumes you have a running instance of the Redis server on the already-configured address 192.168.99.100.

@RunWith(SpringRunner.class)
@DataRedisTest
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class RedisCustomerRepositoryTest {

    @Autowired
    CustomerRepository repository;

    @Test
    public void testAdd() {
        Customer customer = new Customer(1L, "80010121098", "John Smith");
        customer.addAccount(new Account(1L, "1234567890", 2000));
        customer.addAccount(new Account(2L, "1234567891", 4000));
        customer.addAccount(new Account(3L, "1234567892", 6000));
        customer = repository.save(customer);
        Assert.assertNotNull(customer);
    }

    @Test
    public void testFindByAccounts() {
        List<Customer> customers = repository.findByAccountsId(3L);
        Assert.assertEquals(1, customers.size());
        Customer customer = customers.get(0);
        Assert.assertNotNull(customer);
        Assert.assertEquals(1, customer.getId().longValue());
    }

    @Test
    public void testFindByExternal() {
        Customer customer = repository.findByExternalId("80010121098");
        Assert.assertNotNull(customer);
        Assert.assertEquals(1, customer.getId().longValue());
    }
}


6. More Advanced Testing With Testcontainers

You may provide some advanced integration tests using Redis since your Docker container started during the test by the Testcontainer library. I have already published some articles about the Testcontainers framework. If you would like read more details about it, please refer to my previous articles: Microservices Integration Tests with Hoverfly and Testcontainers and Testing Spring Boot Integration with Vault and Postgres using Testcontainers Framework.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@RunWith(SpringRunner.class)
public class CustomerIntegrationTests {

    @Autowired
    TestRestTemplate template;

    @ClassRule
    public static GenericContainer redis = new GenericContainer("redis:5.0.3").withExposedPorts(6379);

    @Before
    public void init() {
        int port = redis.getFirstMappedPort();
        System.setProperty("spring.redis.host", String.valueOf(port));
    }

    @Test
    public void testAddAndFind() {
        Customer customer = new Customer(1L, "123456", "John Smith");
        customer.addAccount(new Account(1L, "1234567890", 2000));
        customer.addAccount(new Account(2L, "1234567891", 4000));
        customer = template.postForObject("/customers", customer, Customer.class);
        Assert.assertNotNull(customer);
        customer = template.getForObject("/customers/{id}", Customer.class, 1L);
        Assert.assertNotNull(customer);
        Assert.assertEquals("123456", customer.getExternalId());
        Assert.assertEquals(2, customer.getAccounts().size());
    }

}


7. Viewing Data

Now, let's analyze the data stored in Redis after our JUnit tests. We may use one of GUI tool for that. I decided to install RDBTools available on site https://rdbtools.com. You can easily browse data stored on Redis using this tool. Here's the result for customer entity with id=1 after running the JUnit test.

Here's the similar result for transaction entity with id=1.

Source Code

The sample application source code is available on GitHub in the repository sample-spring-redis.

Topics:
java ,spring ,spring data ,redis ,code ,tutorial ,database

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}