Alex Beardsley
Alex Beardsley

Reputation: 21173

@Caching With Multiple Keys

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?

Upvotes: 22

Views: 74343

Answers (7)

Robert Fornesdale
Robert Fornesdale

Reputation: 80

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
}

Upvotes: 0

Adam111p
Adam111p

Reputation: 3717

  @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

Upvotes: 0

Abdelrahman Elattar
Abdelrahman Elattar

Reputation: 560

You can use Spring SimpleKey class

@Cacheable(value = "barCache", key = "new org.springframework.cache.interceptor.SimpleKey(#bar.id, #bar.name)")

Upvotes: 2

Rady Archuleta
Rady Archuleta

Reputation: 27

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.

Upvotes: -2

Ithar
Ithar

Reputation: 5425

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

Upvotes: 2

vsingh
vsingh

Reputation: 6749

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

Upvotes: 61

Biju Kunjummen
Biju Kunjummen

Reputation: 49915

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) {
    ....
}

Upvotes: 20

Related Questions