0

In my Java app that based on Spring Boot, I am trying to implement a caching mechanism for the following service method:

@Override
public List<EmployeeDTO> findAllByCountry(Country country) {
    final Map<Pair<UUID, String>, List<CountryTranslatable>> valueList 
        = countryRepository...
        // code omitted for brevity
}

After several examples regarding to this issue, I decided on the approach mentioned on A Guide To Caching in Spring.

However, I am a little bit confused as it contains Spring and Spring Boot implementations and uses different annotation examples. I think I should start from 3.1. Using Spring Boot section as I use Spring Boot, but I am not sure about which Caching Annotation I should use (4.1. @Cacheable seems to be ok but I am not sure).

So, where should I put SimpleCacheCustomizer and how can I apply that approach for my service method above (findAllByCountry)? Any simple example would really be appreciated as I am new in Spring.

Pilpo
  • 1,236
  • 1
  • 7
  • 18

3 Answers3

2

You don't need any customizations if you are a starter, and you want only the basics then do the following

@Configuration
@EnableCaching
public class CachingConfig {

    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager();
    }
}

The provided article states, return new ConcurrentMapCacheManager("addresses"); but you can use the default constructor and the relevant cache for adresses will be created later with @Cacheable("addresses"). So no need for this to be in configuration.

You also need

@Cacheable("employeesList")
@Override
public List<EmployeeDTO> findAllByCountry(Country country) {
    final Map<Pair<UUID, String>, List<CountryTranslatable>> valueList 
        = countryRepository...
        // code omitted for brevity
}

ready to go, that is the basic setup

Panagiotis Bougioukos
  • 15,955
  • 2
  • 30
  • 47
  • Thanks a lot for your good helpful answer, voted up. It is working perfectly, but why the example implementation does not work? It (`SimpleCacheCustomizer`) does not have `@Configuration` and `@EnableCaching` annotations, but there may not be a problem as it is on Baeldung. Any idea? –  Sep 13 '21 at 12:53
  • Maybe I missed some points on the example of Baeldung. Could you please have a look at https://github.com/eugenp/tutorials/tree/master/spring-caching/src/main/java/com/baeldung/caching/config and let me know how should I apply that example to my project? You may add that implementation as a **2nd method** to your answer. –  Sep 13 '21 at 13:03
  • Yes, I know and that's why I was confused. But as I might need the further implementation, I would be appreciated if you post the necessary part of that example (there is a config, etc that I did not use) –  Sep 13 '21 at 13:08
  • Thanks a lot amigo, I realized that I missed `CachingConfig` implementation on CachingConfig section. After implementing it, now it is working. But I do not use "addresses" in `ConcurrentMapCacheManager`. I think it is ok, is not it? –  Sep 13 '21 at 13:21
  • Just a question: Do I have to add `spring-context` and `spring-boot-starter-cache` dependencies to my project? I have `org.springframework` and `org.springframework.boot` in my `pom.xml`. –  Sep 13 '21 at 13:49
  • I agree with you, but it still working without `spring-boot-starter-cache`. Is that normal? Because there is nothing that has "cache" word in my `pom.xml` –  Sep 13 '21 at 13:54
  • you must inspect all your dependencies. somehow there are some dependencies that come from other projects which satiisfy your needs here – Panagiotis Bougioukos Sep 13 '21 at 13:55
  • There is a sentence on https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/html/boot-features-caching.html --> "*Use the spring-boot-starter-cache “Starter” to quickly add basic caching dependencies. The starter brings in spring-context-support. If you add dependencies manually, you must include spring-context-support in order to use the JCache, EhCache 2.x, or Guava support.*" –  Sep 13 '21 at 13:57
  • Then I think I should (at least further usages may need this dependencies). –  Sep 13 '21 at 13:58
  • Sorry, but I realized that `cacheManager.setCacheNames(asList("users", ));` uses `users`, but in the `@Cacheable` annotation it is using `addresses`. I am confused with this, because in our example we used `employeesList` in `cacheManager.setCacheNames(asList("employeesList" ));` So, is there any mistake in [that](https://www.baeldung.com/spring-cache-tutorial) example? –  Sep 13 '21 at 21:19
  • @Henry check my solution i dont have setCacheNames. You dont need it – Panagiotis Bougioukos Sep 13 '21 at 21:39
  • Thanks a lot, but I think setting cache names is useful as we may need to get them later. My question is much more related to that, the article uses `users` as name, but in `@Cacheable` annotation it is using addresses. Is there any mistake? –  Sep 14 '21 at 05:20
  • Yes it is a mistake. Check here why you should use the no args constructor `Construct a dynamic ConcurrentMapCacheManager, lazily creating cache instances as they are being requested.` https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/cache/concurrent/ConcurrentMapCacheManager.html So when you create it with no args constructor it works in dynamic mode and provides caches as they are requested. – Panagiotis Bougioukos Sep 14 '21 at 06:53
  • 1
    Then, can we say that I can use the related names in the following fields like that? `cacheManager.setCacheNames(asList("users"));` and `@Cacheable("users")`. –  Sep 14 '21 at 06:57
  • On the other hand, what about **setting TTL for @Cacheable**? I need to set expiration for `@Cacheable`, but not found a proper example (most of them are old and not suitable for the latest version :( –  Sep 14 '21 at 06:59
0

You should put your SimpleCacheCustomizer along with your others Spring configuration class. That way, your component will be scanned and loaded by Spring.

@Component
public class SimpleCacheCustomizer 
  implements CacheManagerCustomizer<ConcurrentMapCacheManager> {

    @Override
    public void customize(ConcurrentMapCacheManager cacheManager) {
        cacheManager.setCacheNames(asList("employeesList", "otherCacheName"));
    }
}

To use the cache with your service, add the annotation @Cacheable("employeesList")

@Cacheable("employeesList")
@Override
public List<EmployeeDTO> findAllByCountry(Country country) {
    final Map<Pair<UUID, String>, List<CountryTranslatable>> valueList 
        = countryRepository...
        // code omitted for brevity
}

If you want to verify the cache is working, just enable sql_query in your Spring configuration and check that findAllByCountry is no longer making any request to the DB.

Pilpo
  • 1,236
  • 1
  • 7
  • 18
  • **1.** Thanks a lot for your example. I think `employeesList` is a custom name and should be same in `SimpleCacheCustomizer` and in `@Cacheable` annotation of `findAllByCountry` method. Is that true? –  Sep 13 '21 at 10:52
  • **2.** I think I will add other cache fields to `cacheManager.setCacheNames(asList())` like `otherCacheName`. Is that true? –  Sep 13 '21 at 10:53
  • For **1.** Yes, you are right, `employeesList` is just the name for the cache. You can set anything you want but it has to be same in your `SimpleCacheCustomizer` than in your `@Cacheable` annotation. **2.** Yes, it was just an example to indicate to you that you can set, there, other cache name to use. – Pilpo Sep 13 '21 at 12:17
  • I tried this approach, but unfortunately it goes to db for each request. I tried to use `@Cacheable` annotation on my Service interface and it did not work. Then I also tried it on ServiceImpl class, but still the same. Any idea what I am missing? –  Sep 13 '21 at 12:43
  • Does your component `SimpleCacheCustomizer` is loaded by Spring? – Pilpo Sep 13 '21 at 12:46
  • I did not check it, how can I check? Shall I add a breakpoint in that class? –  Sep 13 '21 at 12:48
  • Yes, you can add a breakpoint in the method `SimpleCacheCustomizer.customize`. – Pilpo Sep 13 '21 at 12:50
  • I checked and it is not loaded. May it be related that `SimpleCacheCustomizer` does not have `@Configuration` and `@EnableCaching` annotations, but there may not be a problem as it is on Baeldung. Any idea? –  Sep 13 '21 at 12:57
  • Don't look further, your component is outside the range of the package scanning. Check this out for a more detailed component scanning explanation https://www.baeldung.com/spring-component-scanning. As for `@EnableCaching`, indeed, it has to be present somewhere on your code, otherwise, it'll not work. – Pilpo Sep 13 '21 at 13:04
  • Maybe I missed some points on the example of Baeldung. Could you please have a look at https://github.com/eugenp/tutorials/tree/master/spring-caching/src/main/java/com/baeldung/caching/config and let me know how should I apply that example to my project? You may add that implementation as a **2nd method** to your answer. –  Sep 13 '21 at 13:05
  • Thanks a lot amigo, **voted up**. I realized that I missed `CachingConfig` implementation on CachingConfig section. After implementing it, now it is working. But I do not use "addresses" in `ConcurrentMapCacheManager`. I think it is ok, is not it? –  Sep 13 '21 at 13:21
  • You're welcome. `addresses` is the name of your cache. If you don't use it, you can remove it safely from the `ConcurrentMapCacheManager`. – Pilpo Sep 13 '21 at 13:24
  • Do you mean that it is the general definition of the cache or `ConcurrentMapCacheManager`? –  Sep 13 '21 at 13:28
  • `addresses` is just an example for your cache name. You can use whatever you want. I don't know if that answer your question. – Pilpo Sep 13 '21 at 13:32
  • Sorry, but I realized that `cacheManager.setCacheNames(asList("users", ));` uses `users`, but in the `@Cacheable` annotation it is using `addresses`. I am confused with this, because in our example we used `employeesList` in `cacheManager.setCacheNames(asList("employeesList" ));` So, is there any mistake in [that](https://www.baeldung.com/spring-cache-tutorial) example? –  Sep 13 '21 at 21:19
  • On the other hand, what about **setting TTL for @Cacheable**? I need to set expiration for `@Cacheable`, but not found a proper example (most of them are old and not suitable for the latest version :( –  Sep 14 '21 at 06:59
  • TTL doesn't exist with the default caching implementation. You'll need to use other cache provider like Redis for example (https://www.baeldung.com/spring-boot-redis-cache). You could also use `@CacheEvict` to clear some caching info. – Pilpo Sep 14 '21 at 07:08
  • I tried using `@CacheEvict`, but not worked. Could you please post a proper example for my question on https://stackoverflow.com/questions/69169170/how-to-set-expiration-for-cacheable-in-spring-boot#comment122257568_69169170 ? –  Sep 14 '21 at 07:10
0

If you want to customize the autoconfigured cachemanager then only you should implement CacheManagerCustomizer interface.

In usual cases you don't need to customize the autoconfigured cachemanager. The example has been given in the link you attached.

Your understanding on the cacheable annotation is also correct and it should work fine for you.

You can put that component class with other classes in the component scan range.