Spring Boot Custom Cache Resolver and Cache Manager using HazelCast - spring

I have multiple questions related to HazelCast core as well as Spring Boot cache API.
Let me lay out the scenario first.
We have a monitoring system for monitoring multiple network infrastructures.
I have a Spring Boot app which could be deployed as multiple nodes behind a load-balancer.
In addition to that, this same app can work for multiple infrastructures by just running it with different profile such as infra-1-prod, infra-2-prod etc.
Its horizontally scalable as well as versatile. This nature is achieved by running the application with different profiles.
Along with other things, this profile change, changes the underlying DB connections to a relational database which contains the configuration data for a particular infrastructure.
Have a look at the relevant architecture for the application
The same spring boot application could be run as a node for different infrastructures spawning its own HazelCast instance node. If we have 6 nodes for the application, there will be 6 nodes for the HazelCast cluster. All of them will be in sync.
Now I have a Repository named RuleRepository which returns the Rule data for a particular Rule Alias.
#Repository
public interface RuleRepository extends JpaRepository<Rule, Long> {
#Cacheable(value = Constants.CACHE_ALIAS)
Optional<Rule> findByAlias(String ruleAlias);
//some other functions
}
Now the problem is, as the Rule Aliases are auto generated by DB sequences, an alias R_123 points to different data for Infra-1 and Infra-2 nodes but because all the HazelCast nodes are in sync, incorrect data is overridden.
For this, I thought of giving different names to the cache for every infrastructure so that the cached data don't get jumbled.
Doing this is not straight forward because we can't inject properties into the cache names. For this we need to implement our own custom CacheResolver and CacheManager.
I will lay out my understanding of HazelCast before I ask the first question.
Every HazelCast Instance can have multiple Map Configurations which are basically just different caches. Every CacheManager can be linked with a Single HazelCast instance which will internally contain multiple caches.
Question 1: If the relationship between CacheManager and HazelCastInstance is one-to-one then how will I determine which method data will be cached into which cache (Map Config).
Here is the incomplete implementation I have with me currently
public class CacheableOperations {
private final CacheManager cacheManager;
private final CacheManager noOpCacheManager;
public CacheableOperations(CacheManager cacheManager, CacheManager noOpCacheManager) {
this.cacheManager = cacheManager;
this.noOpCacheManager = noOpCacheManager;
}
private Map<String, CacheableOperation<?>> opMap;
public void init() {
List<CacheableOperation<? extends Class>> ops = new ArrayList<>();
ops.add(new CacheableOperation.Builder(RuleRepository.class)
.method("findByAlias")
.cacheManager(cacheManager)
.build());
postProcessOperations(ops);
}
public CacheableOperation<?> get(CacheOperationInvocationContext<?> context) {
final String queryKey = getOperationKey(context.getTarget().getClass().getName(),
context.getMethod().getName());
return opMap.get(queryKey);
}
private void postProcessOperations(List<CacheableOperation<? extends Class>> ops) {
Map<String, CacheableOperation<?>> tempMap = new HashMap<>();
for (CacheableOperation<?> op : ops) {
for (String methodName : op.getMethodNames()) {
tempMap.put(getOperationKey(op.getTargetClass().getName(), methodName), op);
}
}
opMap = ImmutableMap.copyOf(tempMap);
}
private String getOperationKey(String first, String second) {
return String.format("%s-%s", first, second);
}
Here is the class for CacheConfiguration
#Configuration
#AllArgsConstructor
public class CacheConfiguration extends CachingConfigurerSupport {
private final CacheProperties cacheProperties;
private SysdiagProperties sysdiagProperties;
#Bean
#Override
public CacheManager cacheManager() {
return new HazelcastCacheManager(hazelcastInstance());
}
#Bean
#Profile("client")
HazelcastInstance hazelcastInstance() {
Config config = new Config();
config.getNetworkConfig().getJoin().getTcpIpConfig().addMember(sysdiagProperties.getCache().getMemberIps()).setEnabled(true);
config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);
config.setInstanceName("restapi-master-cache-" + sysdiagProperties.getServiceName());
return Hazelcast.newHazelcastInstance(config);
}
#Bean
#Override
public CacheResolver cacheResolver() {
return new CustomCacheResolver(cacheProperties, operations(), noOpCacheManager());
}
#Bean
public CacheManager noOpCacheManager() {
return new NoOpCacheManager();
}
#Bean
public CacheableOperations operations() {
CacheableOperations operations = new CacheableOperations(cacheManager(), noOpCacheManager());
operations.init();
return operations;
}
And here is the CacheableOperation class
public class CacheableOperation<T> {
private final Class<T> targetClass;
private final String[] methodNames;
private final CacheManager cacheManager;
private CacheableOperation(Class<T> targetClass, String[] methodNames, CacheManager cacheManager) {
this.targetClass = targetClass;
this.methodNames = methodNames;
this.cacheManager = cacheManager;
}
public Class<T> getTargetClass() {
return targetClass;
}
public String[] getMethodNames() {
return methodNames;
}
public CacheManager getCacheManager() {
return cacheManager;
}
public static class Builder<T> {
private final Class<T> targetClass;
private String[] methodNames;
private CacheManager cacheManager;
private Map<String, Method> methods = new HashMap<>();
public Builder(Class<T> targetClass) {
this.targetClass = targetClass;
Arrays.stream(targetClass.getDeclaredMethods())
.forEachOrdered(method -> methods.put(method.getName(), method));
}
public Builder<T> method(String... methodNames) {
this.methodNames = methodNames;
return this;
}
public Builder<T> cacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
return this;
}
public CacheableOperation<T> build() {
checkArgument(targetClass != null);
checkArgument(ArrayUtils.isNotEmpty(methodNames));
checkArgument(Arrays.stream(methodNames).allMatch(name -> methods.get(name) != null));
return new CacheableOperation<T>(targetClass, methodNames, cacheManager);
}
}
}
And finally the CacheResolver
public class CustomCacheResolver implements CacheResolver {
private final CacheableOperations operations;
private final CacheProperties cacheProperties;
private final CacheManager noOpCacheManager;
public CustomCacheResolver(CacheProperties cacheProperties, CacheableOperations operations, CacheManager noOpCacheManager) {
this.cacheProperties = cacheProperties;
this.operations = operations;
this.noOpCacheManager = noOpCacheManager;
}
#Override
public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
if (!cacheProperties.isEnabled()) {
return getCaches(noOpCacheManager, context);
}
Collection<Cache> caches = new ArrayList<>();
CacheableOperation operation = operations.get(context);
if (operation != null) {
CacheManager cacheManager = operation.getCacheManager();
if (cacheManager != null) {
caches = getCaches(cacheManager, context);
}
}
return caches;
}
private Collection<Cache> getCaches(CacheManager cacheManager, CacheOperationInvocationContext<?> context) {
return context.getOperation().getCacheNames().stream()
.map(cacheName -> cacheManager.getCache(cacheName))
.filter(cache -> cache != null)
.collect(Collectors.toList());
}
}
Question 2: In this whole code base, I cannot find the linkage between a Cache Name and a Method Name which I did in the first snippet. All I could see is a link between the method name and the cacheManager instance. Where do I define that?
All the questions and documentation I read about Spring Boot and HazelCast, does not seem to go in great depth in this case.
Question 3: Can someone define the role of a CacheResolver and a CacheManager in a straight forward manner for me.
Thanks for the patience. Answer to even one of the question might help me a lot. :)

You can specify the parameter in the #Cacheable annotation. For example:
#Cacheable("books")
public String getBookNameByIsbn(String isbn) {
return findBookInSlowSource(isbn);
}
That will decide on the name internal map/cache used.

Related

How to connect Redis and Couchbase in spring

How can I connect redis and couchbase to my spring application.
I get this error Parameter 0 of method couchbaseMappingContext in org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration required a single bean, but 2 were found: - couchbaseCustomConversions: defined by method 'customConversions' in class path resource [{classPath}/chat/config/CouchbaseConfiguration.class] - redisCustomConversions: defined in null
I only need redis to 'look' at one package and the other ones need to be connected with only couchbase.
Redis config
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
MappingJackson2HttpMessageConverter converter =
new MappingJackson2HttpMessageConverter(mapper);
return converter;
}
#Bean
public RedisTemplate<Long, ?> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Long, ?> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// Add some specific configuration here. Key serializers, etc.
return template;
}
Couchbase config
#EnableCouchbaseRepositories
#Configuration
public class CouchbaseConfiguration extends AbstractCouchbaseConfiguration {
#Value("${spring.data.couchbase.bucket-name}")
private String bucketName;
#Value("${spring.couchbase.username}")
private String username;
#Value("${spring.couchbase.password}")
private String password;
#Value("${spring.couchbase.connection-string}")
private String connectionString;
#Override
public String getConnectionString() {
return this.connectionString;
}
#Override
public String getUserName() {
return this.username;
}
#Override
public String getPassword() {
return this.password;
}
#Override
public String getBucketName() {
return this.bucketName;
}
}
and when I first start my app in terminal there is this info : Spring Data Redis - Could not safely identify store assignment for repository candidate interface
To resolve the ambiguity in taking the customConversions bean, we could tell the couchbase configuration class how to create the customConversions bean. Adding the below code to the class which extends AbstractCouchbaseConfiguration should solve the issue
#Bean
public CustomConversions customConversions() {
return super.customConversions();
}

How to implement spring data mongo multidatabase transactions

I created an application that copies data from one database to another one based on this tutorial.
In fact, I need to make a method that inserts in two different databases transactional.
Is this possible with MongoDB ? and how can I implement it?
To use multi-document transactions in MongoDB across multiple databases in Spring Data MongoDB, you need to configure a MongoTemplate per database, but all of them must use the same MongoDbFactory because it is treated as the transactional resource.
This means that you will need to override a couple of methods of MongoTemplate to make it use the database it should use (and not the one configured inside SimpleMongoClientDbFactory).
Let's assume that your databases are called 'one' and 'two'. Then it goes like this:
public class MongoTemplateWithFixedDatabase extends MongoTemplate {
private final MongoDbFactory mongoDbFactory;
private final String databaseName;
public MongoTemplateWithFixedDatabase(MongoDbFactory mongoDbFactory,
MappingMongoConverter mappingMongoConverter, String databaseName) {
super(mongoDbFactory, mappingMongoConverter);
this.mongoDbFactory = mongoDbFactory;
this.databaseName = databaseName;
}
#Override
protected MongoDatabase doGetDatabase() {
return MongoDatabaseUtils.getDatabase(databaseName, mongoDbFactory, ON_ACTUAL_TRANSACTION);
}
}
and
#Bean
public MongoDbFactory mongoDbFactory() {
// here, some 'default' database name is configured, the following MongoTemplate instances will ignore it
return new SimpleMongoDbFactory(mongoClient(), getDatabaseName());
}
#Bean
public MongoTransactionManager mongoTransactionManager() {
return new MongoTransactionManager(mongoDbFactory());
}
#Bean
public MongoTemplate mongoTemplateOne(MongoDbFactory mongoDbFactory,
MappingMongoConverter mappingMongoConverter) {
MongoTemplate template = new MongoTemplateWithFixedDatabase(mongoDbFactory,
mappingMongoConverter, "one");
return template;
}
#Bean
public MongoTemplate mongoTemplateTwo(MongoDbFactory mongoDbFactory,
MappingMongoConverter mappingMongoConverter) {
MongoTemplate template = new MongoTemplateWithFixedDatabase(mongoDbFactory,
mappingMongoConverter, "two");
return template;
}
Then just inject mongoTemplateOne and mongoTemplateTwo in your service, mark its method with #Transactional and it should work.
Reactive case
In the reactive case, it's very similar. Of course, you need to use reactive versions of the classes like ReactiveMongoTemplate, ReactiveMongoDatabaseFactory, ReactiveMongoTransactionManager.
There is also a couple of caveats. First, you have to override 3 methods, not 2 (as getCollection(String) also needs to be overridden). Also, I had to do it with an abstract class to make it work:
#Bean
public ReactiveMongoOperations reactiveMongoTemplateOne(
#ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory,
MappingMongoConverter mappingMongoConverter) {
ReactiveMongoTemplate template = new ReactiveMongoTemplate(reactiveMongoDatabaseFactory,
mappingMongoConverter) {
#Override
protected Mono<MongoDatabase> doGetDatabase() {
return ReactiveMongoDatabaseUtils.getDatabase("one", reactiveMongoDatabaseFactory,
ON_ACTUAL_TRANSACTION);
}
#Override
public MongoDatabase getMongoDatabase() {
return reactiveMongoDatabaseFactory.getMongoDatabase(getDatabaseName());
}
#Override
public MongoCollection<Document> getCollection(String collectionName) {
Assert.notNull(collectionName, "Collection name must not be null!");
try {
return reactiveMongoDatabaseFactory.getMongoDatabase(getDatabaseName())
.getCollection(collectionName);
} catch (RuntimeException e) {
throw potentiallyConvertRuntimeException(e,
reactiveMongoDatabaseFactory.getExceptionTranslator());
}
}
private RuntimeException potentiallyConvertRuntimeException(RuntimeException ex,
PersistenceExceptionTranslator exceptionTranslator) {
RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(ex);
return resolved == null ? ex : resolved;
}
};
return template;
}
P.S. the provided code was tested with spring-data-mongodb 2.2.4.

ListenerObject not found in imports for Ehcache 3?

I am trying to implement a listener for an Ehcache 3.3.1 project using the code below. Can anyone suggest a solution for the ListenerObject? I can't seem to find it anywhere,except on the docs page I got the code from
import java.util.logging.Level;
import java.util.logging.Logger;
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheEventListenerConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.event.EventType;
public class CacheHandler{
private Logger LOG = Logger.getLogger(this.getClass().getName());
private String cacheName="basicCache";
public Cache cache;
public CacheHandler(){
if(cache==null)
cache=initCache();
}
private Cache initCache(){
CacheEventListenerConfigurationBuilder cacheEventListenerConfiguration = CacheEventListenerConfigurationBuilder
.newEventListenerConfiguration(new ListenerObject(), EventType.CREATED, EventType.UPDATED)
.unordered().asynchronous();
final CacheManager manager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache(cacheName,
CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(10))
.add(cacheEventListenerConfiguration)
).build(true);
final Cache<String, String> cache = manager.getCache("foo", String.class, String.class);
return cache;
}
public Cache getCache(){
if(cache==null)
cache=initCache();
return cache;
}
}
It is indeed not mentioned but since it is only one method it is normally easy to figure out.
Here is an example:
public class ListenerObject implements CacheEventListener<Object, Object> {
#Override
public void onEvent(CacheEvent<? extends Object, ? extends Object> event) {
System.out.println(event);
}
}
The real one used in the documentation is here.
Then, I've played a bit with your code to real production usable code.
public class CacheHandler implements AutoCloseable {
private static final String CACHE_NAME = "basicCache";
private final Cache<String, String> cache;
private final CacheManager cacheManager;
public CacheHandler() {
cacheManager = initCacheManager();
cache = cacheManager.getCache(CACHE_NAME, String.class, String.class);
}
private CacheManager initCacheManager(){
CacheEventListenerConfigurationBuilder cacheEventListenerConfiguration = CacheEventListenerConfigurationBuilder
.newEventListenerConfiguration(new ListenerObject(), EventType.CREATED, EventType.UPDATED)
.ordered().synchronous();
return CacheManagerBuilder.newCacheManagerBuilder()
.withCache(CACHE_NAME,
CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(10))
.add(cacheEventListenerConfiguration)
).build(true);
}
public Cache getCache(){
return cache;
}
#Override
public void close() {
cacheManager.close();
}
public static void main(String[] args) {
try(CacheHandler handler = new CacheHandler()) {
Cache<String, String> cache = handler.getCache();
cache.put("a", "b");
cache.putIfAbsent("a", "c");
}
}
}
Some comments:
I assumed you want singleton cache kept in a variable. So that's what I did. The lazy initCache wasn't useful because the withCache tells Ehcache to create the cache when creating the cache manager.
We will want to keep a reference to the CacheManager in order to close it at the end.
The getCache was retrieving "foo", not "basicCache"

Spring Casheable returned cached objects fail equality check

The issue I am facing is that two objects returned from spring cacheable method with a same key fail assertSame test. Why are these objects not sharing one same storage area?
Details:
I am using redis cache mechanism to implement caching in a spring boot REST api.
The caching works correctly in the way that it first retrieve the data from externally provided source (JPS repository accessing a database) and then subsequent calls for the same cache key returns data from cache. However, I am not able to mimic this behavior completely in the JUnit test cases. My assertEquals or assertSame fail on 2 objects returned from the cache.
my code base looks as below:
mvn dependencies:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.7.6.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
Spring application config:
#SpringBootApplication
#EnableCaching
public class Application {
#Value("${redis.host}")
private String redisHost;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory jedisConFactory = new JedisConnectionFactory();
jedisConFactory.setHostName(redisHost);
jedisConFactory.setPort(6379);
return jedisConFactory;
}
#Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(jedisConnectionFactory());
return template;
}
#Bean
CacheManager cacheManager() {
return new RedisCacheManager(redisTemplate());
}
Service Class:
#Service
public class CIDomainService {
private RedisTemplate<String, Object> redisTemplate;
private CIDomainDAO ciDomainDAO;
#Autowired
public CIDomainService(CIDomainDAO ciDomainDAO, RedisTemplate<String, Object> redisTemplate) {
this.ciDomainDAO = ciDomainDAO;
this.redisTemplate = redisTemplate;
}
#Cacheable(value = "ciDomain", key = "#id")
public CIDomain getCIDomain(int id) {
CIDomain ciDomain = new CIDomain();
ciDomain.setId(id);
ciDomain.setName("SomeName");
return ciDomain;
}
public void clearAllCache() {
redisTemplate.delete("listCIDomains");
redisTemplate.delete("ciDomain");
}
}
ciDomainDAO in the service above is just a JPS repository interface using the findAll() method to retrieve data from external database or in-memory database. My Test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles("local")
#SpringBootTest
public class CIDomainServiceIntegrationTest {
#Autowired
CIDomainService ciDomainServiceSpy;
#Before
public void setUp(){
ciDomainServiceSpy.clearAllCache();
}
#Test
public void listCIDomains_ShouldRetrieveCIDomainsWithCachingPluggedIn() {
CIDomain domain1 = ciDomainServiceSpy.getCIDomain(1);
CIDomain domain2 = ciDomainServiceSpy.getCIDomain(2);
CIDomain domain3 = ciDomainServiceSpy.getCIDomain(1);
assertSame(domain1, domain3); //fails
}
My Domain Class:
#Entity
#Table(name = "CI_DOMAIN")
public class CIDomain implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
based on this post I understand that object is retrieved from the repository for the very first call and then later call will fetch this object from cache provided same "key" is provided. I am doing the same thing in my test case above but assertSame is failing. Spring cacheable must be caching object in memory which is fetched for a given request. Why would it send different objects everytime for the same requested key.
I have tried to have an alternative solution where I used spy on the service class and verify method calls based on a same key request. However, I encountered different issues in doing that. Creating a spy on the service class does not even use caching mechanism and it does call service getCIDomain method even if same key is provided. I followed this, this, this, this, this and lots of other posts for further analysis but could not get it working either through assertSame of spy.
Any help would really be appreciated.
I had got this issue resolved and was able to design the test case as it should be for verifying spring cacheable mechanism.
Just providing my analysis and resolution below to help someone out there facing this same issue.
I mentioned in my comments and original questions above that assertSame would not work due to how serialization works and assertEquals though was working but it was kind of not satisfying my test requirement.
The conclusion I made (based on comments) that I should actually test number of method calls and not the result. I tried to mock the CIDomainDAO repository dao as in my question but I faced with couple issues. Creating mocked object of CIDomainDAO and passing it in the CIDomainService constructor was not triggering spring cache and my test was failing. If I do not mock CIDomainDAO and tried spying on CIDomainService to check no of method calls and ran my test then I was ending up getting
org.mockito.exceptions.misusing.UnfinishedVerificationException: Missing
method call for verify(mock).
This was obvious as mocking does not seem to work on final methods that CIDomainDAO might have had in its spring generated JPARepository implementation.
This post helped me understand this behavior of mockito.
Concluding that I need to mock CIDomainDAO somehow, I ended up injecting mocked version of CIDomainDAO respository in CIDomainService class. I had to define a CIDomainDAO setter in CIDomainService class specially for this purpose. After that I tried no of method calls and it worked as expected i.e., service called two times but CIDomainDAO called once as the data was returned from the cache in the second call.
Provided below the modified classes from my original question above.
The service class:
#Service
public class CIDomainService {
private RedisTemplate<String, Object> redisTemplate;
private CIDomainDAO ciDomainDAO;
#Autowired
public CIDomainService(CIDomainDAO ciDomainDAO, RedisTemplate<String,
Object> redisTemplate) {
this.ciDomainDAO = ciDomainDAO;
this.redisTemplate = redisTemplate;
}
#Cacheable(value = "ciDomain", key = "#id")
public CIDomain getCIDomain(int id) {
CIDomain ciDomain = new CIDomain();
ciDomain.setId(id);
ciDomain.setName("SomeName");
return ciDomain;
}
public void clearAllCache() {
redisTemplate.delete("listCIDomains");
redisTemplate.delete("ciDomain");
}
public void setCIDomainDAO(CIDomainDAO ciDomainDAO ) {
this.ciDomainDAO = ciDomainDAO;
}
}
And this is the updated test case:
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles("local")
#SpringBootTest
public class CIDomainServiceIntegrationTest {
#Autowired
#InjectMocks
CIDomainService ciDomainService;
#Mock
CIDomainDAO ciDomainDAO;
#Before
public void setUp() {
Mockito.reset(ciDomainDAO);
ciDomainService.clearAllCache();
}
#Test
public void listCIDomains_ShouldNotAttemptToCallRepositoryWhenCachingEnabledAfterFirstCallOfRetrievingCIDomains() {
List<CIDomain> domains1 = ciDomainService.listCIDomains();
List<CIDomain> domains2 = ciDomainService.listCIDomains();
Mockito.verify(ciDomainDAO, Mockito.times(1)).findAll();
}
#Test
public void listCIDomains_ShouldAttemptToCallRepositoryWhenCachingIsClearedAfterFirstCallOfRetrievingCIDomains() {
List<CIDomain> domains1 = ciDomainService.listCIDomains();
ciDomainService.clearAllCache();
List<CIDomain> domains2 = ciDomainService.listCIDomains();
Mockito.verify(ciDomainDAO, Mockito.times(2)).findAll();
}
#After
public void postSetUp() {
Mockito.validateMockitoUsage();
ciDomainService.clearAllCache();
Mockito.reset(ciDomainDAO);
}
}

How to make cache data(on redis) to expire on basis of last access?

I have spring micro-service application which using redis-server for cache store.
Using RedisCacheManager Api. In this we have option to set "setDefaultExpiration". Because of that rediscachemanager calculating expiry from first access of annotated method(#cacheable).
I want to calculate expiry time from last access of cacheable method not from first access.
Google library has given direct method to achive this:
In CacheBuilder we have method called expireAfterAccess
CacheBuilder API
We can use this when we want to use google gauva server. But in my application I have to use redis server for cache because of my centralised cache server requirement.
I checked RedisCacheManager class and didn't find a way to achive this.
How I can achieve this feature in redis-cache-server.
Below code for creating RedisCacheManager bean:
#Bean
RedisCacheManager cacheManager() {
final RedisCacheManager redisCacheManager = new RedisCacheManager(
redisTemplate());
redisCacheManager.setUsePrefix(true);
redisCacheManager.setDefaultExpiration(redisExpireTime);
return redisCacheManager;
}
I solved this problem by customizing the cacheResolver , but it seems inefficient.
public class MyCacheResolver extends SimpleCacheResolver {
#Autowired
private RedisTemplate<Object, Object> template;
public CustomCacheResolver(CacheManager cacheManager) {
super(cacheManager);
}
#Override
public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
if (context.getOperation() instanceof CacheableOperation) {
DefaultParameterNameDiscoverer discover = new DefaultParameterNameDiscoverer();
CacheableOperation op = (CacheableOperation) context.getOperation();
String[] ps = discover.getParameterNames(context.getMethod());
EvaluationContext ctx = new StandardEvaluationContext();
for (int i = 0; i < ps.length; i++) {
ctx.setVariable(ps[i], context.getArgs()[i]);
}
ExpressionParser parser = new SpelExpressionParser();
String redisKey = parser.parseExpression(op.getKey()).getValue(ctx, String.class);
String prefix = getCacheNames(context).iterator().next();
long time = template.getExpire(prefix + ":" + redisKey);
if (time > 0) {
template.expire(prefix + ":" + redisKey, 300L, TimeUnit.SECONDS);
}
}
return super.resolveCaches(context);
}
}
in my case ,every cache hit will automatically refresh the cache ttl to 300 seconds.
then inject the cacheResolver to configuration class,
#Configuration
public class RedisConfig extends CachingConfigurerSupport {
#Bean("customCacheResolver")
#Override
public CacheResolver cacheResolver() {
return new CustomCacheResolver(cacheManager());
}
#Bean
public RedisCacheManager cacheManager() {
//define your cacheManager here
}
}
i hope it's clear,English is not my native language.

Resources