JavaDesire
JavaDesire

Reputation: 129

Spring @Cacheable methods with lists

I'm using latest Ehcache in my Spring 4.1.4 application. What I have is:

class Contact{
    int id;
    int revision;
}    

@Cacheable("contacts")
public List<Contact> getContactList(List<Integer> contactIdList) {
    return namedJdbc.queryForList("select * from contact where id in (:idlist)", Collections.singletonMap("idlist", contactIdList));
}

@CachePut(value="contact", key = "id")
public void updateContact(Contact toUpdate) {
    jdbctemplate.update("update contact set revision = ? where id = ?", contact.getRevision(), contact.getId());
}

What I want to achieve is, that contacts are stored in the cache, and when I'm calling the getContactList method again, that all contacts whose id is already cached are retrieved from the cache and the other ones should be queried normally and then cached. This cache should then update the cached contact entity when it is updated.

I'm using plain Spring JDBC and Ehcache, no JPA and no Hibernate.

Upvotes: 12

Views: 7784

Answers (3)

Warlike
Warlike

Reputation: 101

I believe something like this should work without customizing the existing cache manager. Main idea is to query for cached contacts prior to make a DB query and then return joined list of cached and non-cached contacts. Note:

  1. No @Cacheable annotation on getContactList method anymore
  2. Additional synthetic method to check if contact is in cache.
  3. findContactFromCache method is called from within getContactList which will not work out of the box until you put it in a separate bean or use AspectJ/Self inject solutions.
public List<Contact> getContactList(List<Integer> contactIdList) {
    //the list below is created just to avoid original contactIdList to be modified
    var idList = new ArrayList<Integer>(contactIdList);
    var cachedContacts = new ArrayList<Integer>;
    var contactIterator = idList.iterator();
    while(contactIterator.hasNext()) {
      var contactId = iterator.next();
      var contact = findContactFromCache(contactId);
      if(contact == null) continue;
      contactIterator.remove();
      cachedContacts.add(contact);
    }
    var nonCachedContacts = namedJdbc.queryForList("select * from contact where id in (:idlist)", Collections.singletonMap("idlist", idList));
    return cachedContacts.addAll(nonCachedContacts);
}

@CachePut(value="contact", key = "id")
public void updateContact(Contact toUpdate) {
    jdbctemplate.update("update contact set revision = ? where id = ?", contact.getRevision(), contact.getId());
}

@Cacheable("contact")
public Contact findContactFromCache(int id) {
    return null;
}

Upvotes: 0

Koroslak
Koroslak

Reputation: 726

Worked for me. Here's a link to my answer. https://stackoverflow.com/a/60992530/2891027

TL:DR

@Cacheable(cacheNames = "test", key = "#p0")
public List<String> getTestFunction(List<String> someIds) {

more info about my enviroment in the answer.

Upvotes: 0

Wand Maker
Wand Maker

Reputation: 18762

Don't think that is possible. List<Integer> will be the key against the return value of getContactList will be saved in Cache.

So, unless the list of IDs that are being input to your getContactList contains exactly same IDs as in one of the previous calls, it will be a cache miss and data will be fetched from DB. (NOTE: Two lists are considered equal if they exactly contain same elements and in same order)

One option is to change your method getContactList(List<Integer> contactIdList) to getContact(Integer id) - in this case it may take a while to build cache, but once a Contact for a given ID is in cache, DB will not be used to re-fetch it in future calls.

Though not elegant, but another option is to do the caching manually in getContactList method.

Upvotes: 0

Related Questions