Collection Support Utility for Spring Cache via AOP

Orkun Akile
4 min readSep 19, 2022

--

Spring Cache abstraction doesn’t support working with collection parameters in an iterative manner. Instead a scalar caching method can be created and a collection parameter can be iterated using that method. However, it may not be convenient for a method to be broken down into scalar caching methods. For instance, data is frequently retrieved in batches from a database or a network. For those cases, creating a scalar cache method doesn’t isolate the code from caching-specific logic and hence is not a good caching abstraction.

FlatCacheable is a Java utility to extend Spring Cache abstraction in order to support caching of collections in a feasible and flexible way. It can be found in this GitHub repository: https://github.com/deepend0/spring-cache-ext/.

Basically, it contains three annotations corresponding “flat” variants of Spring caching method annotations:

  • @FlatCacheable
  • @FlatCachePut
  • @FlatCacheEvict

Functionality underlying those annotations is implemented via aspect-oriented programming. Directly extending Spring cache module was also considered. However, Spring cache module doesn’t seem to be extendable in a neat way with an external involvement. For that, Spring AOP is used to implement.

“Flat” analogy in naming is preferred due to the fact that both collection parameter and its collection sub-fields are flattened on-the-fly and each element is cached individually. Besides, as an implementation note, flatMap operation in Java Streams has played a central role to flatten collections. Thus, as a representative one of the annotation names, FlatCacheable is selected as the module name as well.

Usage

First, the context configuration should be imported, caching and AspectJ proxies should be enabled. Then, you can annotate methods with @FlatCacheable, @FlatCachePut or @FlatCacheEvict annotations.

@FlatCacheable

@FlatCacheable does the computation via target method and caches the values from collection parameter only for non-existing elements in the cache.

Key and key-value mapping should be specified using hierarchical dotted field notation. Key should contain key parameter name in method signature with “#” sign (mimics SpEL expressions as for syntax) and then should specify sub-field names if any. Key-value mapping should be done via @FieldMapping. Mapping is required to join key and value objects and therefore to create a key-value map. @keyField is the join field on input parameter and @valueFieldis the one on result value. If a key has a single object to be mapped, then cardinality should be @MappingCardinality.SINGLE, otherwise @MappingCardinality.MULTIPLE. Fields are extracted from each element in the respective collection. If no sub-field is specified, key or value object itself is taken. If a sub-field is a collection then each value of it is taken for join.

@FlatCachePut

@FlatCachePutcomputes the value and then caches no matter whether values exist in the cache or not. It has the same set of parameters as those of @FlatCacheable. However, input parameter and key-value mapping are not mandatory. Keys also can be selected from value object. For that #result special parameter marker should be specified (mimics Spring Caching context variable in naming) and then no @FieldMapping is required.

@FlatCacheEvict

@FlatCacheEvict simply evicts values from the cache based on keys. Keys can be selected either from input parameter or returned value. If key is selected from input parameter, parameter name in method prototype should be specified. Otherwise #result marker should be used.

Example

Assume that we have regions, polygons and scooters for a scooter rental app. Relationship between these entities is one-to-many respectively. Data is stored inside an SQL DB and JPA is used for ORM. Hence Scooter, Polygon and Region entity classes are created. For demonstration purposes, one-to-many relationships bi-directionally implemented in Polygon class keeping both region and scooters inside.

Apart from entities, Spring JPA repository interfaces of Scooter and Region entities are created. Standard Spring caching annotations are used here along with flat caching annotations. Thus we can see the benefit of the collection support clearly.

@Cacheable-annotated methods return value for a scalar method parameter and value is mapped to that key in the cache, no matter if the result is a collection. On the other hand,@FlatCacheable-annotated methods do the caching for each element in collection parameter by mapping input objects and return values. For findByIdsIn method, key field for field mapping is not specified, because join key is the string value’s itself in ids list. @MappingCardinality.SINGLE is the cardinality for that since each key has a single value. As for @findByPolygon_IdIn and @findByPolygonsIn methods, those two only differ in the type of elements in collection parameter. The one for @findByPolygon_IdIn is StringString, and the other one is Polygon. Therefore, key field is empty for the former and id for the latter. Besides, mapping cardinality is @MappingCardinality.MULTIPLE, since each polygon has many polygons inside.

Methods @findByPolygon_Region_IdIn and @findByPolygon_Region_In are annotated with @FlatCachePut. Parameters have the same semantics as those of @FlatCacheable. Only that, in field mapping, joining through multiple level field hierarchy is demonstrated with value fields being polygon.region.id. Besides that findAll method exemplifies @FlatCachePut usage with #result parameter.

deleteAllByIdIn method is annotated with @FlatCacheEvict, meaning that values are evicted from the cache for each id value in list parameter.

RegionRepository has similar methods. However, it depicts key-value mappings from the other way around.

Summary

Flat-caching utility provides collection support for Spring caching abstraction. It is implemented using Spring AOP module. A key-value join mechanism is introduced to handle key-value mapping problem. Its effective usage is demonstrated with a JPA application. This module can be also improved to involve key-value mapper functions and object transformations for the future.

--

--