Reputation: 546
We have spring-boot-starter-cache
added in our project and not using any particular implementation of cache provider. we are loading all the data during application startup by calling following method:
@Override
@Cacheable(cacheNames = "foos")
public List<FooDto> getAllFoo() {
return fooRepository.findAll().stream()
.map(FooEntityDomainToDtoMapper::mapDomainToDto) // mapping entity to dto
.collect(Collectors.toList());
}
//Want to implement something like:
public FooDto getFoo(Long id) {
//return single object from foos(which are cached in above method)
}
It is storing all foos
in the cache. And as expected next time when we are calling getAllFoo
, it is returning from the cache rather than returning from the database. Now next time when user request for individual object by id, we want to return it from this already cached foos
data rather than calling findById()
of JPA. Is there any way to achieve this?
Upvotes: 3
Views: 10290
Reputation: 1606
Cache the object by using the key so while retrieving from the cache you can use that key.
@Override
@Cacheable(value = "fooByIDCache", key = "#id", unless = "#result == null")
public FooDto getFooByID(String id, FooDto fooDTO) {
return fooDTO;
}
Upvotes: 0
Reputation: 7981
Is there any reason you want to, or need to, cache all Foos
in your application collectively rather than individually?
Keep in mind, it is by design that Spring's Cache Abstraction uses the method parameter(s) (if any) as the key and the return value as the value of the cache entry. If the method has no parameters then Spring will generate an id for you.
I have written about how to customize Spring's CacheManager
implementation to cache a Collection of values returned by a @Cacheable
method, individually.
However, for the moment, let's assume you do need/want to cache the entire List of Foos
.
Then, to create a method that pulls an individual Foo
by ID from the "cached" List of Foos
, you could, given your original cached method in a service class, do, for example...
@Sevice
class MyFooService {
private final FooRepository<Foo, Long> fooRepository;
@Cacheable(cacheNames = "foos")
public List<FooDto> getAllFoos() {
return this.fooRepository.findAll().stream()
.map(FooEntityDomainToDtoMapper::mapDomainToDto) // mapping entity to dto
.collect(Collectors.toList());
}
}
Then, in another application component, you could...
@Component
class MyFooAccessor {
private final MyFooService fooService;
MyFooAccessor(MyFooService fooService) {
this.fooService = fooService;
}
Optional<FooDto> getById(Long id) {
this.fooService.getAllFoos().stream()
.filter(fooDto -> fooDto.getId().equals(id))
.findFirst();
}
...
}
The MyFooAccessor
makes sure you do not circumvent the caching proxy (i.e. the AOP Proxy + Caching Advice around the MyFooService
applied by Spring). If the getById(..)
method were a member of the MyFooService
class, and called the getAllFoos()
method directly, you would circumvent the proxy and caching advice resulting in a database access each time.
NOTE: You could use Spring AOP Load Time Weaving (LTW) (see doc) to avoid circumventing the caching proxy if you want to keep the
getById(:Long)
method in theMyFooService
class with thegetAllFoos()
,@Cacheable
method. However...
Typically, you can solve these sort of problems by (re-)structuring your code appropriately, using the proper design pattern. This is not the only solution here, either. That is the beautiful thing about Spring is that it gives you many choices. This is just but 1 choice.
Hope this helps give you more ideas.
Upvotes: 4