Spring Boot, H2 and Redis
In the previous blog I explained how to configure redis with spring boot and wrote simple get and set rest api that demonstrates how redis is actually works. Now let’s understand how cache is actually works in Spring Boot. We have been configured redis configuration with code in our previous blog, but actually Spring Boot is configuring everything for us. We just need to add some annotations to things done for us.
Go to https://start.spring.io/ and create spring boot project with dependencies: web, lombok, h2, redis and jpa.
Make sure that you read about my previoes blog about Spring boot and Redis that I explained how to configure Redis on your PC as it will be required during this project.
The general structure of our application looks like this:
Create all classes that I mentioned in the image or you can clone the project from my Github. Make sure you configured redis and h2 configuration in application.properties:
# Redis configuration
spring.data.redis.host=localhost
spring.data.redis.port=6379
# H2 configurations
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:cacheDB
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
First let’s test our service without cache and then we will add specific annotations for caching. I will wrote down only service class but if you want to know more about other code just go to my github and clone the repo for better understanding.
package io.shixseyidrin.realcache.service;
import io.shixseyidrin.realcache.model.User;
import io.shixseyidrin.realcache.repository.UserRepository;
import io.shixseyidrin.realcache.response.Result;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import static io.shixseyidrin.realcache.response.Result.success;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
@Transactional
public Result<?> saveUser(User user) {
return success(userRepository.save(user));
}
public Result<?> allUsers() {
return success(userRepository.findAll());
}
}
The code looks like this without cache annotations, but before applying cache, I want to write the general method that calculates how much time it takes to calculate specific method using AOP (aspect oriented programming). For doing it create PerformanceAspect class under aop folder and add the following code:
package io.shixseyidrin.realcache.aop;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Slf4j
@Aspect
@Component
public class PerformanceAspect {
@Pointcut("within(@org.springframework.stereotype.Service *)")
public void repositoryClassMethods() {}
@SneakyThrows
@Around("repositoryClassMethods()")
public Object measureMethodExecutionTime(ProceedingJoinPoint proceedingJoinPoint) {
long startTime = System.nanoTime();
Object retVal = proceedingJoinPoint.proceed();
long endTime = System.nanoTime();
String methodName = proceedingJoinPoint.getSignature().getName();
log.info("Execution of {} took {}ms",
methodName,
TimeUnit.NANOSECONDS
.toMillis(endTime - startTime)
);
return retVal;
}
}
Okay now let’s run our application and check how much time it takes when calling saveUser and allUsers endpoints:
Execution time:
Just send multiple requests to save endpoint to add multiple users to database and now retrieve all of them:
The execution time for retrieving all users is:
Now let’s apply caching annotations to check if really it impacted to performance. Add @Cacheable
annotation on the allUsers
method in the User Service and @CacheEvict
annotation to remove the cache when saving a user:
package io.shixseyidrin.realcache.service;
import io.shixseyidrin.realcache.model.User;
import io.shixseyidrin.realcache.repository.UserRepository;
import io.shixseyidrin.realcache.response.Result;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import static io.shixseyidrin.realcache.response.Result.success;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
@Transactional
@CacheEvict(value = "users", allEntries = true)
public Result<?> saveUser(User user) {
return success(userRepository.save(user));
}
@Cacheable(value = "users")
public Result<?> allUsers() {
return success(userRepository.findAll());
}
}
The @CacheEvict
annotation with the allEntries
parameter set to true
removes all entries from the cache named "users". This ensures that the cache will be updated with the latest data after saving a user. Re run application and check if really data saved on cache. After re-running the application all data will be lost because we are using h2, again call save endpoint multiple times to save multiple users to database and then call:
http://localhost:8080/all
For the first time we calling this endpoint, the data will be stored on the cache and for later calls data will be accessed from cache:
Calling first time it tooked 100ms:
2023-02-06T23:03:53.507+04:00 INFO 286975 --- [nio-8080-exec-9] i.s.realcache.aop.PerformanceAspect : Execution of allUsers took 100ms
And for the second call it tooked 19ms which is almost 6 times faster than from previous call:
2023-02-06T23:05:35.002+04:00 INFO 286975 --- [nio-8080-exec-5] i.s.realcache.aop.PerformanceAspect : Execution of allUsers took 19ms
I hope you liked how caching works and how it improves the performance of the application.
Follow me to get to know about my blog updates ;)