Sometimes you need to use Spring Cache with your own data source, but Spring Boot Cache does not support it fortunately, Spring embraces the Open-Closed Principle, so it's simple to expand it to fit your unique use cases.
Let's look at how to expand the Spring Boot cache to support a new data source.
How to expand the Spring Boot Cache capability by supporting MongoDB or DynamoDB
You must register your custom CacheManger
bean and implement the Cache
interface in order to accomplish that. Let's see how it looks.
Implement Cache
interface
A Cache
interface is responsible for defining common cache actions such as saving and retrieving cached data from a database.
It is necessary to implement the TTL mechanism, perhaps at the database level. For instance, TTL index in MonogoDB or TTL in DynamoDB
package dev.zakaria.springcache.config;
import dev.zakaria.springcache.repository.SpringCacheRepository;
import lombok.AllArgsConstructor;
import org.springframework.cache.Cache;
import java.util.concurrent.Callable;
@AllArgsConstructor
public class MongoCache implements Cache {
private final String cacheName;
private final SpringCacheRepository springCacheRepository;
@Override
public String getName() {
return cacheName;
}
@Override
public Object getNativeCache() {
throw new UnsupportedOperationException();
}
@Override
public ValueWrapper get(Object key) {
// TODO: implement your data access logic here
return null;
}
@Override
public <T> T get(Object key, Class<T> type) {
// TODO: implement your data access logic here
return null;
}
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
// TODO: implement your data access logic here
return null;
}
@Override
public void put(Object key, Object value) {
// TODO: implement your data access logic here
}
@Override
public void evict(Object key) {
// TODO: implement your data access logic here
}
@Override
public void clear() {
// TODO: implement your data access logic here
}
}
Register your customized CacheManager
bean
The AbstractCacheManager
class is responsible for instantiating cache objects and registering them in Spring IoC.
By implementing AbstractCacheManager
and annotating it with @Component
, you notify Spring which class to look at when doing caching operations.
package dev.zakaria.springcache.config;
import dev.zakaria.springcache.repository.SpringCacheRepository;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache;
import org.springframework.cache.support.AbstractCacheManager;
import org.springframework.stereotype.Component;
import java.util.Collection;
@Component
public class MongoCacheManager extends AbstractCacheManager {
private final Collection<String> cacheNames;
private final SpringCacheRepository springCacheRepository;
public MongoCacheManager(@Value("${spring.cache.cache-names}") Collection<String> cacheNames, SpringCacheRepository springCacheRepository) {
this.cacheNames = cacheNames;
this.springCacheRepository = springCacheRepository;
}
@Override
protected Collection<? extends Cache> loadCaches() {
return cacheNames.stream()
.map(it -> new MongoCache(it, springCacheRepository))
.toList();
}
}
Use @Cacheable
You can now utilize usual spring cache annotations such as @Cacheable
. But first, we need to put the cache name in application.properties
spring.cache.cache-names=demo,demo2
Ensure that Spring Boot automatically configures for caching by adding the
@EnableCaching
annotation to any of the configuration classes or Application main class.
package dev.zakaria.springcache.controller;
import lombok.extern.log4j.Log4j2;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Log4j2
public class DemoController {
@GetMapping("/demo/{userId}")
@Cacheable("demo")
public PersonalizeResponse demo(@PathVariable String userId) {
return fetchPersonalizeData(userId);
}
private PersonalizeResponse fetchPersonalizeData(String userId) {
log.info("calling third-party API for user id {}", userId);
return new PersonalizeResponse("Obtain personalized information from a third-party API. Since each API call we make costs money, what if we saved this response for later requests?", userId);
}
public record PersonalizeResponse(String result, String userId) {}
}
While use Webflux Adding
.cache()
to yourFlux
orMono
is necessary if you were using the Spring Framework prior version 6.1.
Modify your Cache
implementation to use Spring Framework 6.1 and Webflux
Prior to Spring Framework 6.1, fetch logic had to be implemented in a blocked approach.
However, you can use reactive or non-block style when you modify your cache implementation to fit Spring Framework 6.1.
All you have to do is override the next retrieve() methods.
package dev.zakaria.springcache.config;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.Cache;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
@AllArgsConstructor
public class MongoCache implements Cache {
// ... methods
@Override
public CompletableFuture<?> retrieve(Object key) {
// TODO: Implement reactive data access logic here
return Cache.super.retrieve(key);
}
@Override
public <T> CompletableFuture<T> retrieve(Object key, Supplier<CompletableFuture<T>> valueLoader) {
// TODO: Implement reactive data access logic here
return Cache.super.retrieve(key, valueLoader);
}
}
That's it 🎉
Top comments (0)