Leveraging Redis Caching for Maximum Performance with Spring Boot and Java

Abhishek Ranjan
Javarevisited
Published in
4 min readApr 28, 2023

Caching is a popular technique to improve the performance of applications by reducing the load on resources such as databases or remote services. In this article, we will explore how to use Redis as a cache in a Spring Boot application to achieve maximum performance.

1. Introduction to Caching and Redis

Caching is a technique to store and reuse the results of expensive operations to improve performance and reduce the load on underlying systems. It involves saving the results of an operation in a temporary storage location, called a cache, and then retrieving the results from the cache instead of performing the operation again when the same input is provided.

Redis is an open-source, in-memory data structure store that can be used as a database, cache, and message broker. It is highly performant and supports a wide range of data structures such as strings, hashes, lists, sets, and more.

2. Setting up the Spring Boot Application

To get started, create a new Spring Boot application using the Spring Initializr. Add the following dependencies:

  • Web
  • JPA
  • Lombok
  • Redis

You can generate the project and import it into your favorite IDE.

3. Configuring Redis Cache

Before we can start using Redis as a cache in our application, we need to configure it. First, add the following properties to your application.properties file:

spring.redis.host=localhost
spring.redis.port=6379

Next, create a configuration class called RedisCacheConfig:

@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
@Bean
public JedisConnectionFactory jedisConnectionFactory() {
return new JedisConnectionFactory(new RedisStandaloneConfiguration(redisHost, redisPort));
}
@Bean
public RedisCacheManager cacheManager() {
return RedisCacheManager.builder(jedisConnectionFactory()).build();
}
}

This class enables caching support and configures the JedisConnectionFactory and RedisCacheManager beans.

4. Implementing Cacheable Services

Let’s create a simple service that will benefit from caching. In this example, we will implement a service to retrieve user information from a database.

First, create the User entity:

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String email;
}

Now, create a UserRepository interface:

public interface UserRepository extends JpaRepository<User, Long> {}

Finally, create a UserService class:

@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Cacheable(value = "users", key = "#id")
public User findById(Long id) {
return userRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("User not found"));
}
}

The @Cacheable annotation specifies that the result of the `findById method should be cached. The value attribute defines the cache name, and the key attribute defines the cache key.

5. Cache Eviction Strategies

To keep the cache up to date, we need to evict or remove entries when the underlying data changes. We can use the @CacheEvict annotation to achieve this.

Update the UserService class to add methods for creating and updating users:

@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Cacheable(value = "users", key = "#id")
public User findById(Long id) {
return userRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("User not found"));
}
@CacheEvict(value = "users", key = "#user.id")
public User save(User user) {
return userRepository.save(user);
}
@CacheEvict(value = "users", key = "#user.id")
public User update(User user) {
findById(user.getId()); // Ensure the user exists
return userRepository.save(user);
}
@CacheEvict(value = "users", allEntries = true)
public void deleteById(Long id) {
userRepository.deleteById(id);
}
}

The @CacheEvict annotation removes the specified cache entry after the method execution. In this case, we're evicting the cache when creating, updating, or deleting a user.

6. Testing the Performance Improvement

Now that we have implemented caching, let’s test the performance improvement. We will create a simple REST controller and measure the response times with and without caching.

Create a UserController class:

@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.findById(id));
}
}

You can use tools like Apache JMeter, Postman, or curl to measure the response times. Make sure to run multiple requests to see the caching effect.

7. Conclusion

In this article, we demonstrated how to use Redis as a cache with Spring Boot and Java. We covered the configuration, implementation of cacheable services, cache eviction strategies, and testing the performance improvement. By leveraging Redis caching, you can significantly improve the performance of your Spring Boot applications and reduce the load on your underlying systems.

Make sure to consider your caching strategy and cache eviction policies carefully, as improper handling can lead to stale data and unexpected results.

--

--