I'm trying to configure Spring CacheManager with Hazelcast. Also, I want to configure Hazelcast's Near Cache so I can retrieve the (already deserialized) instance of my cached object.
Here is my configuration
#Bean
public HazelcastInstance hazelcastConfig() {
val config = new Config().setInstanceName("instance");
val serializationConfig = config.getSerializationConfig();
addCacheConfig(config, "USERS")
serializationConfig.addSerializerConfig(new SerializerConfig()
.setImplementation(getSerializer())
.setTypeClass(User.class)
return Hazelcast.newHazelcastInstance(config);
}
#Bean
public CacheManager cacheManager(HazelcastInstance hazelcastInstance) {
return new HazelcastCacheManager(hazelcastInstance);
}
#Bean
public PlatformTransactionManager chainedTransactionManager(PlatformTransactionManager jpaTransactionManager, HazelcastInstance hazelcastInstance) {
return new ChainedTransactionManager(
jpaTransactionManager,
new HazelcastTransactionManager(hazelcastInstance)
);
}
// Configure Near Cache
private void addCacheConfig(Config config, String cacheName) {
val nearCacheConfig = new NearCacheConfig()
.setInMemoryFormat(OBJECT)
.setCacheLocalEntries(true)
.setInvalidateOnChange(false)
.setTimeToLiveSeconds(hazelcastProperties.getTimeToLiveSeconds())
.setEvictionConfig(new EvictionConfig()
.setMaxSizePolicy(ENTRY_COUNT)
.setEvictionPolicy(EvictionPolicy.LRU)
.setSize(hazelcastProperties.getMaxEntriesSize()));
config.getMapConfig(cacheName)
.setInMemoryFormat(BINARY)
.setNearCacheConfig(nearCacheConfig);
}
Saving and retrieving from the Cache is working fine, but my object is deserialized every time I have a cache hit. I want to avoid this deserialization time using a NearCache, but it doesn´t work. I also tried BINARY memory format.
Is this possible with Hazelcast? Or is this deserialization always executed even if I have a NearCache?
Thanks
So after a few changes, it is working now. Here is my conclusion:
So in order to have NearCache working with Spring Cache, all your cached objects should be Immutable. This means final classes and final fields. Also, they all should extend the Serializable interface.
Related
I'm using Caffeine Cache library for Spring Cache. Is there a way to get all the cached keys?
My current application works on a near-realtime data, with the flow as :
In the Cache Updater Thread(which runs at a fixed interval, irrespective of the user request), I need to get all the keys currently in the Cache, fetch their latest data from Db & then use #CachePut to update the cache.
Yo can inject CacheManager and obtain native cache from it.
#AllArgsConstructor
class Test {
private CacheManager cacheManager;
Set<Object> keys(String cacheName){
CaffeineCache caffeineCache = (CaffeineCache) cacheManager.getCache(cacheName);
com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache = caffeineCache.getNativeCache();
return nativeCache.asMap().keySet();
}
}
Of course you should add some class casting checks.
You can return keyset by using asMap().keySet() method as follows.
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
class Test{
private Cache<String,String> testCache;
Test(){
testCache = Caffeine.newBuilder().expireAfterWrite( 3000, TimeUnit.SECONDS).build();
}
// return keys as a set
public Set<String> getCacheKeySet(){
return testCache.asMap().keySet();
}
For different values of cachedData it's fetching response Date every time from cachedData in parameter while as per my understanding if for a particular propertyId if there has been some call with some cachedData param it shouldn't fetch again instead it should pick from the cache.
MyMethod
#Cacheable(value = "responseCached", key="#propertyId", condition = "#result != null")
public Date fetchCachedData(String propertyId, Map<String, Date> cachedData) {
return cachedData.get(propertyId);
}
ehCacheConfig
#EnableCaching
#Configuration
public class EhCacheConfig {
#Bean
public EhCacheManagerFactoryBean ehCacheManagerFactory(){
EhCacheManagerFactoryBean ehCacheBean = new EhCacheManagerFactoryBean();
ehCacheBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
ehCacheBean.setShared(true);
return ehCacheBean;
}
#Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
List<Cache> caches = new ArrayList<Cache>();
caches.add(new ConcurrentMapCache("responseCached"));
cacheManager.setCaches(caches);
return cacheManager;
}
}
I think you have a misunderstanding of the general concept. If you annotate a method with #Cacheable executions of this method are avoided, in case the data is already in the cache. In your example the cache is the ConcurrentMapCache not the cachedData. The simple example is:
#Cacheable(value = "responseCached", key="#propertyId")
public Date fetchData(String propertyId) {
// computing or I/O intensive code to produce result here
Date d = ...
return d;
}
For a unique propertyId the method fetchData is only executed once. You can omit the key definition, since it is the only parameter.
A note to your method name fetchCachedData: The idea of the Spring cache abstraction is, that the users of the method (ideally) don't need to know whether something is cached or not. Its best to name your method after something in the business domain, like fetchOfferDate.
I try to create folders in the redis cache of my project.
I want something similar to what spring automatically do with spring session.
I have a lot of "DisplayItem" entries and I want to store them in a "displayitem" folder.
Here is my code :
#Cacheable(value = "displayItem", cacheManager = "longLifeCacheManager")
public DisplayItem getDisplayItem(String displayItemCode) {
// Do a lot of things
}
#Cacheable(cacheManager = "mediumLifeCacheManager", value = "preferences:userPreferences", key = "#zenithName")
public UserPreferences getUserPreferencesByZenithName(String zenithName) {
// Do something
}
If I replace the value by "foldername:displayItem", it create a folder, but only for the keys (like I did for the preferences in the screenshot).
I don't find how to store all the values in this same folder.
How can I do this ?
I finally found a solution.
In the configuration of the CacheManager, I need to set UserPrefix with True.
#Bean(name = "mediumLifeCacheManager")
public CacheManager mediumLifeCacheManager(RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setDefaultExpiration(redisExpirationMedium);
cacheManager.setUsePrefix(true);
return cacheManager;
}
Now this works like I want.
I'm using spring boot with second level cache for the entities, e.g.
#Entity
#Table(name = "customer")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Customer implements Serializable {
....
}
This is working as expected, but now I have to turn the application into a multi-tenant version. Each tenant has it's own database, where we use a ThreadLocal to store the current tenant and AbstractRoutingDataSource for routing to the tenant's database. This is working, if the 2nd level cache is off.
It would be nice to get also the 2nd level cache working. The problem seems to be the cache name, which is the FQCN for an entity. Since the cache is not tenant or database-aware, each tenant uses the same cache.
For this, we use a ThreadLocal for resolving the current tenant is simply accessible by
TenantContext.getCurrentTenant();
and returns the tenant name.
We use the EhCache, which is backed by the spring cache abstraction:
#Bean
public CacheManager cacheManager() {
net.sf.ehcache.CacheManager cacheManager = net.sf.ehcache.CacheManager.create();
EhCacheCacheManager ehCacheManager = new EhCacheCacheManager();
ehCacheManager.setCacheManager(cacheManager);
return ehCacheManager;
}
Is it possible to intercept the generation of the cache-name, so that the current tenant name is used instead of the FQCN and each insert/lookup/evict resolves this tenant-aware-cache-name?
In order to intercept the generation of the cache-name one approach is to override the method getCache(String name) of the EhCacheCacheManager as follows:
#Bean
public CacheManager cacheManager() {
net.sf.ehcache.CacheManager cacheManager = net.sf.ehcache.CacheManager.create();
EhCacheCacheManager ehCacheManager = new EhCacheCacheManager(){
#Override
public Cache getCache(String name) {
String tenantId = TenantContext.getCurrentTenant();
return super.getCache(String.format("%s:%s", tenantId, name));
}
};
ehCacheManager.setCacheManager(cacheManager);
return ehCacheManager;
}
I have a problem when trying to get back a Guava cache from a cache manager, instead I get a Spring Cache.
This is the bean in my SpringConfig file :
#Bean
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager() {
#Override
protected Cache createConcurrentMapCache(final String name) {
return new ConcurrentMapCache(name, CacheBuilder.newBuilder().expireAfterWrite(1440, TimeUnit.MINUTES)
.maximumSize(100).recordStats().build().asMap(), false); }
};
return cacheManager;
}
And then I'm able to use the #Cacheable :
#Cacheable(value = "myCache")
public void myCacheMethod(String key){
// call a web service
}
Everything works fine, but I can't get the cache Guava object created by the CacheBuilder in order to call the stats() method.
This is how I get the cache :
Cache myCache = cacheManager.getCache("myCache");
ValueWrapper wrapper = myCache.get("key");
WebServiceType myCachedObject= (WebServiceType) wrapper.get();
The last cache is a Spring cache, and I get an error if I cast it to Guava cache.
Is this possible ? Or did I do something wrong ?