Why Redis Cache is not getting empty in my Spring Boot application? - java

I am using Redis Cache in my Spring Boot application to store data of multiple rest API's.
I am clearing Redis cache on a regular interval using Spring Cron Jobs. The method is getting called at required time-slots.
I have verified the logs but the cache is not getting clear and hence it is showing the stale data.
The code where I'm trying to clear the cache.
public class CustomerDerivation {
#Autowired
#Qualifier("redisCacheMngr")
CacheManager redisCacheMngr;
#Scheduled(cron = "${redis.api.update.interval}")
#CacheEvict(value = "redis-cache", allEntries = true, cacheNames = {"redis-cache"})
protected void cacheEvict() {
redisCacheMngr.getCache("redis-cache").clear();
logger.info("Evicting ModelCache");
}
}
Custom cache configuration code.
#Configuration
#Profile("cloud")
public class CacheConfig extends AbstractCloudConfig {
#Autowired
Environment env;
#Bean
public RedisConnectionFactory brRedisFactory() {
return connectionFactory().redisConnectionFactory(env.getProperty("model_cache_name"));
}
#Bean
public RedisTemplate<String, Object> brRedisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(brRedisFactory());
return redisTemplate;
}
#Bean(name = "redisCacheMngr")
public CacheManager cacheManager() {
RedisCacheManager cacheManager = new RedisCacheManager(brRedisTemplate());
cacheManager.setUsePrefix(true);
cacheManager.setTransactionAware(true);
return cacheManager;
}
}
How to fix the code to clear the redis cache ?

1) Have you enabled the cache using #EnableCaching annotation?
2) why are you using #CacheEvict and clear() in the same method? Both serves the same purpose. Use either one of them.

Check the following:
App is started with the according profile (ie. "cloud")
#EnabledCaching on your configuration
Avoid to mix #CacheEvict and the CacheManager bean, you have to choose one way for eviction
Extract the #Scheduled method in another "CronJobs" bean to avoid multiple annotations and AOP inside class issues.
Regards.

Spring use Spring AOP (Aspect Oriented Programming) to implement caching which means you must use public access level on your cacheEvict() method so it can be intercepted by AOP proxy. Otherwise it is like you never annotate your method with #CacheEvict

Related

Is kafka container factory a requirement in Spring Kafka?

I have a simple consumer in Spring working. I have a config class defined with a bunch of factories, etc. When I remove the config class, the consumer still works. I'm wondering the benefit of having the factory, ie:
#Bean
public ConcurrentKafkaListenerContainerFactory<String,
GenericRecord> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, GenericRecord> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
factory.setBatchListener(true);
return factory;
}
public ConsumerFactory<String, GenericRecord> consumerFactory() {
return new DefaultKafkaConsumerFactory<>(retrieveConsumerConfigs());
}
and now just passing vals in via application properties and calling it a day. I have explicit control over the config in the class-based approach, but was also thinking I could drop the class and have the vals be available through the spring env variables like spring.kafka.bootstrapservers, for example.
The container factory is required for #KafkaListener methods.
Spring Boot will auto-configure one (from application.properties/yml) if you don't provide your own bean. See KafkaAutoConfiguration.
Boot will also configure the consumer factory (if you don't).
An application, typically, does not need to declare any infrastructure beans.
EDIT
I prefer to never declare my own infrastructure beans. If I need some feature that is not exposed as a Boot property, or where I want to override some property for just one container, I simply add a customizer bean.
#Component
class Customizer {
public Customizer(ConcurrentKafkaListenerContainerFactory<?, ?> factory) {
factory.setContainerCustomizer(container -> {
if (container.getContainerProperties().getGroupId().equals("slowGroup")) {
container.getContainerProperties().setIdleBetweenPolls(60_000);
}
});
}
}
or
#Component
class Customizer {
Customizer(AbstractKafkaListenerContainerFactory<?, ?, ?> containerFactory,
ThreadPoolTaskExecutor exec) {
containerFactory.getContainerProperties().setConsumerTaskExecutor(exec);
}
}
etc.
the simple consumer in Spring works because spring-boot auto-configuration under the hoods creates an object of ConcurrentKafkaListenerContainerFactory and registers it with the spring container.
You can validate it by injecting the implementation of KafkaListenerContainerFactory as done below:
#RestController
public class EmployeeController {
private final KafkaListenerContainerFactory kafkaListenerContainerFactory;
#Autowired
public EmployeeController(KafkaListenerContainerFactory kafkaListenerContainerFactory) {
System.out.println(kafkaListenerContainerFactory instanceof ConcurrentKafkaListenerContainerFactory);
this.kafkaListenerContainerFactory = kafkaListenerContainerFactory;
}
}
But if you are not happy with spring boot's auto-generated bean, you can create your own bean and register it with the spring container by using #Bean annotation

#Cacheable doesn't intercept the method, cache is always empty

I have a method as following:
#Cacheable(value = "SAMPLE")
public List<SomeObj> find() {
// Method that initiates and returns the List<SomeObj> and takes around 2-3 seconds, does some logging too
}
And I am enabling caching in one of my configuration classes:
#EnableCaching
#Configuration
public SomeConf extends CachingConfigurerSupport {
// Here I also initialize my classes with #Cacheable annotation
#Bean
#Override
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Collections.singletonList((new ConcurrentMapCache("SAMPLE"))));
return cacheManager;
}
#Bean
#Override
public CacheResolver cacheResolver() {
return new SimpleCacheResolver(cacheManager());
}
#Bean
#Override
public KeyGenerator keyGenerator() {
return new SimpleKeyGenerator();
}
}
I have the following in my pom.xml:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>1.5.14.RELEASE</version>
</dependency>
I am declaring a CacheManager as follows:
#Bean
public CacheManager cacheManager(){
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Collections.singletonList((new ConcurrentMapCache("SAMPLE"))));
return cacheManager;
}
When I get an #Autowired CacheManager instance into one of my #Services I can see that there exists a cache with name "SAMPLE", but its entries are always empty. I call again and again the method find(), but it doesn't seem to populate the cache.
I have tried to put an argument (say int a) to the find() method and put it as key = "#a" to #Cacheable, but nothing changed.
When I try to recreate the issue in an isolated environment, I can see that it works properly. But when I add my dependencies (non open-source company libraries, which include an EhCache configuration as well) it doesn't work. How can I debug this, what am I doing wrong?
Update:
I have also tried to use cacheManager = myCacheManager in #Cacheable as well. No luck.
Update 2:
I am using AspectJ and Spring AOP. I think that it may have something to do with it. I have tried #EnableCaching(mode = AdviceMode.ASPECTJ) with #EnableLoadTimeWeaving but same thing.
Update 3:
I was finally able to reproduce the issue, here it is: client-api-cache
Basically when you run the application and telnet localhost 9000 after you send any line to it, it should print NOT CACHED once even though the method is called twice in CachedController (Second coming from the cache). But it prints twice.
The root cause is that you are misusing "afterPropertiesSet". So, what you are doing is endless loop and never pass control back to the Spring pipeline, so Spring isn't able to init caching facility properly.
Check out code which fixes our problem: https://dumpz.org/cbx8h28KeAss
As Andrews S noted, this does sound like colliding caches in the auto configuration.
#EnableCaching's javadoc has some useful notes regarding how the cacheManager is selected and, specifically, an idea on how to proceed. What you'd do in this case is establish a CachingConfigurer to select your cache - perhaps you can just extend CachingConfigurerSupport (as in example below) and be done.
A bean of type CacheManager must be registered, as there is no reasonable default that the framework can use as a convention. And whereas the <cache:annotation-driven> element assumes a bean named "cacheManager", #EnableCaching searches for a cache manager bean by type. Therefore, naming of the cache manager bean method is not significant.
For those that wish to establish a more direct relationship between #EnableCaching and the exact cache manager bean to be used, the CachingConfigurer callback interface may be implemented. Notice the #Override-annotated methods below:
#Configuration
#EnableCaching
public class AppConfig extends CachingConfigurerSupport {
#Bean
public MyService myService() {
// configure and return a class having #Cacheable methods
return new MyService();
}
#Bean
#Override
public CacheManager cacheManager() {
// configure and return an implementation of Spring's CacheManager SPI
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
return cacheManager;
}
#Bean
#Override
public KeyGenerator keyGenerator() {
// configure and return an implementation of Spring's KeyGenerator SPI
return new MyKeyGenerator();
}
}
This approach may be desirable simply because it is more explicit, or it may be necessary in order to distinguish between two CacheManager beans present in the same container.
Notice also the keyGenerator method in the example above. This allows for customizing the strategy for cache key generation, per Spring's KeyGenerator SPI. Normally, #EnableCaching will configure Spring's SimpleKeyGenerator for this purpose, but when implementing CachingConfigurer, a key generator must be provided explicitly. Return null or new SimpleKeyGenerator() from this method if no customization is necessary.
CachingConfigurer offers additional customization options: it is recommended to extend from CachingConfigurerSupport that provides a default implementation for all methods which can be useful if you do not need to customize everything. See CachingConfigurer Javadoc for further details.
You may also find this thread useful: How to have multiple cache manager configuration in spring cache java
Edit:
So thankfully I have a project using caching and wrote this little bit:
#Bean
#Override
public CacheResolver cacheResolver() {
return new SimpleCacheResolver(cacheManager()) {
#Override
protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
Collection<String> toReturn = super.getCacheNames(context);
toReturn.forEach(System.out::println);
return toReturn;
}
#Override
public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
System.out.println(Arrays.toString(context.getArgs()));
System.out.println(context.getClass());
System.out.println(context.getMethod());
System.out.println(context.getOperation());
return super.resolveCaches(context);
}
};
}
Aside from seeing my established cache names pop out, I do note that context is outputting:
[]
class org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContext
public abstract ...transferobjects.CacheContainer ...service.LookupService.getCacheSelections()
Builder[public ...transferobjects.CacheContainer ...dao.LookupFacade.getCacheSelections()] caches=[cacheselections] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='true'
The context.getClass() output is of interest, given your Aspect question. That said I have a very similar logging/timing aspect in my own code that isn't causing any confusion for the rest of the caching. Try my resolver out and see if the output is instructive on what calls are being made against the cache code.
Edit #2:
The TLDR issue appears to be that we're expecting #Cacheable to work in places where it can't - mainly due to the fact that the framework hasn't fully brought itself up yet. You are using InitializingBean and I tried replacing that functionality with #PostConstruct which both fail.
Spring cache using #Cacheable during #PostConstruct does not work
I got your github code. I initially ran into the blocking issue you reported in the other thread but, in the interest of pursuing one thing at a time, I just commented out that blocking code and called service.cachedMethod() directly.
I ran your jar file with --trace and noted that the cacheManager was scanned and created, but it wasn't initially obvious to me when it was being invoked. So, no big deal, I just had the bean method throw a RuntimeException. Upon doing that I noted that both your NOT CACHED lines print prior to that invocation - another hint that things aren't set up as expected.
Finally, I added System.out.println(service.toString()); in your Controller's afterPropertiesSet() method and saw com.company.client.api.domain.CachedServiceImpl#2bbfb8b - your object, not a proxied object. Thus no caching.
So, in general, you'll need to rework how you're trying to enable this functionality.
Make sure you are setting your cache into cachemanager as below.
#EnableCaching
#Configuration
public SomeConf {
#Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("find():List<SomeObj>")));
return cacheManager;
}
}

EhCache 3 and Spring Boot

Is there a way to abstract the EhCache 3 CacheManager (org.ehcache.CacheManager) to Spring's CacheManager (org.springframework.cache.CacheManager)?
With EhCache2, it is possible, by the following:
#Bean
public org.springframework.cache.CacheManager cacheManager(net.sf.ehcache.CacheManager ehcache) {
EhCacheCacheManager cacheManager = new EhCacheCacheManager(ehcache);
return cacheManager;
}
HINT: I've found a way to abstract the javax.cache.CacheManager cacheManager to the Spring's CacheManager (org.springframework.cache.CacheManager), by:
#Bean
public org.springframework.cache.CacheManager cacheManager(javax.cache.CacheManager cacheManager) {
return new JCacheCacheManager(cacheManager);
}
It will be also helpful if there is a way to cast org.ehcache.CacheManager to javax.cache.CacheManager .
Thanks.
Yes, you should rely on the standard Java caching specification, JSR-107, aka JCache (javax.cache.CacheManager)
Spring has a nice integration of it, and Ehcache2 and Ehcache3 also are compatible with it
Please have a look at this answer, it also comes with some simple examples : https://stackoverflow.com/a/39340151/24069

How to enable ehCache (3.x) in SpringMVC (5.x) project

In our project we use the ehcache 2.x and now we want to migrate to 3.x but somehow I fail tremendously. In 2.x we have the configuration in ehcache.xml. To what I read is that I need to change the content as well when using 3.x.
But first of all I have no clue how to wire the Cache by itself. I have
#EnableCaching
#Configuration
public class CacheConf {
#Bean
public CacheManager cacheManager() {
final CacheManagerBuilder<org.ehcache.CacheManager> ret = CacheManagerBuilder.newCacheManagerBuilder();
ret.withCache("properties", getPropCache());
ret.withCache("propertyTypes", getPropCache());
return (CacheManager) ret.build(true);
}
....
But this configuration I get a java.lang.ClassCastException: org.ehcache.core.EhcacheManager cannot be cast to org.springframework.cache.CacheManager which means that I cannot set the ehcache properly.
Note: In 2.x we configured different types of caches, more or less due to the expiration period - and since other object types are stored therein.
Hints to get it running, cause I didn't find a working sample with Spring 5 and ehcache 3.x? Best would be a config without xml!
The Ehcache CacheManager isn't a Spring CacheManager. This is why you get the ClassCastException.
Ehcache 3 is JSR107 (JCache) compliant. So Spring will connect to it using that.
You will find an example here and a simpler one in Spring Boot petclinic.
If I take your example, you will do something like
#EnableCaching
#Configuration
public class CacheConf {
#Bean
public CacheManager cacheManager() {
EhcacheCachingProvider provider = (EhcacheCachingProvider) Caching.getCachingProvider();
Map<String, CacheConfiguration<?, ?>> caches = new HashMap<>();
caches.put("properties", getPropCache());
caches.put("propertyTypes", getPropCache());
DefaultConfiguration configuration = new DefaultConfiguration(caches, provider.getDefaultClassLoader());
return new JCacheCacheManager(provider.getCacheManager(provider.getDefaultURI(), configuration));
}
private CacheConfiguration<?, ?> getPropCache() {
// access to the heap() could be done directly cause this returns what is required!
final ResourcePoolsBuilder res = ResourcePoolsBuilder.heap(1000);
// Spring does not allow anything else than Objects...
final CacheConfigurationBuilder<Object, Object> newCacheConfigurationBuilder = CacheConfigurationBuilder
.newCacheConfigurationBuilder(Object.class, Object.class, res);
return newCacheConfigurationBuilder.build();
}
}

Spring Caching - How to manage caches in different scopes?

I'm working on a Spring MVC application using Spring Caching Abstraction. The cache implementation is EhCache in version 2.6.5
Now I need two caches - one application scoped and another session scoped, since some method results are based on the web session. What is the best way to achieve this?
I figured that I can't register the session cache in the same CacheManager as the global cache, since there might be no session available when spring tries to inject the CacheManager. Therefore I have another CacheManager which is session scoped...
#Bean
public net.sf.ehcache.Cache sessionCache() {
return new net.sf.ehcache.Cache(sessionCacheConfiguration());
}
#Bean
#Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public CacheManager sessionCacheManager(){
net.sf.ehcache.CacheManager sessionCacheManager = net.sf.ehcache.CacheManager.create();
sessionCacheManager.setName("sessionCacheManager");
sessionCacheManager.addCache(sessionCache());
return new EhCacheCacheManager(sessionCacheManager);
}
#Bean
public CacheManager globalCacheManager() {
return new EhCacheCacheManager(ehCacheCacheManager().getObject());
}
#Bean
public EhCacheManagerFactoryBean ehCacheCacheManager() {
EhCacheManagerFactoryBean cmfb = new EhCacheManagerFactoryBean();
cmfb.setConfigLocation(new ClassPathResource("ehcache.xml"));
cmfb.setShared(true);
return cmfb;
}
#Bean
public CompositeCacheManager cacheManager(){
return new CompositeCacheManager(globalCacheManager(), sessionCacheManager()){{
setFallbackToNoOpCache(true);
}};
}
Unfortunately this doesn't work properly, spring creates the sessionCacheManager only once. So I get the cached results over all sessions.
You can only store serializable beans in a session. Of course, as long as you only have a single application node, you can technically store everything by the object reference. However, it will not be available after restart.
If you want to cache data belonging to a session define a global cache with a compound key (session-id, you-key).

Categories