Reputation: 7622
I am using Spring Cache @CacheEvict
& @Cacheable
Currently I am running a scheduler every Hr to clear cache and next time when fetchUser() is called it will fetch data from external APi and add to cache.
@Scheduled(cron = "0 0 * * * *}")
@CacheEvict(value = "some-unique-value", allEntries = true)
public void clearUserCache() {
log.info("Cache cleared");
}
@Cacheable(value = "some-unique-value", unless = "#result.isFailure()")
@Override
public Result<UserResponse> fetchUser() {
try {
UserResponse userResponse = api.fetchUserDetail();
return Result.success(userResponse);
} catch (Exception e) {
return Result.failure(INTERNAL_SERVER_ERROR);
}
}
Now what we need is to clear cache only when User API call is success. Is there a way to do that.
As now cache is cleared on schedule and suppose external API call fails. Main API will return error response. In that case I should be able to use existing cache itself.
Upvotes: 2
Views: 2731
Reputation: 7622
I didn't find any direct implementation but with a work around I was able to do it.
Use Case
Steps:
ON
on Schedule time and OFF
when cache is updated.UserService
class to check if scheduler was triggered or not.true
, trigger User API call. Check for response, if success. Trigger CacheEvict
method and update Cache.Sample Code:
SchedulerConfig
private boolean updateUserCache;
@Scheduled(cron = "${0 0 * * * *}") // runs every Hr
public void userScheduler() {
updateUserCache = true;
log.info("Scheduler triggered for User");
}
@CacheEvict(value = "USER_CACHE", allEntries = true)
public void clearUserCache() {
updateUserCache = false;
log.info("User cache cleared");
}
public boolean isUserCacheUpdateRequired() {
return updateUserCache;
}
UserService
UserResponse userResponse = null;
if (schedulerConfig.isUserCacheUpdateRequired()) {
userResponse = userCache.fetchUserDetail();
if (userResponse != null) {
// clear's cache and userResponse is stored in cache automatically when getUserDetail is called below
schedulerConfig.clearUserCache();
}
}
return userCache.getUserDetail(userResponse);
UserCache
@Cacheable(value = "USER_CACHE", key = "#root.targetClass", unless = "#result.isFailure()")
public Result<User> getUserDetail(UserResponse userResponse) {
try {
if (userResponse == null) { // handle first time trigger when cache is not available
userResponse = fetchUserDetail(); // actual API call
}
return Result.success(mapToUser(userResponse));
} catch (Exception e) {
return Result.failure("Error Response");
}
}
Note:
@Cacheable
part as separate Bean because caching only works on proxy objects. If I keep getUserDetail
inside UserService
and call directly, its not been intercepted as proxy and cache logic is not working, API call is triggered each time.Upvotes: 1
Reputation: 717
If I got it correctly, why don't you call it as a normal method after checking the API call is correct at this method's parent?
With your code, something along the lines of
// we just leave scheduled here as you need it.
@Scheduled(cron = "0 0 * * * *}")
@CacheEvict(value = "some-unique-value", allEntries = true)
public void clearUserCache() {
log.info("Cache cleared");
}
@Cacheable(value = "some-unique-value", unless = "#result.isFailure()")
@Override
public Result<UserResponse> fetchUser() {
try {
UserResponse userResponse = api.fetchUserDetail();
return Result.success(userResponse);
} catch (Exception e) {
return Result.failure(INTERNAL_SERVER_ERROR);
}
}
public void parentMethod() {
Result<UserResponse> userResult = this.fetchUser();
if(userResult.isFailure()) {
this.clearUserCache();
}
}
This way, if any Exception
is thrown it will return with a failure
status and you're able to check it. So the cache will be cleared either every hour or when it didn't work.
So the next time, as it was a failure and there's no cache, it will try again.
Upvotes: 1