Spring KeyGenerator For Appending Cache name to the key - spring

I am using spring cache with Redis for caching
I have the following methods:
#CachePut(value ="DATA1", key = "#key1")
public Object saveData1(long key1, Object obj) {
return obj;
}
#CachePut(value ="DATA2", key = "#key1")
public Object saveData2(long key1, Object obj) {
return obj;
}
This is causing collisions in keys and the data is being overridden.
I want to generate the key with the cache name appended to it.
Like: DATA1-key1, DATA2-key1.
Is it possible?
I have seen a few examples which use class name and method name. But I want to use the cache name.
Thank you.

Create a custom key generator like this:
#Component("myKeyGenerator")
public class MyKeyGenerator implements KeyGenerator {
public Object generate(Object target, Method method, Object... params) {
String[] value = new String[1];
long key;
CachePut cachePut = method.getAnnotation(CachePut.class);
if (cachePut != null) {
value = cachePut.value();
}
key = (long) params[0];
return value[0] + "-" + key;
}
}
And use it like below:
#CachePut(value = "DATA1", keyGenerator = "myKeyGenerator")
I haven't test this but should work, atleast you will get a basic idea how to do it.

You need to set parameter "usePrefix" as true in your CacheManager bean. This will prepend cacheName in your keys.
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
...
<property name="usePrefix"><value>true</value></property>
...
</bean>

Related

Mapping List of Object From Parent Object which has a List of Objects

I am trying to use mapstruct to transform an object as below
Source
MainObject
{
String key;
List<ChildObject> children;
}
ChildObject{
String childVar1;
String childVar2;
}
Target
List<TargetObj> targetObjects;
TargetObj{
String key;
String var1;
String var2;
}
I need to prepare a list of TargetObj instances with the key mapped from the key from MainObject and var1 and var2 mapped from ChildObject.
I tried to use ObjectFactory and Decorator as mentioned in the mapstruct documentation. But couldn't find a way to get this done. Both cases I got an error which states cannot return iterable object from non iterable parameters.
You can try and use a combination of #BeforeMapping or #AfterMapping with the #Context.
Your mapper can look like:
#Mapper
public interface MyMapper {
default List<TargetObj> map(MainObject source) {
if (source == null) {
return Collections.emptyList(); // or null or whatever you prefer
}
return map(source.getChildren(), new CustomContext(source));
}
List<TargetObject> map(List<ChildObject> children, #Context CustomContext context);
#Mapping(target = "key", ignore = true) // key is mapped in the context
TargetObject map(ChildObject child, #Context CustomContext context);
}
And the custom context would look something like:
public class CustomContext {
protected final MainObject mainObject;
public CustomContext(MainObject mainObject) {
this.mainObject = mainObject;
}
#AfterMapping // of #BeforeMapping
public void afterChild(#MappingTarget ChildObject child) {
child.setKey(mainObject.getKey());
// More complex mappings if needed
}
}
The goal is to do manual mapping from your MainObject to the List<TargetObj> by using other methods that MapStruct will generate

Evict not working in Spring boot

I have a method that fetches all the data and i am caching the result of that method but i am not able to evict the result.
#Component("cacheKeyGenerator")
public class CacheKeyGenerator implements KeyGenerator {
#Override
public Object generate(Object target, Method method, Object... params) {
final List<Object> key = new ArrayList<>();
key.add(method.getDeclaringClass().getName());
return key;
}
}
CachedMethod:-
#Override
#Cacheable(value="appCache",keyGenerator="cacheKeyGenerator")
public List<Contact> showAllContacts() {
return contactRepository.findAll();
}
#Override
#CachePut(value="appCache",key="#result.id")
public Contact addData(Contact contact) {
return contactRepository.save(contact);
}
Now when ever addData is called i want the data in the cache "appCache" with the key ="cacheKeyGenerator" to be evicted.So that the data returned by the method "showAllContacts()" is accurate.Can anyone please help!
The Entire code can be found at - https://github.com/iftekharkhan09/SpringCaching
Assuming you have a known constant cache key for showAllContacts then the solution should be to simply add #CacheEvict on addData passing in the cache name and key value:
#Override
#Caching(
put = {#CachePut(value="appCache", key="#result.id")},
evict = {#CacheEvict(cacheNames="appCache", key="someConstant")}
)
public Contact addData(Contact contact) {
return contactRepository.save(contact);
}
However because you use a key generator it is a bit more involved. Now given what your key generator does, you could instead pick a value for that cache key, making sure there can't be any collisions with the values from #result.id and use that value instead of a the key generator returned one.

How to create Spring Cache KeyGenerator that allows no caching for specified key

I just want to disable cache for users that are admins. So I write a method to generate keys as below that returns null for admins. But I get
java.lang.IllegalArgumentException: Null key returned for cache
operation
exeption.
Is there any way achieve that?
//a method that generates a menu for each user
#Cacheable(cacheNames = "topmenu", keyGenerator = "uiComponentKey")
#Override
public String renderResponse() {...}
//method used by a key generator to generate cache keys.
#Override
public Object getCacheKey() {
if (user.isAdmin()) {
return null;
}
return user.getUser().getLogin() + "#" + "topmenu";
}
I guess you can achive that using conditional caching feature. Smth like this:
#Cacheable(cacheNames = "topmenu", condition="#user.isAdmin()")
#Override
public String renderResponse(User user) {...}
Note, that you're going to have to pass user object to this method in this case.

Spring pageable cache key for each page

I am trying to do something like following
#Cacheable(value = ACTIVE_DATA_CONFIGURATION_CACHE, key = "#tenant.id.concat('-').concat(#pageable.page)")
public Page<DataConfiguration> findAllByTenant(final Pageable pageable, final Tenant tenant) {
}
exception
org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'page' cannot be found on object of type 'org.springframework.data.domain.PageRequest' - maybe not public?
at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:224)
As Pageable does not have a property page, if you are trying to generate a key as {tenant.id}-{pageNumber}, you can change the key value to #tenant.id.concat('-').concat(#pageable.pageNumber).
For your use case try using KeyGenerator
#Cacheable(value = "doOneThing", keyGenerator = "CustomGenerator")
Something like this:
public class CustomGenerator implements KeyGenerator {
public Object generate(Object target, Method method, Object... params) {
String code = "UNIQUE_CODE"; // implements logic from params
return code;
}
}

spring redis wrong result

I am using Spring Redis with the #Cacheable annotiation for two methods. When I call one method I am getting a result cached for the other method.
How can it happen that I get the result from the wrong cache while I configured a different cache for each method using the #Cachebale annotation?
Setup: Spring Version 4.1.6. Redis data 1.5 and Redis client 2.7.0.
Example code:
#Cacheable("test1")
public List<String> findSgsns() {
}
#Cacheable("test2")
public List<String> findSgsns2() {
}
The problem was sloved by adding following setting to spring configuration (set usePrefix):
<bean
id="cacheManager"
class="org.springframework.data.redis.cache.RedisCacheManager"
c:template-ref="redisTemplate">
<property name="usePrefix" value="true" />
</bean>
By default, Spring use SimpleKeyGenerator to generate the key if you don't specify it in the #Cacheable annotation.
public class SimpleKeyGenerator implements KeyGenerator {
#Override
public Object generate(Object target, Method method, Object... params)
{
return generateKey(params);
}
/**
* Generate a key based on the specified parameters.
*/
public static Object generateKey(Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
}
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}
}
As there is no method arguments in both of your methods (findSgsns() and findSgsns2()), it essentially will generate the same cache key for both methods.
You've already found a solution which basically utilize usePrefix property in the redisTemplate bean, which essentially add you value ( namely, "test1" and "test2") you specified in your #Cacheable annotation when it forms the cache key in Redis. I would like to mention 2 more alternatives for the sake of completeness here:
Specify your own key for each method (Note: you can use Spring EL to specify your keys):
#Cacheable(value = "test1", key = "key1")
public List<String> findSgsns() {
}
#Cacheable(value = "test2", key = "key2")
public List<String> findSgsns2() {
}
Build a custom key generator, and below is sample key generator which takes method name into redis cache key generation (Note: the custom key generator will take effect automatically by extending CachingConfigurerSupport class):
#Configuration
public class RedisConfig extends CachingConfigurerSupport {
#Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
#Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
}

Resources