I am trying to create and delete a cache using RedisCacheManager with spring-boot and want to use HSET programmatically but am unable to do it. I am able to do it as a simple SET but not as HSET.
This is the bean that I have created.
#Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() //
.entryTtl(Duration.ofHours(1)) //
.disableCachingNullValues();
return RedisCacheManager.builder(connectionFactory) //
.cacheDefaults(config) //
.build();
And even made the class where I am making the call as #RedisHash but no luck.
#Service
#Slf4j
#RedisHash(value = "CURRENT_CALLS")
public class CacheCleanupService implements Serializable {
#CacheEvict(value = "CURRENT_CALLS" ,key = "(#cacheKey)")
public void redisCacheNumberCleanup(String cacheKey) {
log.info("Key CLEANUP from the cache: {}", cacheKey);
}
#Cacheable(value = "CURRENT_CALLS", key = "(#cacheKey)")
public String redisCacheNumberStore(String cacheKey) {
log.info("Key Add from the cache: {}", cacheKey);
return cacheKey;
}
}
The o/p I am getting is this when calling these above methods from another #Service class.
127.0.0.1:6379> keys CURRENT_CALLS:*
1) "CURRENT_CALLS::+15109100689:+15134631989"
2) "CURRENT_CALLS::+15109100648:+15134631989"
3) "CURRENT_CALLS::+15109100688:+15134631988"
127.0.0.1:6379> get "CURRENT_CALLS::+15109100648:+15134631989"
"+15109100648:+15134631989"
However, I want the o/p like this
127.0.0.1:6379> keys CURRENT_CALLS
1) "CURRENT_CALLS"
127.0.0.1:6379> hgetall "CURRENT_CALLS"
1) "+15109100648:+15134631989"
2) "1"
3) "+15109100688:+15134631988"
4) "2"
5) "+15109100689:+15134631989"
6) "3"
7) "+17326667726:+17722915819"
8) "4"
How to achieve this through spring-boot annotations.
It seems not likely to play around Redis Hashes data type using the annotation based way.
In the Cache Abstraction section of Spring Framework documentation, all the usages are based on basic cache operation (get key and set key value, if in the context of Redis)
As you mentioned that you are marking your service class with #RedisHash, though,
#RedisHash is used on entities, i.e., the models to be persisted in Redis.
See also
another SO question Spring Boot Caching with Redis and Store value as Redis Hash
tut on developer.redis.com Caching REST Services with Redis
Related
I have a use case where by the #CachePut annotation adds an entry to the cache, and I have to retrieve it manually (via code).
I can see that the total backup count gives me 1 as the number of entries, but all the maps give me the size as 0. So, I am not sure what I am doing wrong.
Here's my code
HazelcastConfig.java
#Configuration
public class HazelcastConfig {
#Bean
public Config hazelcastConf() {
Config c = new Config()
.setInstanceName("hazelcast-instance")
.addMapConfig(
new MapConfig()
.setName("testmap")
.setEvictionConfig(
new EvictionConfig()
.setEvictionPolicy(EvictionPolicy.LRU)
.setMaxSizePolicy(MaxSizePolicy.PER_NODE)
.setSize(1000)
)
.setTimeToLiveSeconds(500000)
);
c.getNetworkConfig().getRestApiConfig().setEnabled(true);
c.getNetworkConfig().getRestApiConfig().enableGroups(RestEndpointGroup.DATA);
return c;
}
}
TestServiceImpl.java
#Service
public class TestServiceImpl implements TestService {
#Autowired
#Lazy
private RestTemplate restTemplate;
#Override
#CachePut(value = "testmap", key="1")
public String getId() {
System.out.println("--------------------------");
System.out.println("-------INSIDE getId-------");
String id = null;
CBObject obj = restTemplate.getForObject("http://localhost:3000/testCB", CBObject.class);
if (null != obj && null != obj.getId()) {
id = String.valueOf(obj.getId());
}
System.out.println("-------- EXIT getId-------");
System.out.println("--------------------------");
return id;
}
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
TestController.java
#RestController
#RequestMapping("/v1")
public class TestController {
#Autowired
private TestService testService;
#GetMapping("/testCB")
public ResponseEntity<?> doCB() {
Map<String, String> resp = new HashMap<>();
String id = testService.getId();
if (null != id) {
resp.put("id", id);
}
Config config = new HazelcastConfig().hazelcastConf();
System.out.println(config.getMapConfig("testmap").getTotalBackupCount()); // 1
HazelcastInstance hz = Hazelcast.getHazelcastInstanceByName(config.getInstanceName());
System.out.println(hz.getReplicatedMap("testmap").size()); // 0
System.out.println(hz.getMap("testmap").size()); // 0
System.out.println(hz.getMultiMap("testmap").size()); // 0
return ResponseEntity.status(HttpStatus.ACCEPTED).body(resp);
}
}
Have you explicitly enabled caching using the #EnableCaching annotation (Ref Doc, Javadoc)?
Also, see the guidance from Spring Boot in the Ref Doc on Caching if you are using Spring Boot.
Furthermore, when using Spring Boot, you can either add the command-line switch --debug to your launch command or set the debug property to true in Spring Boot application.properties to get output from the Auto-configuration that has been applied. In particular you will want to see that the CacheAutoConfiguration class has been processed.
If you are NOT using Spring Boot, then in addition to the #EnableCaching annotation, you will also need to explicitly declare a CacheManager bean, such as:
#Bean
HazelcastCacheManager cacheManager(HazelcastInstance hazelcaseInstance) {
return new HazelcastCacheManager(hazelcastInstance);
}
This will require the com.hazelcast:hazelcast-spring JAR dependency on your runtime classpath.
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast-spring</artifactId>
<version>${hazelcast.version}</version>
<scope>runtime</scope>
</dependency>
For example.
NOTE: Do you not confuse the HazelcastCacheManager Spring Cache Abstraction CacheManager implementation, which requires the hazelcast-spring JAR and is required by Spring's Cache Abstraction either with or without Spring Boot, with Hazelcast's standard HazelcastCacheManager. These 2 classes are not the same thing.
Alternatively, you could also use Hazelcast as a JCache caching provider implementation in either Spring Framework or Spring Boot. The core Spring Framework offers support for using JCache as well. When using Spring Boot, you will need to specify the JCache cache provider type for Hazelcast (i.e. Embedded or Client/Server). I will leave this as an exercise for you to figure out.
Lastly, I recently built an example for my own testing purposes using Hazelcast as a caching provider in Spring Framework's Cache Abstraction using Spring Boot, if you would like to take a look.
Hope this helps!
Cheers!
Developed an API to retrieve data from a table and put it into cache in spring boot from Eclipse. Integrated with an SAP cloud platform.
DB- SAP cloud HANA DB.
Step-1: Call an API from the postman.
Step-2: Method invoked. The method annotated with #Cacheable("dictionary")
** ** -->At this time the values from DB 4000+ records get cached
--> within 5 mins if postman executes, values are returning faster based on execution time as expected.
Step-3: Every 5 mins, #CacheEvict is scheduled.
** Before Eviction starts, updating a DB column "comments" in DB **
Step-4: Scheduled Eviction method executed.
#Service
public class CacheEvit {
#Autowired
private CacheManager cacheManager;
#Scheduled(cron = "0 0/5 * * * ?") // execure after every 5 min
public void clearCacheSchedule() {
System.err.println("inside clear scheduled--" + cacheManager.getCacheNames());
Cache c = cacheManager.getCache("dictionary");
for (String name : cacheManager.getCacheNames()) {
System.err.println("inside clear scheduled before clear in for loop" + cacheManager.getCacheNames());
cacheManager.getCache("dictionary").clear(); // clear cache by name
}
if (c != null) {
System.err.println("cleared succ -- " + cacheManager.getCacheNames());
} else {
System.err.println("cache is not null");
}
}
}
#Cacheable("dictionary")
public List<DictionaryEntity> getDictionaryData() {
System.err.println("inside getDictionaryData");
return dictionaryServiceImpl.getDictionary();
}
#EnableScheduling
#EnableCaching --> declared in Starter class.
#Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
new ConcurrentMapCache("dictionary")));
return cacheManager;
}
These both are in different java classes and different services.
Problem: As cache got cleared, The method with #Cachable is executed ( observed by printing log ) but the value of cache is not loaded newly as we edited comments, still its returning old values
For me the issue was that the cacheable method was in the same class, which you mentioned are not in your case. However, cacheable will not get called if the classes are part of the same bean. I separated them and it works fine for me. Hope it helps.
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.
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();
}
I have started converting an existing Spring Boot(1.5.4.RELEASE) application to support multi-tenant capabilities. So i am using MySQL as the database and Spring Data JPA as the data access mechanism. i am using the schema based multi-tenant approach. As Hibernate document suggests below
https://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch16.html
I have implemented MultiTenantConnectionProvider and CurrentTenantIdentifierResolver interfaces and I am using ThreadLocal variable to maintain the current tenant for the incoming request.
public class TenantContext {
final public static String DEFAULT_TENANT = "master";
private static ThreadLocal<Tenant> tenantConfig = new ThreadLocal<Tenant>() {
#Override
protected Tenant initialValue() {
Tenant tenant = new Tenant();
tenant.setSchemaName(DEFAULT_TENANT);
return tenant;
}
};
public static Tenant getTenant() {
return tenantConfig.get();
}
public static void setTenant(Tenant tenant) {
tenantConfig.set(tenant);
}
public static String getTenantSchema() {
return tenantConfig.get().getSchemaName();
}
public static void clear() {
tenantConfig.remove();
}
}
Then i have implemented a filter and there i set the tenant dynamically looking at a request header as below
String targetTenantName = request.getHeader(TENANT_HTTP_HEADER);
Tenant tenant = new Tenant();
tenant.setSchemaName(targetTenantName);
TenantContext.setTenant(tenant);
This works fine and now my application points to different schema based on the request header value.
However there is a master schema where i store the some global settings and i need to access that schema while in a middle of a request for a tenant. Therefore i tried to hard code the Threadlocal variable just before that database call in the code as below.
Tenant tenant = new Tenant();
tenant.setSchemaName("master");
TenantContext.setTenant(tenant);
However this does not point to the master schema and instead it tries to access the original schema set during the filter. What is the reason for this?
As per my understanding Hibernate invokes openSession() during the first database call to a tenant and after i try to invoke another database call for "master" it still use the previous tenant as CurrentTenantIdentifierResolver invokes only during the openSession(). However these different database calls does not invoke within a transaction.
Can you please help me to understand the issue with my approach and any suggestions to fix the issue
Thanks
Keth
#JonathanJohx actually i am trying to override the TenantContext set by the filter in one of the controllers. First i am loging in a tenant where TenantContext is set to that particular tenant. While the request is in that tenant i am requesting data from master. In order to do that i am simply hard code the tenant as below
#RestController
#RequestMapping("/jobTemplates")
public class JobTemplateController {
#Autowired
JobTemplateService jobTemplateService;
#GetMapping
public JobTemplateList list(Pageable pageable){
Tenant tenant = new Tenant();
tenant.setSchemaName(multitenantMasterDb);
TenantContext.setTenant(tenant);
return jobTemplateService.list(pageable);
}