Spring @Service class 中的引导缓存不起作用
Spring boot caching in @Service class does not work
我在@Service 方法中保存一些值时遇到问题。
我的代码:
@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();
}
我的仓库class:
@Repository
public interface GlobalSettingsRepository extends CrudRepository<GlobalSettings, Settings> {
@Cacheable(value = "noTimeCache", key = "#setting.name()", unless="#result == null")
GlobalSettings findBySetting(Settings setting);
它应该像这样工作:
- 如果数据存在,从数据库中获取值,
- 如果不从枚举中保存值。
但它没有从数据库或枚举中保存任何数据。
我的缓存配置:
@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;
}
}
我有一些例子来确保缓存在我的项目中以 rest 方法工作:
@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);
而且效果很好。
我做错了什么?
问题出在您调用可缓存方法的地方。当你从同一个 class 调用你的 @Cacheable
方法时,你只是从 this
引用调用它,这意味着它没有被 Spring 的代理包装,所以 Spring 无法捕获你的调用来处理它。
解决此问题的一种方法是 @Autowired
为自身服务,并仅调用您期望 spring 必须通过此引用处理的方法:
@Service(value = "SettingsService")
public class SettingsService {
//...
@Autowired
private SettingsService settingsService;
//...
public String getGlobalSettingsValue(Settings setting) {
// ...
return settingsSerive.getGlobalEnumValue(setting)
//-----------------------^Look Here
}
@Cacheable(value = "noTimeCache", key = "#setting.name()")
public String getGlobalEnumValue(Settings setting) {
return Settings.valueOf(setting.name()).getDefaultValue();
}
}
但是如果你有这样的问题,说明你的class承担的太多了,不符合"single class - single responsibility"的原则。更好的解决方案是将带有 @Cacheable
的方法移动到专用的 class.
您的 SettingsService
中有两个方法,一个被缓存 (getGlobalEnumValue(...)
),另一个未被缓存但调用另一个方法 (getGlobalSettingsValue(...)
)。
然而,Spring 缓存抽象的工作方式是通过代理您的 class(使用 Spring AOP)。但是,对同一 class 内的方法的调用将不会调用代理逻辑,而是调用下面的直接业务逻辑。这意味着如果您在同一 bean 中调用方法,则缓存不起作用。
因此,如果您调用 getGlobalSettingsValue()
,它不会填充,也不会在该方法调用 getGlobalEnumValue(...)
时使用缓存。
可能的解决方案是:
- 使用代理时不在同一个 class 中调用另一个方法
- 同时缓存其他方法
- 使用 AspectJ 而不是 Spring AOP,它在编译时将代码直接编织到字节代码中,而不是代理 class。您可以通过设置
@EnableCaching(mode = AdviceMode.ASPECTJ)
来切换模式。但是,您也必须 set up load time weaving。
- 将服务自动连接到您的服务中,并使用该服务而不是直接调用该方法。通过自动装配服务,您将代理注入到您的服务中。
我在@Service 方法中保存一些值时遇到问题。 我的代码:
@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();
}
我的仓库class:
@Repository
public interface GlobalSettingsRepository extends CrudRepository<GlobalSettings, Settings> {
@Cacheable(value = "noTimeCache", key = "#setting.name()", unless="#result == null")
GlobalSettings findBySetting(Settings setting);
它应该像这样工作:
- 如果数据存在,从数据库中获取值,
- 如果不从枚举中保存值。
但它没有从数据库或枚举中保存任何数据。
我的缓存配置:
@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;
}
}
我有一些例子来确保缓存在我的项目中以 rest 方法工作:
@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);
而且效果很好。
我做错了什么?
问题出在您调用可缓存方法的地方。当你从同一个 class 调用你的 @Cacheable
方法时,你只是从 this
引用调用它,这意味着它没有被 Spring 的代理包装,所以 Spring 无法捕获你的调用来处理它。
解决此问题的一种方法是 @Autowired
为自身服务,并仅调用您期望 spring 必须通过此引用处理的方法:
@Service(value = "SettingsService")
public class SettingsService {
//...
@Autowired
private SettingsService settingsService;
//...
public String getGlobalSettingsValue(Settings setting) {
// ...
return settingsSerive.getGlobalEnumValue(setting)
//-----------------------^Look Here
}
@Cacheable(value = "noTimeCache", key = "#setting.name()")
public String getGlobalEnumValue(Settings setting) {
return Settings.valueOf(setting.name()).getDefaultValue();
}
}
但是如果你有这样的问题,说明你的class承担的太多了,不符合"single class - single responsibility"的原则。更好的解决方案是将带有 @Cacheable
的方法移动到专用的 class.
您的 SettingsService
中有两个方法,一个被缓存 (getGlobalEnumValue(...)
),另一个未被缓存但调用另一个方法 (getGlobalSettingsValue(...)
)。
然而,Spring 缓存抽象的工作方式是通过代理您的 class(使用 Spring AOP)。但是,对同一 class 内的方法的调用将不会调用代理逻辑,而是调用下面的直接业务逻辑。这意味着如果您在同一 bean 中调用方法,则缓存不起作用。
因此,如果您调用 getGlobalSettingsValue()
,它不会填充,也不会在该方法调用 getGlobalEnumValue(...)
时使用缓存。
可能的解决方案是:
- 使用代理时不在同一个 class 中调用另一个方法
- 同时缓存其他方法
- 使用 AspectJ 而不是 Spring AOP,它在编译时将代码直接编织到字节代码中,而不是代理 class。您可以通过设置
@EnableCaching(mode = AdviceMode.ASPECTJ)
来切换模式。但是,您也必须 set up load time weaving。 - 将服务自动连接到您的服务中,并使用该服务而不是直接调用该方法。通过自动装配服务,您将代理注入到您的服务中。