DEV Community

Cover image for Caching in Spring Boot with Redis
Matheus Bernardes Spilari
Matheus Bernardes Spilari

Posted on

Caching in Spring Boot with Redis

Caching is an essential technique to improve the performance of applications by reducing database load and response time. In this post, we will explore how to integrate Redis as a caching layer in a Spring Boot application, running Redis inside a Docker container.

Why Use Redis for Caching?

Redis (Remote Dictionary Server) is an in-memory key-value store known for its high performance and scalability. It supports various data structures, persistence, and high availability, making it a great choice for caching in Spring Boot applications.

Setting Up Redis with Docker Compose

Update our docker-compose.yaml file with:


redis:
    image: redis:latest
    ports:
      - "6379:6379"
    volumes:
      - example-redis-data:/var/lib/redis/data
volumes:
  example-redis-data:

Enter fullscreen mode Exit fullscreen mode

Adding Redis Cache to a Spring Boot Application

Step 1: Add Dependencies

In your pom.xml, add the necessary dependencies for Spring Boot caching and Redis:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
</dependencies>
Enter fullscreen mode Exit fullscreen mode

Step 2: Create a cache configuration with Redis

Annotate your class configuration with @EnableCaching:


@Configuration
@EnableCaching
public class RedisCacheConfig {

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(10))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()));

        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(cacheConfiguration)
                .build();
    }

    @Bean
    public SimpleKeyGenerator keyGenerator() {
        return new SimpleKeyGenerator();
    }

}
Enter fullscreen mode Exit fullscreen mode

Explanation of the parameters:

  • entryTtl(Duration.ofMinutes(10)) β†’ Defines that every entry of the cache expires after 10 minutes.
  • GenericJackson2JsonRedisSerializer() β†’ Serializes the objects in a JSON format, granting the compatibility and readability of the data.
  • SimpleKeyGenerator β†’ Use the key automatically generated by Spring to identify the objects on cache.

Step 3: Configure Redis Connection

Add the following properties to your application.properties or application.yml file:

spring.redis.host=localhost
spring.redis.port=6379
Enter fullscreen mode Exit fullscreen mode

Step 4: Implement Caching in a Service

Create a service class that caches data using the @Cacheable annotation:

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }


    @Cacheable(value = "users", key = "#id")
    public UserResponseDto findById(UUID id) {
        var user = userRepository.findById(id).orElseThrow();
        return new UserResponseDto(user.getId(), user.getName());
    }
}
Enter fullscreen mode Exit fullscreen mode

With caching enabled, the first request for a product ID will take time to process, but subsequent requests will return the cached value almost instantly.

Step 5: Test the Caching

  • Grab a id from a user you stored in the database.
  • With that id, you call the endpoint users/{id from user}.
  • In my tests using Bruno, the call to the database, without the cache is responding in 31ms Call without cache in 31ms
  • With the cache the response comes in 8ms. Call without cache in 8ms

Step 6: Remove Cache

If a user was deleted, we need to remove him from the cache to avoid outdated data.

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }


    @Cacheable(value = "users", key = "#id")
    public UserResponseDto findById(UUID id) {
        var user = userRepository.findById(id).orElseThrow();
        return new UserResponseDto(user.getId(), user.getName());
    }

    @Transactional
    @CacheEvict(value = "users", key = "#id")
    public void deleteById(UUID id) {
        userRepository.deleteById(id);
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 7: Refreshing Cache

If you want to guarantee that the cach will always be updated, even we a new user is created, you can use @CachePut.

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }


    @Cacheable(value = "users", key = "#id")
    public UserResponseDto findById(UUID id) {
        var user = userRepository.findById(id).orElseThrow();
        return new UserResponseDto(user.getId(), user.getName());
    }

    @Transactional
    @CacheEvict(value = "users", key = "#id")
    public void deleteById(UUID id) {
        userRepository.deleteById(id);
    }

    @Transactional
    @CachePut(value = "users", key = "#result.id")
    public UserResponseDto save(CreateUserDto user) {
        var newUser = new UserModel(user.name());

        var userCreated = userRepository.save(newUser);

        return new UserResponseDto(userCreated.getId(), userCreated.getName());
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Integrating Redis as a caching layer in Spring Boot significantly improves application performance by reducing database calls. Running Redis in a Docker container makes it easy to set up and maintain. By using Spring Boot’s caching abstraction, we can seamlessly cache frequently accessed data and enhance the user experience.


πŸ“ Reference

πŸ’» Project Repository

πŸ‘‹ Talk to me

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (1)

Collapse
 
unmeshch profile image
Unmesh Chougule β€’ β€’ Edited

@mspilari
Thanks Matheus for this crisp blog. I have few questions on this as follows

  1. Why there is @Transactional on delete method in service
  2. Here we are feeding the keys for cache then do we still need the bean for SimpleKeyGenerator?

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

πŸ‘‹ Kindness is contagious

Please leave a ❀️ or a friendly comment on this post if you found it helpful!

Okay