I am using #Cacheable annotation to cache the results of my method. For performance reason I want to cache both null and non-null values returned from method.
But problem here is Spring is caching non-null values but not caching null for some reason.
Here is my code:
#Cacheable(
cacheManager = "promoCacheManager",
value = "promos:campaign",
key = "'promos:campaign:'.concat(#currencyId)"
)
public PromosDto getPromosByCurrency(Integer currencyId) {
...
I have tried every thing. Even I set
unless = "#result != null || #result == null"
But that didn't help as well.
Any pointer on this?
Check your cache manager settings.
For example: RedisCacheManager has an overloaded constructor where you can specify cacheNullValues; this is false by default - try setting it to true.
https://docs.spring.io/spring-data/redis/docs/current/api/org/springframework/data/redis/cache/RedisCacheManager.html#RedisCacheManager-org.springframework.data.redis.core.RedisOperations-java.util.Collection-boolean-
Also keep in mind:
NOTE When enabling cacheNullValues please make sure the RedisSerializer used by RedisOperations is capable of serializing NullValue.
Why not return Optional<PromosDto> to safely wrap a null. That should cache fine then, according to https://docs.spring.io/spring/docs/4.3.10.RELEASE/spring-framework-reference/htmlsingle/#cache-annotations-cacheable-condition
Related
I use Redis for caching and have the following service method:
#Cacheable(value = "productCache")
#Override
public List<ProductDTO> findAllByCategory(Category category) {
// code omitted
return productDTOList;
}
When I pass categoryA to this method, the result is cached and is kept during expiration period. If I pass categoryB to this method, it is retrieved from database and then kept in cache. Then if I pass categoryA again, it is retrieved from cache.
1. I am not sure if it is normal, because I just use value parameter ("productCache") of #Cacheable annotation and have no idea how it caches categoryA and categoryB results separately. Could you please explain how it works?
2. As mentioned on this page, there is also key parameter. But when using it as shown below, I think it does not make any sense and it works as above. Is that normal or am I missing something?
#Cacheable(value = "productCache", key="#category")
#Override
public List<ProductDTO> findAllByCategory(Category category) {
// code omitted
return productDTOList;
}
3. Should I get cache via Cache cache = cacheManager.getCache("productCache#" + category); ?
Caches are essentially key-value stores, where – in Spring –
the key is generated from the method parameter(s)
the value is the result of the method invocation
The default key generation algorithm works like this (taken right from Spring docs):
If no params are given, return SimpleKey.EMPTY.
If only one param is given, return that instance.
If more than one param is given, return a SimpleKey that contains all parameters.
This approach works well for most use-cases, as long as parameters have natural keys and implement valid hashCode() and equals() methods. If that is not the case, you need to change the strategy.
So in your example, the category object acts as key per default (for this to work, the Category class should have hashCode() and equals() implemented correctly). Writing key="#category" is hence redundant and has no effect. If your Category class would have an id property, you could write key="#category.id" however.
Should I get cache via Cache cache = cacheManager.getCache("productCache#" + category); ?
No, since there is no such cache. You only have one single cache named productCache.
I've seen a lot of articles where those parameters where specified, like that:
<cache alias="dishDTOs" uses-template="default">
<key-type>java.lang.Integer</key-type>
<value-type>com.topjava.graduation.restaurant.dto.DishResponseDTO</value-type>
</cache>
But what is the point of it? Everything seems to work even without them, moreover, if i specify these I have this exception
Invalid value type, expected : com.topjava.graduation.restaurant.dto.DishResponseDTO but was : java.util.ArrayList
Methods under test ( just call these 2 one by one ):
#Cacheable(value = "dishDTOs", key = "-2")
public List<DishResponseDTO> getAll() {
// code
}
#Cacheable(value = "dishDTOs", key = "#dishId")
public DishResponseDTO getOne(int dishId) {
// code
}
You should probably use two different caches. In the first case, you are trying to save a list (return type of the getAll method) into a cache specified for individual DishResponseDTOs. That's why you get the exception.
If you don't specify the types, the cache will assume Object, so you won't have any type safety. See, for example, Ehcache docs.
Actually, I am caching an Http Response using Spring Cache and now I want to put a condition that is to update the cache only when the Response is valid.
#Cacheable(value = CACHE, condition = "#result.body.responseData.toLowerCase().contains("A")")
public ResponseEntity<ProcessMqReqPostResponseBody> sendMqRequest(Integer pageNumber, Integer pageSize, String sortOrder, String merchantId) {
//Method Implementation
}
Without the condition, I can test my cache fine but when I added this condition, I get the error
org.springframework.expression.spel.SpelEvaluationException: EL1007E:
Property or field 'body' cannot be found on null
I don't understand that because I can output the responseEntity in my test and it is not null. Is that behavior correct?
Thanks,
Ashley
Spring's behavior is correct because you are writing a condition that will work according to the result.
The #result value is always null before the method is executed, the result value is only filled after the method is executed.
If you want to test this, you can change your condition as follows, method will not be cached at all and will not give an error.
#Cacheable(value = CACHE, condition = "#result != null and #result.body.responseData.toLowerCase().contains(\"A\")")
If you want to act on the result, you can only do so using the unless element.
Unlike condition(), this expression is evaluated after the method has been called and can therefore refer to the result.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/cache/annotation/Cacheable.html#unless--
I need 3 separate caches:
Response with some data
Null
Exception
I've already defined the two caches
#Caching(cacheable = {
#Cacheable(value = "SomeCache", key = "#a1", unless = "#result == null"),
#Cacheable(value = "SomeNullCache", key = "#a1", unless = "#result != null")})
So, I have to implement the last case.
JSR-107 provides #CacheResult annotation with exceptionCacheName attribute, but how can I do this using Spring Cache? I don't want to combine JSR-107 and Spring Cache.
The cache abstraction does not support caching exception throw by annotated method. Your setup looks very weird to me. Why would you use two different regions for null and non-null values?
Use the standard annotation if you want such setup.
I don't want to combine JSR-107 and Spring Cache.
The id generation being different (to be spec compliant and keeping backward compatibility), I wouldn't recommend such usage. At least not on the same region.
I want to set null value to entity by sending null request.
For example:
PATCH: "{deleteDate: null}" to http://localhost/api/entity/1
But it doesn't work.
I found here information how PATCH requests processed:
An new instance of Foo is created
Foo is populated with all values that have been sent with the request
The Foo entity with the id provided by the URI is loaded
All properties that differ between the two objects are copied from the new Foo to the persisted Foo, unless the value is null in the new Foo.
Do I understand correctly that it is impossible to set value to NULL with PATCH request to spring-data-rest service API?
In Spring context null values in PATCH method means that there are no changes.
If you want write null values you can
1) use PUT method;
2) implement your own DomainObjectMerger class, in which you can extend method merge with condition like
sourceValue != targetValue;
3) use DomainObjectMerger.NullHandlingPolicy configuration.
Depends on your Spring Data REST version.
All 3 options from egorlitvinenko's answer will solve the described problem but will have another one:
All other properties which were not specified in PATCH request would be "nullified" too.
Seems like spring-data-rest, issue-345 was already fixed in v2.2.x.