Spring Webflux and @Cacheable Annotation
Last Updated :
04 Mar, 2024
In Java Spring, the application’s caching capability is enabled by the CachingConfig class. It accomplishes this by utilizing the @EnableCaching to enable caching. The @Cacheable and reactive frameworks were not seamlessly integrated. The main problem is that the JSR-107 cache API blocks; there are no non-blocking cache implementations.
Implementation of Spring Webflux and @Cacheable Annotation
Below are the steps to implement Spring Webflux and @Cacheable Annotation.
Step 1: Add cache() operator to Mono
The Reactor 3 does not seamlessly integrate @cacheable. We may get around that by including the cache() operator in the returned mono.
@repository
interface taskrepository : reactivemongorepository<task, string>
@service
class taskservice(val taskrepository: taskrepository) {
@cacheable("tasks")
fun get(id: string): mono<task> = taskrepository.findbyid(id).cache()
}
Step 2: Create SpringBootTest
Using a reactive MongoDB driver, build a basic Spring WebFlux application. We will utilize Testcontainers in instead of MongoDB running as a separate process. Using @SpringBootTest as an annotation, our test class will have the following:
final static MongoDBContainer mongoDBContainer =
new MongoDBContainer(DockerImageName.parse("mongo:4.1.8"));
@DynamicPropertySource
static void mongoDbProperties(DynamicPropertyRegistry registry) {
mongoDBContainer.start();
registry.add("spring.data.mongodb.uri", mongoDBContainer::getReplicaSetUrl);
}
Step 3: Create ItemService class
We will develop an ItemService class for this test, complete with save and getItem methods:
Java
import org.springframework.cache.annotation.Cacheable;
import reactor.core.publisher.Mono;
@Service
public class ItemService {
private final ItemRepository repository;
public ItemService(ItemRepository repository) {
this .repository = repository;
}
@Cacheable ( "items" )
public Mono<Item> getItem(String id) {
return repository.findById(id);
}
public Mono<Item> save(Item item) {
return repository.save(item);
}
}
|
Step 4: Configure the cache and repository loggers
To keep track of our test’s progress, we’ve configured logs for the cache and repository in application.properties:
logging.level.org.springframework.data.mongodb.core.ReactiveMongoTemplate=DEBUG
logging.level.org.springframework.cache=TRACE
Step 5: Initial Test
Once everything is set up, we can run the test and evaluate the outcome.
Java
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class ItemServiceTest {
private ItemService itemService = new ItemService( );
@Test
public void givenItem_whenGetItemIsCalled_thenMonoIsCached() {
Mono<Item> glass = itemService.save( new Item( "glass" , 1.00 ));
String id = glass.block().get_id();
Mono<Item> mono = itemService.getItem(id);
Item item = mono.block();
assertThat(item).isNotNull();
assertThat(item.getName()).isEqualTo( "glass" );
assertThat(item.getPrice()).isEqualTo( 1.00 );
Mono<Item> mono2 = itemService.getItem(id);
Item item2 = mono2.block();
assertThat(item2).isNotNull();
assertThat(item2.getName()).isEqualTo( "glass" );
assertThat(item2.getPrice()).isEqualTo( 1.00 );
}
}
|
Step 6: Caching the Result of Mono/Flux
Make a reference to our service method’s real outcome:
@Cacheable("items")
public Mono<Item> getItem_withCache(String id) {
// The @Cacheable annotation indicates that the results of this method should be cached with the key "items".
// The repository.findById(id) is the actual data retrieval.
// The .cache() method is used to cache the result of the Mono<Item> to improve performance.
return repository.findById(id).cache();
}
- As a solution, we can utilize the built-in caching mechanism of Flux and Mono.
- As we previously mentioned, @Cacheable has an internal cache and caches the wrapper object.
Step 7: Configure the Caffeine Cache
Since the Reactor 3 addon will no longer be supported in the upcoming release, we will only demonstrate the cache implementation using Caffeine. In this instance, we will set up the Caffeine cache as follows:
Java
public ItemService(ItemRepository repository) {
this .repository = repository;
this .cache = Caffeine.newBuilder().build( this ::getItem_withAddons);
}
|
Step 8: Add new service method
The Caffeine cache is initialized with the bare minimum of setup in the ItemService constructor, and the new service method makes use of that cache:
Java
@Cacheable ( "items" )
public Mono<Item> getItem_withCaffeine(String id) {
return cache.asMap().computeIfAbsent(id, k -> repository.findById(id).cast(Item. class ));
}
|
By following the above steps, we can implement Spring Webflux and @Cacheable Annotation.
Share your thoughts in the comments
Please Login to comment...