So from my understanding in bellow code, when string ID is already in cache as key, DTO value gets returned from cache, instead of calling externalService method:
#Cachable("elements")
public ElementsDTOResponse getById(String id) {
return externalService.getById(id);
}
But can i use cache, if externalService takes list of IDs instead? My external service takes DTO in form:
ElementsDTO { List < ElementDTO > elementDTOList; }
Where ElementDTO { String id }
So can i do it, so externalService.getByIds(elementsDTO) will only be called with list of IDs that are not already in cache? Also if all IDs in DTO are already in cache (as keys) then do not call externalService at all, instead get values from cache. Something like that
#Cachable("elements")
#CachePut(key = "#elementsDTO.elementDTOList.id")
public ElementsDTOResponse getById(ElementsDTO elementsDTO) {
return externalService.getAllByIds(id);
}
Is it possible to do that simply with Spring Cache?
Related
I am newbie at Caffeine and Spring boot. I am trying to find a solution to cache a list of objects. the DB transaction takes 3 seconds to load the list, so I am looking to cache the resultset using Caffeine. To cache each item individually, I am following the below approach, but it doesn't seem to work.
public List<Item> getAllItemsOnStartUp() {
allItemsList = repo.getAllItems();
for (Item item : allItemsList) {
staticItem = item;
getItem(item.getItmsId());
}
return allItemsList;
}
#CachePut(value = "allItems", key = "#itmsId")
public Item getItem(BigInteger itmsId) {
return item;
}
#Override
#Cacheable(value = "allItems")
public List<Item> getAllItems() {
allItemsList = repo.getAllItems();
return allItemsList;
}
#Override
#Transactional
#CachePut(value = "allItems", key="#a0.itmsId")
public Item insertItem(Item item) {
Item savedItem = rRepo.save(item);
return savedItem;
}
When the server starts up, getAllItemsOnStartUp() must run and populate cache. the app calls getAllItems() to retrieve the list, it is expected to use cache here but every time the app gets data from DB which takes 3 seconds.
I saw this post today but I will answer anyway. Maybe it can help others.
Spring-boot uses by default CaffeineCache to cache service calls and it's very simple to use it. You just have to annotate your service method using #Cacheable. The example below caches the user permissions in a cache named "sso-users-cache" (because I don't want to call the service to check user's permissions all the time), creating an entry using company name (the system is multi-company) and userId, unless the method returns an error (remember: if you don't have a clausule unless, you can cache even an error of your service).
#Cacheable(cacheNames = ["sso-users-cache"], key = "#company.concat('-sso-user-').concat(#userId)", unless = "#result instanceof T(com.github.michaelbull.result.Err)")
fun fetchActionsByOrganizationAndUser(company: String, userId: String): Result<Set<String>, String> {
val response = Unirest
.get(getUserPermitionsUrl(company = company, userId = userId))
.header("Content-Type", "application/json")
.asString()
.ifFailure {
logger.error("SSO Error: Get user permissions failed: context: $company : userId: $userId")
}
return if(response.isSuccess) {
Ok(serializeUtil.asCollection(response.body, String::class.java).toSet())
} else {
Err("SSO Error: Get user permissions failed: context: $company : userId: $userId\"")
}
}
The parameter cacheNames defines an entry in your cache while the key, will tell the name of the entry on cache (it's used because users have different permissions and they need different entries inside the cache.
The parameter unless tells the cache to not cache if my method returns an error (very important!)
About update the cache informations, it's not necessary. What you need is to invalidate the cache information in case user's permission changes. For exemple, the method below add a new permission for the user and I need to invalidate (clear) the cache I have for the specific user:
#CacheEvict(cacheNames = ["sso-user-actions-cache"], key = "#company.concat('-user-actions-').concat(#userId)")
fun addPermissionToUser(company: String, userId: String, permission: String) {
Unirest
.put(addUserPermitionsUrl(company = company, userId = userId, permission = permission))
.header("Content-Type", "application/json")
.asEmpty()
}
Again: it's important to use the property key because I want to clear only the entry for a specific user!!! Otherwise, you will clean the whole permission cache (for all users)
After the #CacheEvict, the next call to check user's permission, spring will realize that the user doesn't have an entry on cache and will call the service and cache the return. That's it.
I have crud methods that modify the data in the cache and database
I also have a method that returns all entities when I use it after changes in the cache and database, I get irrelevant data.
As I understand it, the point is in the method of returning all entities. It uses the default key, it is different from other methods.
What do I need to do so that I can return the actual data sheet?
#Service
#CacheConfig(cacheNames = "configuration")
class ServiceConfiguration{
#Cacheable //this method returns non actual data
public List<MySomeConfiguration> getAllProxyConfigurations() {
return repository.getAllConfigurations();
}
#Cacheable(key = "#root.target.getConfigurationById(#id).serverId")
public MySomeConfiguration getConfigurationById(Long id) {
...
return configuration;
}
#CachePut(key = "#configuration.serverId", condition = "#result.id != null")
public MySomeConfiguration addOrUpdateConfiguration(Configuration configuration) {
return configuration;
}
#Cacheable(key = "#serverId")
public MySomeConfiguration getConfigurationByServerId(String serverId) {...
return configuration;
}
#CacheEvict(key = "#root.target.getConfigurationById(#id).serverId")
public void deleteConfigurationById(Long id) {
...
}
}//end class
p.s. sorry for my english
By default redis cache manager uses StringRedisSerializer for Key serializer
Your class toString() is used to serializer the key of the object so don't give different keys for different methods like put, get, evict etc jus rely on your toString() to get the key or override by using Spring Cache Custom KeyGenerator
Refer
https://www.baeldung.com/spring-cache-custom-keygenerator
Let's imagine that I have a method which can updates one field for all entities.
public void makeAllCarsRed(){...}
I know that Spring offers 3 annotation to manage cache:
#Cacheable - tries to find value in cache. If cannot find - execute method and adds to cache;
#CachEvict - just remove objects from cache by criteria;
#CachPut - put new value to cache
I don't see a way to apply these annotations for my situation.
P.S.
I think that it is too expensive to invalidate all cach
Spring caches a whole entity, not a single field. So you cannot evict a field from the cache.
If you would want to evict the entities from the cache, you would also have aproblem, but that can be solved :
Outside your method it is not visible, which entites are updated, so you cannot use any of these annotations.
What you can do, is get the CacheManager injected into the class that owns the method makeAllCarsRed() and than update the cache manually for all entites that are updated :
in your config
#Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("cacheName");
}
class that has makeAllCarsRed() :
public class MakeAllCarsRedService{
#Autowired CacheManager cm;
...
public void makeAllCarsRed(){
Cache cache = cm.getCache("cacheName");
//remove from cache
cache.evict(key)
//or, if neddec add to cache
put(key, entity);
...
}
}
A cached method do not return a deep copy of the cache content so you can make side effects on it. Let getCars(boolean) be your cached method:
#Cacheable("cars")
public List<Car> getCars(boolean isAvailable) {
return ...;
}
Call your cached method with the right parameters and then corrupt the cache:
public void makeAllCarsRed() {
List<Car> cache = getCars(true);
for (Car carInCache : cache) {
carInCache.setColor("red");
}
}
The next time you will call getCars(boolean), it will return the updated cache.
I have a simple Spring application which has a method:
#Cacheable("getSession")
public Session getSession(String sessionId) { ...
which is backed by eh-cache and works well. Where I'm having problems is that I need to evict objects from this cache not based on sessionId (a GUID by which they are keyed in the cache) but instead by the database Id of the returned object:
public void evictSessionFromCache(long id) { ...
There is no way for me to know the sessionId at the point this object needs to be evicted.
Does anybody have any pointers on this? I've tried extending EhCacheCacheManager to return a wrapped Cache object with a mapping between db ID and sessionID but this feels like a clunky solution:
if(name.equals("getSession")) {
if(wrappedCache == null) {
wrappedCache = new WrappedCache(super.getCache(name));
}
return wrappedCache;
} else {
return super.getCache(name);
}
Are there any properties I can use out of the box to utilise this annotation and access objects by more than one id? Or should I forget the annotation and manipulate the cache / cacheManager directly?
Thanks!
I am using spring cache abstraction to cache objects in the service layer.
This is fine for simple get/put operations, as follows:
static private final String cacheName = "messages";
#CacheEvict(value=cacheName, key="#message.id")
public void deleteMessage(Message message) {
...
}
#Cacheable(value=cacheName, key="#id")
public Message findMessage(Long id) {
...
}
#CachePut(value=cacheName, key="#message.id")
public void saveMessage(Message message) {
...
}
#CachePut(value=cacheName, key="#message.id")
public Message updateMessage(Message message) {
...
}
However, what annotation would I use for the following method:
public long countAllMessages() {
...
}
Since all the objects will be in the cache, there should be a way to get the answer from the cache and not having to go to the repository layer.
Also, the cache is being applied on the following method:
#Cacheable(cacheName)
public List<Message> findAllMessages() {
...
}
I could make the count method call the find all method like this:
public long countAllMessages() {
return findAllMessages().size();
}
But this would not be efficient in the case where the cache is disabled, as the call would then load all records from the db instead of doing a SELECT COUNT(*)...
The Spring cache abstraction doesn't currently provide a direct way of accessing statistics about the underlying caches. For example, there is no direct way to get the size of all the caches, the keys of the cached objects, etc. It's a higher level abstraction than that. You can, however, get access to the underlying native cache classes via the Cache#getNativeCache() method. Grabbing an instance of Cache from the CacheManager class will give you access to this method. Assuming you know the underlying types of the Cache instances managed by the CacheManager, you can cast them to the appropriate types and gain access to the helper methods on each (assuming there are any).
For example, if your underlying cache store is EHCache, you can grab an instance of the Cache named "messages" by calling CacheManager#getCache("messages"). You'd then be able to check the type and cast the type to net.sf.ehcache.Ehcache, from which you can call the various statistics helper methods (eg. getStatistics()).
Here's an example of going through all the caches managed by CacheManager, checking if they're of type net.sf.ehcache.Ehcache, and subsequently getting their statistics.
public class EhCacheStatistics {
private CacheManager cacheManager;
#Inject
public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
public long getTotalEhCacheSize() {
long totalSize = 0l;
for (String cacheName : cacheManager.getCacheNames()) {
Cache cache = cacheManager.getCache(cacheName);
Object nativeCache = cache.getNativeCache();
if (nativeCache instanceof net.sf.ehcache.Ehcache) {
net.sf.ehcache.Ehcache ehCache = (net.sf.ehcache.Ehcache) nativeCache;
totalSize += ehCache.getStatistics().getObjectCount();
}
}
return totalSize;
}
}