Spring Cache @Cacheable - 从同一个bean的另一个方法调用时不工作

Bal*_*ala 83 java spring caching ehcache

从同一个bean的另一个方法调用缓存方法时,Spring缓存不起作用.

这是一个以清晰的方式解释我的问题的例子.

组态:

<cache:annotation-driven cache-manager="myCacheManager" />

<bean id="myCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
    <property name="cacheManager" ref="myCache" />
</bean>

<!-- Ehcache library setup -->
<bean id="myCache"
    class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:shared="true">
    <property name="configLocation" value="classpath:ehcache.xml"></property>
</bean>

<cache name="employeeData" maxElementsInMemory="100"/>  
Run Code Online (Sandbox Code Playgroud)

缓存服务:

@Named("aService")
public class AService {

    @Cacheable("employeeData")
    public List<EmployeeData> getEmployeeData(Date date){
    ..println("Cache is not being used");
    ...
    }

    public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
        List<EmployeeData> employeeData = getEmployeeData(date);
        ...
    }

}
Run Code Online (Sandbox Code Playgroud)

结果:

aService.getEmployeeData(someDate);
output: Cache is not being used
aService.getEmployeeData(someDate); 
output: 
aService.getEmployeeEnrichedData(someDate); 
output: Cache is not being used
Run Code Online (Sandbox Code Playgroud)

getEmployeeData方法调用使用缓存employeeData在第二次调用预期.但是当getEmployeeDataAService类(in getEmployeeEnrichedData)中调用该方法时,不使用Cache.

这是弹簧缓存如何工作或我错过了什么?

Sha*_* D. 126

我相信这是它的工作原理.根据我记得的内容,生成了一个代理类,它拦截所有请求并使用缓存值进行响应,但同一类中的"内部"调用不会获得缓存值.

来自https://code.google.com/p/ehcache-spring-annotations/wiki/UsingCacheable

只拦截通过代理进入的外部方法调用.这意味着实际上,自调用目标对象中的一个方法调用目标对象的另一个方法,即使被调用的方法用@Cacheable标记,也不会在运行时导致实际的高速缓存拦截.

  • 此外,外部“@Cacheable”方法应该是“public”,它不适用于包私有方法。很难找到它。 (4认同)
  • 您还可以编写一个服务,例如CacheService,并将所有缓存方法放入服务中.在您需要的地方自动连接服务并调用方法.在我的情况下帮助. (2认同)

rad*_*tao 25

从Spring 4.3开始,问题可以通过注释上的自动自动装配来解决@Resource:

@Component
@CacheConfig(cacheNames = "SphereClientFactoryCache")
public class CacheableSphereClientFactoryImpl implements SphereClientFactory {

    /**
     * 1. Self-autowired reference to proxified bean of this class.
     */
    @Resource
    private SphereClientFactory self;

    @Override
    @Cacheable(sync = true)
    public SphereClient createSphereClient(@Nonnull TenantConfig tenantConfig) {
        // 2. call cached method using self-bean
        return self.createSphereClient(tenantConfig.getSphereClientConfig());
    }

    @Override
    @Cacheable(sync = true)
    public SphereClient createSphereClient(@Nonnull SphereClientConfig clientConfig) {
        return CtpClientConfigurationUtils.createSphereClient(clientConfig);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 它不会产生循环依赖吗? (8认同)
  • Spring 5 崩溃:“不鼓励依赖循环引用,并且默认情况下禁止它们。更新您的应用程序以消除 bean 之间的依赖循环。” (3认同)
  • 在 `4.3.17` 下试过这个,但没有用,对 `self` 的调用不通过代理并且缓存(仍然)被绕过。 (2认同)

mol*_*olm 17

下面的示例是我用来从同一个bean中命中代理的,它类似于@mario-eis的解决方案,但我发现它更具可读性(可能不是:-).无论如何,我喜欢在服务级别保留@Cacheable注释:

@Service
@Transactional(readOnly=true)
public class SettingServiceImpl implements SettingService {

@Inject
private SettingRepository settingRepository;

@Inject
private ApplicationContext applicationContext;

@Override
@Cacheable("settingsCache")
public String findValue(String name) {
    Setting setting = settingRepository.findOne(name);
    if(setting == null){
        return null;
    }
    return setting.getValue();
}

@Override
public Boolean findBoolean(String name) {
    String value = getSpringProxy().findValue(name);
    if (value == null) {
        return null;
    }
    return Boolean.valueOf(value);
}

/**
 * Use proxy to hit cache 
 */
private SettingService getSpringProxy() {
    return applicationContext.getBean(SettingService.class);
}
...
Run Code Online (Sandbox Code Playgroud)

另请参见在Spring bean中启动新事务

  • 是的,最好避免它,但我没有看到更好的解决方案来解决这个问题. (2认同)

小智 14

如果您从同一个 bean 调用缓存的方法,它将被视为私有方法,并且注释将被忽略


Mar*_*Eis 9

以下是我对同一类中只有少量方法调用的小项目所做的工作.强烈建议使用代码内文档,因为它可能会让同事感到惊讶.但它易于测试,简单,快速实现,并使我完全成熟的AspectJ仪器.但是,对于更重的用法,我建议使用AspectJ解决方案.

@Service
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class AService {

    private final AService _aService;

    @Autowired
    public AService(AService aService) {
        _aService = aService;
    }

    @Cacheable("employeeData")
    public List<EmployeeData> getEmployeeData(Date date){
        ..println("Cache is not being used");
        ...
    }

    public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
        List<EmployeeData> employeeData = _aService.getEmployeeData(date);
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这个答案是 /sf/answers/2386359531/ 的重复。 (2认同)

mcv*_*vkr 8

是的,由于其他帖子中已经提到的原因,缓存不会发生。不过,我会通过将该方法放入其自己的类(在本例中为服务)来解决问题。这样您的代码将更容易维护/测试和理解。

@Service // or @Named("aService")
public class AService {

    @Autowired //or how you inject your dependencies
    private EmployeeService employeeService;
 
    public List<EmployeeData> getEmployeeData(Date date){
          employeeService.getEmployeeData(date);
    }

    public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
        List<EmployeeData> employeeData = getEmployeeData(date);
        ...
    }

}
Run Code Online (Sandbox Code Playgroud)
@Service // or @Named("employeeService")
public class EmployeeService {

    @Cacheable("employeeData")
    public List<EmployeeData> getEmployeeData(Date date){
        println("This will be called only once for same date");
        ...
    }

}
Run Code Online (Sandbox Code Playgroud)