Open In App

Spring Webflux and @Cacheable Annotation

Last Updated : 04 Mar, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

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 {
  
    // Mock or set up your ItemService and necessary dependencies
    private ItemService itemService = new ItemService(/* inject dependencies here */);
  
    @Test
    public void givenItem_whenGetItemIsCalled_thenMonoIsCached() {
  
        // Save a new Item named "glass" with a price of 1.00
        Mono<Item> glass = itemService.save(new Item("glass", 1.00));
  
        // Obtain the ID of the saved item
        String id = glass.block().get_id();
  
        // Retrieve the saved item using the getItem method
        Mono<Item> mono = itemService.getItem(id);
        Item item = mono.block();
  
        // Assertions for the properties of the retrieved item
        assertThat(item).isNotNull();
        assertThat(item.getName()).isEqualTo("glass");
        assertThat(item.getPrice()).isEqualTo(1.00);
  
        // Call getItem again with the same ID to check caching
        Mono<Item> mono2 = itemService.getItem(id);
        Item item2 = mono2.block();
  
        // Assertions for the properties of the second retrieved item
        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) {
    // Initialize the repository field with the provided ItemRepository instance.
    this.repository = repository;
  
    // Initialize the cache field using Caffeine, building it with a method reference to getItem_withAddons.
    // This suggests that the cache is related to the getItem_withAddons method.
    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) {
    // The @Cacheable annotation indicates that the results of this method should be cached with the key "items".
      
    // The cache.asMap() retrieves the underlying map of the cache.
    // The computeIfAbsent method is used to get an item from the cache based on the provided ID.
    // If the item is not present in the cache, it is computed using the specified function.
    // In this case, it retrieves the item from the repository and casts it to Item.
    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.



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads