Spring Redis Cache not evicting - spring

The following works (results in the evict being performed):
fooController {
#ApiEndpoint
public delete(id) {
fooService.deleteFoo(id)
}
}
fooService {
#CacheEvict(value = "cache1", key = "#id")
public void deleteFoo(Long id) {
//delete logic here
}
}
But this does not work (nothing is evicted from the cache):
fooController {
#ApiEndpoint
public delete(name) {
fooService.deleteFoo2(name)
}
}
fooService {
public void deleteFoo2(String name) {
//delete logic here
deleteFoo(name.getId())
}
#CacheEvict(value = "cache1", key = "#id")
public void deleteFoo(Long id) {
//delete logic here
}
}
Why are my #CacheEvict annotations only called when the method is called straight from the controller?
I'm using Redis as the caching mechanism.

Aop is not worinking when your method is called inside the class.
It is working when method is called by another class.
So you can define deleteFoo in another service.

To make spring aspect intercept #Cache* annotations you have to make external call. If you don't like to call this method from another object, use bean self-invocation approach. In this case your class is presented as two objects and one calls the other:
#Resource private FooController thisBean;
public delete(id) {
thisBean.deleteFoo(id)
}
#CacheEvict(value = "cache1", key = "#id")
public void deleteFoo(Long id) {}

Related

Spring declarative caching test only works if testing direct call to #Cacheable method

I'm trying to test a #Cacheable method using the following code, but is not working, the method is not being cached, if instead of putting the #Cacheable in the getCountCache method I put it in the getCount method the test works, tried a lot of things but couldn't find the cause.
#ContextConfiguration
#ExtendWith(SpringExtension.class)
public class CacheTest {
static class MyRepoImpl {
private Count count;
public MyRepoImpl(Count count){
this.count = count;
}
public int getCount(String key){
return getCountCache(key);
}
#Cacheable(cacheNames = "sample")
public int getCountCache(String key) {
return count.getCount();
}
}
static class Count {
int i = 0;
public int getCount() {
return i++;
}
}
#EnableCaching
#Configuration
public static class Config {
#Bean CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
#Bean MyRepoImpl myRepo() {
count = Mockito.mock(Count.class);
return new MyRepoImpl(count);
}
}
static Count count;
#Autowired MyRepoImpl repo;
#Test
public void methodInvocationShouldBeCached() {
Mockito.when(count.getCount()).thenReturn(1, 2, 3, 4);
Object result = repo.getCount("foo");
assertThat(result).isEqualTo(1);
Mockito.verify(count, Mockito.times(1)).getCount();
result = repo.getCount("foo");
assertThat(result).isEqualTo(1);
Mockito.verify(count, Mockito.times(1)).getCount();
}
}
Since Spring cache works on proxy to the bean method annotated with #Cachable called from inside class won't cache. You need to call it directly from outside of the class for caching mechanism to work.
Spring cache #Cacheable method ignored when called from within the same class this explanation is really good.

Spring caching eviction

I am having trouble with understanding spring #cacheEvict annotation. Does it a method to trigger cache eviction? Please see below.
#CacheEvict
public void clearEmployeeById(int id) {
//Do we have to add a method as trigger here in order to trigger the cache eviction? or can we leave this method without implementation
}
``
You need to specify the cache name in #CacheEvict
#CacheEvict(value = {"employee"}, allEntries = true)
public void clearEmployeeById(int id) {
//Logic
}
Cache employee will get evicted, Whenever clearEmplyoeeByID() method gets invoked.
To add cache into employee
#Cacheable(value = {"employee"})
public Employee addEmployeeById(int id) {
//Logic
return employee;
}
To clear all cache, you need to use CacheManager
#Autowired
private CacheManager cacheManager;
#Scheduled(fixedDelay = 86400000)
public boolean evictAllCaches() {
cacheManager.getCacheNames().stream().forEach(c -> cacheManager.getCache(c).clear());
return true;
}

Spring boot cache not working in #PostConstruct

I'm building a "class cache", with classes I want to call later.
The main goal is that I don't want scan the context every time that a class instance is needed.
# Model / Repository classes
#Getter
#RequiredArgsConstructor
public class Block implements Serializable {
private final String className;
private final Set<String> classCandidates = new HashSet<>();
public boolean addCandidate(final String classCandidate) {
return this.classCandidates.add(classCandidate);
}
}
#Slf4j
#Component
#CacheConfig(cacheNames = ConstantsCache.CACHE_BLOCK)
public class BlockRepository {
#Cacheable(key = "#className")
public Block findByInputClass(final String className) {
log.info("---> Loading classes for class '{}'", className);
val block = new Block(className);
findCandidates(block);
return block;
}
}
First to evaluate the cache, I've put the cache method #Autowired in a #RestController, wich works fine. The cache is populated when I call the rest method.
#RestController
public class Controller {
#Autowired
BlockRepository blockRepository;
#RequestMapping("/findByInputClass")
public Block findByInputClass(#RequestParam("className") final String className) {
return blockRepository.findByInputClass(className);
}
}
After doing that, I've moved the #Autowired object to a #Service, creating a method to self-populate the cache. But this does not work. The cache is not populated when the #PostConstructor method is called.
#Slf4j
#Component
public class BlockCacheService {
#Autowired
BlockRepository blockRepository;
#PostConstruct
private void postConstruct() {
log.info("*** {} PostConstruct called.", this.getClass().getTypeName());
val block = blockRepository.findByInputClass(ConstantsGenerics.BLOCK_PARENT_CLASS);
final Set<String> inputClasses = getInputFromCandidates(block.getClassCandidates());
appendClassesToCache(inputClasses);
}
private void appendClassesToCache(final Set<String> inputClasses) {
for (val inputClass : inputClasses) {
blockRepository.findByInputClass(inputClass);
}
}
}
How can I properly populate the cache using a service or component, that must start with the application.
Thanks in advance.
EDIT:
I've found a possible solution here: https://stackoverflow.com/a/28311225/1703546
Than I've changed the #Service code to put the cache manually instead of use the #Cacheable magic abstraction.
The class now is like this.
#Slf4j
#Component
public class BlockCacheService {
#Autowired
CacheManager cacheManager;
#Autowired
BlockRepository blockRepository;
#PostConstruct
private void postConstruct() {
log.info("*** {} PostConstruct called.", this.getClass().getTypeName());
val block = blockRepository.findByInputClass(ConstantsGenerics.BLOCK_PARENT_CLASS);
final Set<String> inputClasses = getInputFromCandidates(block.getClassCandidates());
appendClassesToCache(inputClasses);
}
private void appendClassesToCache(final Set<String> inputClasses) {
for (val inputClass : inputClasses) {
val block = blockRepository.findByInputClass(inputClass);
cacheManager.getCache(ConstantsCache.CACHE_BLOCK).put(block.getClassName(), block);
}
}
}
Now the cache is populated correctly, but the question is, this is the best solution?
Thanks.
You can't use an aspect in #PostConstruct as it may not have been created yet (and that is documented by the way).
One possible way to make that work is to implement SmartInitializingBean instead as it gives a callback when all singletons have been fully initialized (including their aspect. Changing that on your original service should work.
Having said that, this code of yours has an impact on the startup time. Why don't you let your cache to be filled lazily instead?

Spring Cache with Ehcache #CacheEvict doesn't work

I've got next method in UserService:
#Cacheable(value = "user", key="#p0")
public User find(String user) {
return userRepository.findByUser(User);
}
It caches well. In other service I have:
#Transactional
public void updateToken(int id, String token) {
Group group = groupRepository.findOne(id);
group.getMembers().forEach(member -> {
member.getUser().setToken(token);
removeUserCacheByName(member.getUser().getName());
});
groupRepository.save(group);
}
#CacheEvict(value = "user", key="#p0")
public void removeUserCacheByName(String name) {
log.debug("Removing user cache by name {}.", name);
}
After updateToken method, cache does not clear.
What you're seeing is normal. You're calling removeUserCacheByName() from within the Proxy object so the catching aspect doesn't execute. You have this behaviour explained in the documentation.
You can do some things to work around this:
1) Take the evict method (removeUserCacheByName) to another bean, autowire it in updateToken()'s class, and call it.
2) An ugly but useful one, autowire the ApplicationContext, get the same object from it and call the method, e.g.:
public class UserService{
#Autowired
private ApplicationContext ac;
#Transactional
public void updateToken(int id, String token) {
Group group = groupRepository.findOne(id);
group.getMembers().forEach(member -> {
member.getUser().setToken(token);
UserService sameBean = ac.getBean(UserService.class);
sameBean.removeUserCacheByName(member.getUser().getName());
});
groupRepository.save(group);
}
#CacheEvict(value = "user", key="#p0")
public void removeUserCacheByName(String name) {
log.debug("Removing user cache by name {}.", name);
}
}

What's the best way to access your AOP proxy in spring?

Due to Spring's implementation of AOP there are times when you want to call a method within the same class where you'd like the call to pass through your advice.
Here's a quick example
#Service
public class SomeDaoClass {
public int getSomeValue() {
// do some cache look up
Integer cached = ...;
if ( cached == null ) {
cached = txnGetSomeValue();
// store in cache
}
return cached;
}
#Transactional
public int txnGetSomeValue() {
// look up in database
}
}
Now ignore for a second that in spring I could use #Cached annotations for caching, the point of my example is that getSomeValue() is accessed by other beans passing through the spring proxy and running the associated advice. The internal call to txnGetSomeValue will not and in my example will miss having the advice we apply at the #Transactional point cut.
What's the best way to get access to your proxy so you can apply the advice?
My best approach works, but is clumsy. It heavily exposes the implementation. I figured this out years ago and have just stuck with this awkward code but would like to know what is the preferred method. My hacky approach looks like this.
public interface SomeDaoClass {
public int getSomeValue();
// sometimes I'll put these methods in another interface to hide them
// but keeping them in the primary interface for simplicity.
public int txnGetSomeValue();
}
#Service
public class SomeDaoClassImpl implements SomeDaoClass, BeanNameAware, ApplicationContextAware {
public int getSomeValue() {
// do some cache look up
Integer cached = ...;
if ( cached == null ) {
cached = proxy.txnGetSomeValue();
// store in cache
}
return cached;
}
#Transactional
public int txnGetSomeValue() {
// look up in database
}
SomeDaoClass proxy = this;
String beanName = null;
ApplicationContext ctx = null;
#Override
public void setBeanName(String name) {
beanName = name;
setProxy();
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ctx = applicationContext;
setProxy();
}
private void setProxy() {
if ( beanName == null || ctx == null )
return;
proxy = (BlockDao) ctx.getBean(beanName);
beanName = null;
ctx = null;
}
}
What I've done is add BeanNameAware and SpringContextAware so that I can look up my proxy object in Spring. Ugly I know. Any advice on a cleaner way to do this would be nice.
You could inject the proxy using #Resource
#Service
public class SomeDaoClassImpl implements SomeDaoClass {
#Resource
private SomeDaoClassImpl someDaoClassImpl;
public int getSomeValue() {
// do some cache look up
Integer cached = ...;
if ( cached == null ) {
cached = somDaoClassImpl.txnGetSomeValue();
// store in cache
}
return cached;
}
#Transactional
public int txnGetSomeValue() {
// look up in database
}
Note: #Autowired will not work.

Resources