20

I have a service that takes in a DTO and returns some result:

@Override
public int foo(Bar bar) {
    ....
}

Bar is as follows (simplified):

public class Bar {
    public int id;
    public String name;
    public String baz;

    @Override
    public int hashCode() {
        //this is already being defined for something else
        ...
    }

    @Override
    public boolean equals(Object o) {
        //this is already being defined for something else
        ...
    }
}

I want to use @Cacheable on the foo method; however, I want to hash on the id and name properties, but not baz. Is there a way to do this?

Alex Beardsley
  • 20,988
  • 15
  • 52
  • 67
  • Does this answer your question? [@Cacheable key on multiple method arguments](https://stackoverflow.com/questions/14072380/cacheable-key-on-multiple-method-arguments) – Balázs Németh Jan 21 '21 at 11:08

7 Answers7

53

You can use this approach also

@Override
@Cacheable(key="{#bar.name, #bar.id}")
public int foo(Bar bar) {
    ....
}

It is suggested not to use hashcode as keys @Cacheable key on multiple method arguments

Community
  • 1
  • 1
vsingh
  • 6,365
  • 3
  • 53
  • 57
  • 2
    But when I use this approach it's throwing `java.lang.ClassCastException: Invalid key type, expected : org.springframework.cache.interceptor.SimpleKey but was : java.util.ArrayList`. How can I get over this? – Sanjay May 06 '21 at 08:09
20

Yes, you can specify using a Spring-EL expression along these lines:

@Override
@Cacheable(key="#bar.name.concat('-').concat(#bar.id)")
public int foo(Bar bar) {
    ....
}

or define a modified hashCode on bar and call that:

@Override
@Cacheable(key="#bar.hashCodeWithIdName")
public int foo(Bar bar) {
    ....
}
Biju Kunjummen
  • 49,138
  • 14
  • 112
  • 125
2

Both answers by @Biju and @vsingh are correct; but I would like to add one more alternative if the Bar object you are trying to cache is complex or the foo method contains a large amount of parameters using SpEL might not be the most ideal solution for generating the key.

Alternatively you may want to consider keyGenerator.

Example:

@Override
@Cacheable(value="barCahceKey", keyGenerator="barKeyGenerator")
public int foo(Bar bar) {
  ....
}

@Component
public class BarKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object o, Method method, Object... objects) {
      // TODO logic to generate unique key
      return "Bar_Key_Generator_With_Params_etc";
    }
}

With this approach you have the fully flexibility of how the key is constructed.

KeyGenerator API

Ithar
  • 4,865
  • 4
  • 39
  • 40
  • Custom keyGenerator allow to create only 1 key. What about having multipe keys from KeyGenerator? This KeyGenerator only return Object not array of object. So, I guess custom KeyGenerator cannot be used for multiple key generation. – Akanksha gore Jan 28 '22 at 06:22
2

You can use Spring SimpleKey class

@Cacheable(value = "barCache", key = "new org.springframework.cache.interceptor.SimpleKey(#bar.id, #bar.name)")
  • This works for but why does [@vsingh](https://stackoverflow.com/a/24193252/9079093) solution gives me `java.lang.ClassCastException: Invalid key type, expected : org.springframework.cache.interceptor.SimpleKey but was : java.util.ArrayList` exception? – Sanjay May 06 '21 at 14:35
  • But why does it require `key = "new org.springframework.cache.interceptor.SimpleKey(#bar.id, #bar.name)"` instead of `key = "{#bar.id, #bar.name}"` – Sanjay May 06 '21 at 14:59
0
  @Cacheable(value = "myCacheByNameUserCity", key = "T(java.util.Objects).hash(#name, #user, #city)")
  public User getUser(String name, String user,String city)

I propose this method with Object.hash, because "new org.springframework.cache.interceptor.SimpleKey (# bar.id, # bar.name)" returns toString () So it returns:

getClass().getSimpleName() + " [" + StringUtils.arrayToCommaDelimitedString(this.params) + "]";

So finally it returns "SimpleKey [bla,blaa, blalala]" not a unique code

Adam111p
  • 3,469
  • 1
  • 23
  • 18
0

Example for a KeyGenerator-implementation with multiple keys

public class MyKeyGenerator implements KeyGenerator {

  public Object generate(Object target, Method method, Object... params) {
    if (params.length > 2) {
        // only the first two, please! this was useful in my case.
        // omit or use like this, if (not) needed
        params = Arrays.copyOf(params, 2);
    }
    var key = String.format("mykey_%s", StringUtils.arrayToDelimitedString(params, "_")); 
    return key;
  }
}

In your CacheConfig you inject the bean like:

@Bean("myKeyGenerator") KeyGenerator myKeyGenerator() {
    return new MyCacheKeyGenerator();
}

And in your repository-class like:

// assuming you also defined a "myCache" in your cache-config!

@Cacheable(value = "myCache", keyGenerator = "myKeyGenerator", unless="#result == null")
public byte[] getMyData(int objectId, int objectType, boolean furtherParams, int willBeIgnored) {
}

@CacheEvict(value = "myCache", keyGenerator = "myKeyGenerator")
public void storeMyData(int id, int type, byte[] myData, int changingUserId) {
   // store here and do the same annotation for the delete-method
}
-1

The keys from the same Object, you can use object.hashCode(), so you don't need to specific keys one by one

@Override
@Cacheable(key="#bar.hashCode()")
public int foo(Bar bar) {
    ....
}

OR if you have an object and another key

@Override
@Cacheable(key="{#bar.hashCode(), #anotherKey}")
public int foo(Bar bar) {
    ....
}

I think this is a better solution.