I have problems with save some values in @Service method. My code:
@Service(value = "SettingsService")
public class SettingsService {
...
public String getGlobalSettingsValue(Settings setting) {
getTotalEhCacheSize();
if(!setting.getGlobal()){
throw new IllegalStateException(setting.name() + " is not global setting");
}
GlobalSettings globalSettings = globalSettingsRepository.findBySetting(setting);
if(globalSettings != null)
return globalSettings.getValue();
else
return getGlobalEnumValue(setting)
}
@Cacheable(value = "noTimeCache", key = "#setting.name()")
public String getGlobalEnumValue(Settings setting) {
return Settings.valueOf(setting.name()).getDefaultValue();
}
My repository class:
@Repository
public interface GlobalSettingsRepository extends CrudRepository<GlobalSettings, Settings> {
@Cacheable(value = "noTimeCache", key = "#setting.name()", unless="#result == null")
GlobalSettings findBySetting(Settings setting);
It should work like this:
- get value form DB if data exist,
- if not save value from enum.
but it didn't save any data from DB or enum.
My cache config:
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public EhCacheCacheManager cacheManager(CacheManager cm) {
return new EhCacheCacheManager(cm);
}
@Bean
public EhCacheManagerFactoryBean ehcache() {
EhCacheManagerFactoryBean ehCacheManagerFactoryBean = new EhCacheManagerFactoryBean();
ehCacheManagerFactoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
return ehCacheManagerFactoryBean;
}
}
I have some example to make sure that cache is working in my project in rest method:
@RequestMapping(value = "/system/status", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> systemStatus() {
Object[] list = userPuzzleRepository.getAverageResponseByDateBetween(startDate, endDate);
...
}
public interface UserPuzzleRepository extends CrudRepository<UserPuzzle, Long> {
@Cacheable(value = "averageTimeAnswer", key = "#startDate")
@Query("select AVG(case when up.status='SUCCESS' OR up.status='FAILURE' OR up.status='TO_CHECK' then up.solvedTime else null end) from UserPuzzle up where up.solvedDate BETWEEN ?1 AND ?2")
Object[] getAverageResponseByDateBetween(Timestamp startDate, Timestamp endDate);
and it's work well.
What am I doing wwrong?
The problem is in the place you call your cacheable method from. When you call your
@Cacheable
method from same class, you just call it fromthis
reference, which means it doesn't wrapped by Spring's proxy, so Spring can't catch your invocation to handle it.One on ways to solve this problem is to
@Autowired
service to itself and just call methods you expected spring have to handle by this reference:But if you have such problems it means your classes are take on too much and aren't comply with the principle of "single class - single responsibility". The better solution would be to move method with
@Cacheable
to dedicated class.You have two methods in your
SettingsService
, one that is cached (getGlobalEnumValue(...)
) and another one that isn't cached, but calls the other method (getGlobalSettingsValue(...)
).The way the Spring cache abstraction works however is by proxying your class (using Spring AOP). However, calls to methods within the same class will not call the proxied logic, but the direct business logic beneath. This means caching does not work if you're calling methods in the same bean.
So, if you're calling
getGlobalSettingsValue()
, it will not populate, nor use the cache when that method callsgetGlobalEnumValue(...)
.The possible solutions are:
@EnableCaching(mode = AdviceMode.ASPECTJ)
. However, you'll have to set up load time weaving as well.