-
Notifications
You must be signed in to change notification settings - Fork 31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Null values are not cached #127
Comments
I'm also facing the same issue in our use case We want to cache 404s from backends and we use @Cacheable and micronauut httpclient. In micronaut HttpClientIntroductionAdvice whenever the status code is 404 it returns null and in cacheInterceptor null value is ignored and it's not cached. So how can we cache 404s? Would appreciate any help. This is how we use @Cacheable and httpClient:
|
@graemerocher I am thinking about adding special token to represent the
|
Lots of difficult questions that I am not sure the answer to, it is certainly something that should be able to be disabled. Some caches serialize the value so the object would need to be some type that is serializable that you can then check the value of. A string like I think the behaviour should continue as it right now and only if the user sets say for example |
@graemerocher thank you for your feedback, it sounds nice. What do you think about implementing this behaviour for now only for Caffeine? |
And another question is it seems like it would require using Optional of null value, is it ok for you? |
I'm not sure what you mean, you can probably return |
I am talking here about this part in for (String cacheName : cacheNames) {
SyncCache syncCache = cacheManager.getCache(cacheName);
try {
Optional optional = syncCache.get(key, returnArgument);
if (optional.isPresent()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Value found in cache [" + cacheName + "] for invocation: " + context);
}
cacheHit = true;
wrapper.value = optional.get();
break;
}
} catch (RuntimeException e) {
if (errorHandler.handleLoadError(syncCache, key, e)) {
throw e;
}
}
}
if (!cacheHit) {
if (LOG.isDebugEnabled()) {
LOG.debug("Value not found in cache for invocation: " + context);
}
doProceed(context, wrapper);
syncPut(cacheNames, key, wrapper.value);
} But I am new to Java so maybe I misinterpret it. |
I would be in favor of just making this a configurable option. I stumbled on this issue as I was trying to find out if null values were cached or not. In my case I was expecting the behavior to not cache null by default - although I certainly understand the use cases presented above. Would a simple config property for each cache do the trick? |
Could be viable yes |
I think I stumbled into this today while completing the official tutorial on caching. It appears that when an empty list of strings is returned from the method marked Details: I was completing the tutorial https://guides.micronaut.io/latest/micronaut-cache-maven-java.html. When I started up the app, the first URL I tried was The only difference between November and the other months in the tutorial is that November has two headlines saved and the others don't. So this response doesn't involve using cached method return values: {
"month": "JANUARY"
} But this one does: {
"month": "NOVEMBER",
"headlines": [
"Micronaut Graduates to Trial Level in Thoughtworks technology radar Vol.1",
"Micronaut AOP: Awesome flexibility without the complexity"
]
} I found this unintuitive because to me, "the month of January has no headlines right now" sounds like a valid scenario for the business logic. I would want that cached. To recalculate this might involve performing work like a database query. It'd be nice to be able to skip that by using caching. A workaround I could think of would be to wrap my data in a type to represent the "no headlines" scenario, but this seems like redundant work because Java already has a type capable of conveying this information (an empty list of strings). |
I tested my workaround idea. For this application, it doesn't work because the cacheable method is returning null. I now realize my issue is exactly what's described here, where the problem is how it reacts to null being returned. Details: I wrapped my list of headlines in a new type: public record Headlines(int count, List<String> headlines) {} And I updated @Introspected
public record News(Month month, Headlines headlines) {} And I made the needed changes to the service class to use this type instead. I still get the behaviour I described above, and now I realize why. The code in the cacheable method is this: try {
TimeUnit.SECONDS.sleep(3);
return headlines.get(month);
} catch (InterruptedException e) {
return null;
} The catch block isn't relevant. But what is relevant is how it tries to get the headlines for the month by checking the map defined in the class: Map<Month, Headlines> headlines = new HashMap<Month, Headlines>() {{
put(Month.NOVEMBER, new Headlines(2, Arrays.asList("Micronaut Graduates to Trial Level in Thoughtworks technology radar Vol.1",
"Micronaut AOP: Awesome flexibility without the complexity")));
put(Month.OCTOBER, new Headlines(1, Collections.singletonList("Micronaut AOP: Awesome flexibility without the complexity")));
}}; And |
Another workaround I just tried was to use Details: I changed the return type of the cacheable method from This didn't work either. It still takes about three seconds every time for @Introspected
public record News(Month month, Optional<List<String>> headlines) {} @Cacheable
public Optional<List<String>> headlines(Month month) {
try {
TimeUnit.SECONDS.sleep(3);
if (!headlines.containsKey(month)) {
return Optional.empty();
}
return Optional.of(headlines.get(month));
} catch (InterruptedException e) {
return Optional.empty();
}
} So it seems like Micronaut's caching framework deals with |
My third attempt at a workaround works. If I make sure the cacheable method checks for when there are no headlines (by using Details: In @Cacheable
public List<String> headlines(Month month) {
try {
TimeUnit.SECONDS.sleep(3);
return headlines.get(month);
} catch (InterruptedException e) {
return null;
}
} to: @Cacheable
public List<String> headlines(Month month) {
try {
TimeUnit.SECONDS.sleep(3);
if (!headlines.containsKey(month)) {
return Collections.emptyList();
}
return headlines.get(month);
} catch (InterruptedException e) {
return null;
}
} Then all month URLs take about three seconds the first request and a few milliseconds for each subsequent request, including |
Is it intended that
null
value is not cached?I think it has to be configurable, because
null
is valid value for me, or am I missing something?Example:
Not every user has to have company, but when the method returns
null
, it's not cached, but called every time. I didn't even find this behaviour documented.I am using
io.micronaut.cache:micronaut-cache-caffeine:2.0.0.M2
.The text was updated successfully, but these errors were encountered: