big data Docker event driven platform

Spring Boot Application with Azure Redis Cache

Caching is essential for any application. It allows quick retrieval of frequently accessed data leading to faster loading times, more relevant insights, and happier users. With Spring Boot, REDIS, and ASAE adding a cache has never been easier

This article will go over how to create a Spring boot application that integrates with Azure Cache for Redis, a service that provides caching support for your applications. We will walk you through setting up a vanilla Spring Boot application that can accept HTTP requests to save data.

We will set up a local Postgres database to store a few student records, configure our Spring application to cache records in our Azure Redis instance, and finally, we will also expose a couple of endpoints to showcase some of the caching tooling Spring Data Reactive Redis offers.

Setup

Azure Cache For Redis

This service provides an in-memory data store based on, you guessed it, Redis.
What you need:

Postgres Setup

Our first step will be to set up a Postgres database. If you have one available free to use that and create a new table “Students” and enter some stub data. We will be using docker to containerize a Postgres DB. You can find our code here and can get Postgres running in docker by doing the following:

  1. Clone the repo
  2. cd into the Postgres folder
  3. Run docker-compose up -d to start the container detached from your terminal.

This will boot up a Postgres docker container available on port 5432.

Create your Spring boot application.
There are many ways to create a SpringBoot Application, at Enfuse.io our favorite is using the trusty Spring Initializr. As you create your app we recommend using Java 17 and Gradle. Make sure to include the following dependencies:

  • Spring Web
  • Spring Data Reactive Redis.
  • Postgres
  • Data JPA

When complete it should look like this:

Create Redis Cache on Azure

  1. Go to https://portal.azure.com/ and click + Create a resource

2. Click Database in the categories panel to the left and then select Redis Cache

Create Redis Instance

3. On the new Page specify:
– Enter the DNS name for your cache
– Specify your Subscription, Resource Group, Location, and Cache type

4. Click on the “Review + Create” Tab and after validation is done, click on Create to finalize the cache setup

You should be able to see the new resource listed in your Dashboard now.

There is a lot going on here but we just need 2 things:

  1. Redis Console. We will use this to look at what is being stored in our cache when the app is running.
  2. Access Keys. These are used to authenticate our application when we connect to the Redis instance

Configuration

Configure your Spring Boot App to use Azure Cache

In your application.properties file, reference your Azure Redis properties and access keys to set up the following configuration:

spring.redis.host = <dns.uri.to.your.cache>
spring.redis.port = <cachePort>
spring.redis.password = <your-redis-access-key>

To let your app hook into our Postgres you will need to add these configuration properties to your application.properties.

spring.datasource.platform = postgres
spring.datasource.url = jdbc:postgresql://localhost:5432/postgres
spring.datasource.username = postgres
spring.datasource.password = superpswd
driver-class-name= org.postgresql.Driver
spring.jpa.show-sql = true
spring.jpa.properties.hibernate.format_sql = true

For our application, we are simulating a classroom app, so our main entity will be a student who has an id and a name. Here is the basic entity and JpaRepository 

@Entity
@Table(name = "students")
public class Student implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Long id;
    String firstName;
    public Student() {}
    public Student(Long id, String firstName) {
        this.id = id;
        this.firstName = firstName;
    }
    public Long getId() {
        return id;
    }
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public void setId(Long id) {
        this.id = id;
    }
}
@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {}

These settings will let your application know the location of your cache, so it can communicate back and forth during runtime.

Caching

Configuring your Redis Template

RedisTemplate is a useful abstraction that allows you to interact with Redis, it takes care of serialization and connection management in order to focus on the task at hand – caching our content.

For this application, we use StringRedisSerializer for our keys and GenericJackson2JsonRedisSerializer for our values. We use StringRedisSerializer since is common to have string values as keys and GenericJackson2JsonRedisSerializer will take care of our objects, serializing them to JSON/using dynamic typing.

We make use of the LettuceConnectionFactory to take care of our connection management which will create thread-safe connections every time we interact with Redis.

Finally, we use @EnableCaching annotation in order to use cache annotations throughout our app. Our configuration bean will end up looking like this:

@Configuration
@EnableCaching
public class RedisConfig{
 @Bean
 public RedisTemplate redisTemplate(LettuceConnectionFactory redisConnectionFactory){
   RedisTemplate<Object,Object> template = new RedisTemplate<>();
   template.setKeySerializer(new StringRedisSerializer());
   template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
   template.setConnectionFactory(redisConnectionFactory);
   return template;
   }
}

Caching

There are 2 primary annotations we use in this app, @Cacheable and @CacheEvict.

Cacheable will make sure to cache our method return values. We use the key parameter in the annotation to explicitly tell Spring to cache the record with the id parameter as the key. If a record is found in the cache, the method does not execute and the record is returned from the cache, otherwise, the method runs and caches the return value.
CacheEvict is used for methods where you update records, this ensures that our cache deletes the record corresponding with the key whenever we update a record in the underlying data store. Doing this will prevent us to keep stale data in our cache.

We create a service class that will be in charge of exposing methods to our controllers in order to read from our Postgres backend. It will use the 2 cache annotations we just talked about to keep track of records we have fetched in the past.

@Service
@CacheConfig(cacheNames="studentsCache")
public class StudentServiceImpl implements StudentService{
 @Autowired
 StudentRepository studentRepository;

 @Cacheable(cacheNames="students")
 @Override
 public List<Student> findAll() {
  return (List<Student>) studentRepository.findAll();
 }

 @Cacheable(cacheNames="student",key="#id",unless="#result==null")
 @Override
 public Student findById(Long id) {
  Student student = studentRepository.findById(id).orElse(null);
  return student;
  }

 @CacheEvict(cacheNames="student",key="#student.id")
 public Student updateStudent (Student student) {
  Student update = studentRepository.findById(student.getId()).orElse(null);
  update.setFirstName(student.getFirstName());
  Student updated = studentRepository.save(update);
  return updated;
 }
}

We used this abstraction to be able to swap repository implementation if we ever needed to. The important aspect to notice here is how we are using our cache annotations. Every time one of these methods is called, it will evaluate the cache before executing it. If there is a key match, we will not execute the method and return the value from our Redis store.

Controller

Finally, we expose a few endpoints by creating a controller that will use our service class methods to serve data back to a client app.

@RestController
public class AzureRedisController {
    @Autowired
    private StudentService studentService;
    @GetMapping("/students")
    public ResponseEntity<List<Student>> getAllStudents(){
        List<Student> studentsList = studentService.findAll();
        return ResponseEntity.ok(studentsList);
    }
    @GetMapping(value = "/students/{id}")
    public ResponseEntity<Student> getCustomerById(@PathVariable("id") Long id) {
        Student student = studentService.findById(id);
        return ResponseEntity.ok(student);
    }
    @PatchMapping(value="/students")
    public ResponseEntity updateStudent(@RequestBody Student student){
        Student updated = studentService.updateStudent(student);
        return ResponseEntity.ok(updated);
    }
}

Validation

Cache in Action

Start the application and Spring will boot up on port 8080. We have enabled SQL logs to appear in the console when we make a call to our database. We can determine our cache is working whenever these SQL logs don’t show when we make a second call to the same endpoint with the exact same parameters (if any).

Bring up Postman and issue the following requests

http://localhost:8080/students      #to get all student records
http://localhost:8080/students/1    #get an individual student

These REST calls will fetch all the student records and a student based on their ID respectively. The first call will make a database fetch while running it again and will get the cached record from the Azure Redis instance.


You can also verify our cache is working by using the Redis CLI provided by Azure. In the console type scan 0 in order to get all the keys stored for a particular value and get <key> to see the value associated with a particular key.
This is what our console spits out when we look for one of our student records.

Make a PATCH request to http://localhost:8080/students with a body payload '{ "id": 1, "firstName": "Frank"}'. the record will be removed from our after the call is complete and updated in the database.

Conclusion

Congratulations! You made it to the end of this blog, let’s recap what we’ve done.

  1. We ran a Postgres database using a docker container.
  2. Then, we created a new Redis store instance from our Azure account. We took care of the configuration needed to hook that up to our application.
  3. We created a service layer that would take care of our caching capabilities whenever a client application requests some data using Spring Redis annotations.
  4. Finally, we exposed a couple of endpoints in order to interact with our application from outside with Postman.

We have now the building blocks to have our application use Redis as a cache store. Feel free to play around and further customize our cache (setting up expiration policies, adding keyspaces, etc).


Happy Coding!

Further Reading

Author

Christian Herrera