0
@CachePut(cacheNames = "projectTeams", key = "#projectTeam.id")
public List<ProjectTeam> updateAll(List<ProjectTeam> projectTeam) {
   return projectTeamRepository.save(projectTeam);
}


@Cacheable(cacheNames = "projectTeams", key = "#id")
public List<ProjectTeam> findByIds(ListOfIds<Long> ids) {
log.info("get project team from db");
log.info("projectTeam id " + id);
return projectTeamRepository.findByIds(ids);
} 

I don't have any idea what i should put in key. Does spring cache supports SaveAll.

John Blum
  • 7,381
  • 1
  • 20
  • 30

1 Answers1

1

Spring uses a default key generation strategy to derive the key for the cache entry from the caching-enabled (@Cacheable or @CachePut annotated) service or repository methods based on the arguments to the method.

Of course, you can customize key generation used by any cache-enabled bean methods by 1) implementing the KeyGenerator interface and 2) declaring the KeyGenerator bean in your caching annotations, like so:

class ListKeyGenerator implements KeyGenerator {

  public Object generate(Object target, Method method, Object... params) {

    List<?> list = params[0];

    // use the list to generate a (unique) key for the cache entry
    Object generatedKey = ...;

    return generatedKey;
  }
}

NOTE: The target is the Spring managed bean containing your updateAll(..) and findByIds(..) methods. The Method ref is the cache-enabled method invoked, such as updateAll(..). And, the array of Object params are the arguments (e.g. the List) to the cache-enabled methods.

TIP: For simplicity and clarity (recommended), you may want to have 2 independent List-based KeyGenerators since 1 is a List of IDs (Long-typed; in the finder/query method) and the other is a List of ProjectTeam instances (in the update).

In configuration, you would declare:

@Configuration
class ApplicationCacheConfiguration {


  @Bean
  KeyGenerator listKeyGenerator() {
    return new ListKeyGenerator();
  }
}

You would apply the custom KeyGenerator using:

@Service
class ProjectTeamService {

  @CachePut(cacheNames = "projectTeams" keyGenerator = "listKeyGenerator")
  public List<ProjectTeam> updateAll(List<ProjectTeam> projectTeams) {
    // perform any preprocessing steps as necessary
    return this.projectTeamRepository.saveAll(projectTeams);
  }
}

NOTE: See corresponding Javadoc for @Cacheable and Javadoc for @CachePut.

However, you do not necessarily need a custom KeyGenerator if you simply rely on Spring's default key generation strategy as I mentioned to begin with.

Regarding the approach you chose, this will not work since it is trying to use a Spring SpEL expression to access a single element/item (i.e. a single ProjectTeam instance) in a List of ProjectTeam objects. Same applies for the List of IDs.

Which element from the List do you use? Do you use all elements (which would be difficult to aggregate using SpEL)? What happens if the List argument to the cache-enabled method is empty, or worse, null?

TIP: You should review Spring's SpEL capabilities for more details.

These sort of concerns are more appropriately handled in your custom KeyGenerator implementation.

You should also be aware that the cached entries for the cache-enabled methods of your Spring managed bean will be (using "default key generation"):

cache-enabled method          | cache key         | cache value
----------------------------------------------------------------------
findByIds(:List<Long>)        | List<Long>        | List<ProjectTeam>
updateAll(:List<ProjectTeam>) | List<ProjectTeam> | List<ProjectTeam>

If you are expecting the individual elements/items (e.g. either Long IDs or ProjectTeam instances) of the Lists to be broken out and cached individually in separate cache entries, then you are mistaken because this is NOT how Spring's Cache Abstraction works by default.

The reason I mention this is because it is sort of implied by your (attempted) caching configuration as declared in the annotations.

Spring takes the argument(s) to the cache-enabled method (e.g. @Cacheable annotated method) and uses that as a key for the cache entry. The return value of the method is used as the value. Since the cache-enabled methods take a List and return a List then the cache entry key is the List argument and the List return value is the cache entry value.

As you might imagine, with each different List of IDs or List of ProjectTeam instances requested, your cache memory space is going to fill up fast! That is because if the List in each request (cache-enable method) just differs by 1 element, then that is going to constitute a new cache entry in the cache. Depending on how many (different) requests (invocations) your application processes, and the frequency, this could lead to a serious problem (e.g. OOME)!

If you need individual cache entries for individually requested items, then you should have a look at this SO post from earlier.

Good luck!

John Blum
  • 7,381
  • 1
  • 20
  • 30
  • Looks like a misguided SO moderator (#sigh) deleted the answer to the "SO post from earlier" that I shared. Here are a few of the references I included in that post: 1) https://stackoverflow.com/questions/46330052/how-to-cache-the-list-of-entires-based-on-its-primary-key-using-spring-caching, 2) https://stackoverflow.com/questions/44529029/spring-cache-with-collection-of-items-entities, 3) https://stackoverflow.com/questions/41966690/putting-all-returned-elements-into-a-spring-boot-cache-using-annotations, and... – John Blum Mar 07 '23 at 18:32
  • 4) https://stackoverflow.com/questions/33657881/what-strategies-exist-for-using-spring-cache-on-methods-that-take-an-array-or-co – John Blum Mar 07 '23 at 18:32