简体   繁体   English

除非不满足条件,否则 Spring 缓存值不会被更新

[英]Spring cache value not being renewed when unless condition is not met

I'm having an issue with the unless condition of the Cacheable annotation.我对 Cacheable 注释的除非条件有疑问。

From the documentation, I understand that the unless condition is verified after the method being annotated is called and the value the method returns is cached (and actually returned) only if the unless condition is not met.从文档中,我了解到,在调用被注释的方法之后验证了除非条件,并且仅在不满足条件时才缓存(并实际返回)方法返回的值。 Otherwise, the cached value should be returned.否则,应该返回缓存的值。

Firstly, is this assumption true?首先,这个假设是真的吗?

EDIT:编辑:

[From the Spring documentation] As the name implies, @Cacheable is used to demarcate methods that are cacheable - that is, methods for whom the result is stored into the cache so on subsequent invocations (with the same arguments), the value in the cache is returned without having to actually execute the method. [来自 Spring 文档] 顾名思义,@Cacheable 用于划分可缓存的方法 - 即,在后续调用(使用相同的参数)时将结果存储到缓存中的方法,在无需实际执行该方法即可返回缓存。

[My understanding] So for a given key, the method will be always executed until the unless condition is not met once. 【我的理解】所以对于给定的key,方法会一直执行,直到unless条件不满足一次。 Then the cached value will be returned for all subsequent calls to the method.然后将为所有后续调用该方法返回缓存的值。

To illustrate my issue, I tried to break down my code into four simples classes:为了说明我的问题,我尝试将我的代码分解为四个简单的类:

1) DummyObject that represents instances to be cached and retrieved. 1) DummyObject,表示要缓存和检索的实例。 It's a timestamp wrapper to show what is last value that has been cached.它是一个时间戳包装器,用于显示缓存的最后一个值。 The toBeCached boolean is a flag that should be checked in the unless condition to know if the instance returned should be cached or not. toBeCached boolean 是一个标志,应该在除非条件中检查以了解返回的实例是否应该被缓存。

2) DummyDAO that returns DummyObject instances based on provided keys. 2) DummyDAO 根据提供的键返回 DummyObject 实例。 Upon retrieval of an instance, the DAO checks when was the last value retrieved and verified if it should be cached or not (independently of what key is provided. Doesn't matter if this logic is "broken" as I'm always using the same key for my example).在检索实例时,DAO 检查最后一次检索的值是什么时候,并验证它是否应该被缓存(与提供的键无关。这个逻辑是否“损坏”无关紧要,因为我总是使用对于我的示例,相同的键)。 The DAO then marks the instance returned with the flag toBeCached.然后 DAO 用 toBeCached 标志标记返回的实例。 If the value is marked to be cached, the DAO actually updates its lastRetrieved timestamp as the instance should be eventually cached by the CachedDAO (because the unless condition won't be met).如果该值被标记为缓存,则 DAO 实际上会更新其 lastRetrieved 时间戳,因为实例最终应由 CachedDAO 缓存(因为不会满足除非条件)。

3) DummyCachedDao that calls the DummyDAO to retrieve instances of DummyObject. 3) DummyCachedDao 调用 DummyDAO 来检索 DummyObject 的实例。 If instances are marked toBeCached, it should cache the newly returned value.如果实例被标记为BeCached,它应该缓存新返回的值。 It should return the previously cached value otherwise.否则它应该返回先前缓存的值。

4) The Application that retrieves a value (that will be cached), sleeps for a short time (not long enough for the cache duration to pass), retrieves a value (that should be the cached one), sleeps again (long enough for cache duration to pass), retrieves again a value (that should be a new value to be cached). 4) 应用程序检索一个值(将被缓存),休眠一小段时间(不足以让缓存持续时间通过),检索一个值(应该是缓存的值),再次休眠(足够长要传递的缓存持续时间),再次检索一个值(应该是要缓存的新值)。

Unfortunately, this code does not work as expected as the retrieved value is always the original value that has been cached.不幸的是,此代码无法按预期工作,因为检索到的值始终是已缓存的原始值。 To ensure that the logic worked as expected, I checked if the unless conditions are met or not by replacing the retrieveTimestamp by retrieveTimestampBypass in the Application class.为了确保逻辑按预期工作,我通过在应用程序 class 中将 retrieveTimestamp 替换为 retrieveTimestampBypass 来检查是否满足条件。 Since internal calls bypass the Spring proxy, the retrieveTimestamp method and whatever breakpoints or logs I put in are actually caught/shown.由于内部调用绕过 Spring 代理,retrieveTimestamp 方法和我输入的任何断点或日志实际上都被捕获/显示。

What would cause the value to never be cached again?什么会导致该值不再被缓存? Does the cache need to be clean from previous values first?缓存是否需要先从以前的值中清除?

public class DummyObject
{
  private long timestamp;
  private boolean toBeCached;

  public DummyObject(long timestamp, boolean toBeCached)
  {
    this.timestamp = timestamp;
    this.toBeCached = toBeCached;
  }

  public long getTimestamp()
  {
    return timestamp;
  }

  public boolean isToBeCached()
  {
    return toBeCached;
  }
}
@Service
public class DummyDAO
{
  private long cacheDuration = 3000;
  private long lastRetrieved;

  public DummyObject retrieveTimestamp(String key)
  {
    long renewalTime = lastRetrieved + cacheDuration;
    long time = System.currentTimeMillis();
    boolean markedToBeCached = renewalTime < time;

    System.out.println(renewalTime + " < " + time + " = " + markedToBeCached);

    if(markedToBeCached)
    {
      lastRetrieved = time;
    }

    return new DummyObject(time, markedToBeCached);
  }
}
@Service
public class DummyCachedDAO
{
    @Autowired
    private DummyDAO dao;

    // to check the flow.
    public DummyObject retrieveTimestampBypass(String key)
    {
        return retrieveTimestamp(key);
    }

    @Cacheable(cacheNames = "timestamps", unless = "#result.isToBeCached() != true")
    public DummyObject retrieveTimestamp(String key)
    {
      return dao.retrieveTimestamp(key); 
    }
}
@SpringBootApplication
@EnableCaching
public class Application
{
  public final static String KEY = "cache";
  public final static String MESSAGE = "Cached timestamp is: %s [%s]";

  public static void main(String[] args) throws InterruptedException
  {
    SpringApplication app = new SpringApplication(Application.class);
    ApplicationContext context = app.run(args);
    DummyCachedDAO cache = (DummyCachedDAO) context.getBean(DummyCachedDAO.class);

    // new value
    long value = cache.retrieveTimestamp(KEY).getTimestamp();
    System.out.println(String.format(MESSAGE, value, new Date(value)));

    Thread.sleep(1000);

    // expecting same value
    value = cache.retrieveTimestamp(KEY).getTimestamp();
    System.out.println(String.format(MESSAGE, value, new Date(value));

    Thread.sleep(5000);

    // expecting new value
    value = cache.retrieveTimestamp(KEY).getTimestamp();
    System.out.println(String.format(MESSAGE, value, new Date(value));

    SpringApplication.exit(context, () -> 0);
  }
}

There are so many details and maybe issues here but first of all you should remove这里有很多细节,可能还有问题,但首先你应该删除

private long lastRetrieved;

from DummyDao class.来自 DummyDao class。 DummyDao is a singleton instance lastRetrieved field is not thread safe. DummyDao 是 singleton 实例 lastRetrieved 字段不是线程安全的。

As you can also see from the logs after you cache the item first time it will always be retrieved from there as it has cached in the first call.正如您在第一次缓存项目后从日志中看到的那样,它将始终从那里检索,因为它已在第一次调用中缓存。

Otherwise you should have seen following log否则你应该看到以下日志

3000 < 1590064933733 = true

The problem is actually quite simple.这个问题其实很简单。 There is no solution to my problem and rightfully so.我的问题没有解决方案,这是理所当然的。

The original assumption I had was that " the unless condition is verified every time after the method being annotated is called and the value the method returns is cached (and actually returned) only if the unless condition is not met. Otherwise, the cached value should be returned. "我最初的假设是“每次调用被注释的方法后都会验证除非条件,并且只有在不满足条件时才缓存(并实际返回)方法返回的值。否则,缓存的值应该被退回。

However, this was not the actual behavior because as the documentation states, "@Cacheable is used to demarcate methods that are cacheable - that is, methods for whom the result is stored into the cache so on subsequent invocations (with the same arguments), the value in the cache is returned without having to actually execute the method."但是,这不是实际行为,因为正如文档所述,“@Cacheable 用于划分可缓存的方法 - 即将结果存储到缓存中的方法等后续调用(使用相同的参数),无需实际执行该方法即可返回缓存中的值。”

So for a given key, the method will be always executed until the unless condition is not met once.因此,对于给定的键,该方法将始终执行,直到一次不满足除非条件。 Then the cached value will be returned for all subsequent calls to the method.然后将为所有后续调用该方法返回缓存的值。

So I tried to approach the problem in a different way for my experiment, by trying to use a combination of annotations (@Caching with @Cacheable and @CachePut, although the documentation advises against it).因此,我尝试通过尝试使用注释组合(@Caching 与@Cacheable 和@CachePut,尽管文档建议不要这样做),以不同的方式为我的实验解决问题。 The value that I was retrieving was always the new one while the one in the cache was always the expected one.我检索的值始终是新值,而缓存中的值始终是预期值。 (*) (*)


That's when I tilted THAT I couldn't upload the value in the cache based on an internal timestamp that would have been generated in the method that is being cached AND retrieving at the same the cached value if the unless condition was met or the new one otherwise.那是当我倾斜我无法根据内部时间戳上传缓存中的值否则。

What would be the point of executing the method every single time to compute the latest value but returning the cached one (because of the unless condition I was setting)?每次执行该方法以计算最新值但返回缓存的值有什么意义(因为我设置了除非条件)? There is no point...无关紧要...

What I wanted to achieve (update the cache if a period expired) would have been possible if the condition of cacheable was to specify when to retrieve the cached version or retrieve/generate a new one.如果可缓存的条件是指定何时检索缓存版本或检索/生成新版本,我想要实现的目标(如果期限已过则更新缓存)是可能的。 As far as I am aware, the cacheable is only to specify when a method needs to be cached in the first place.据我所知,可缓存的只是指定何时需要首先缓存方法。


That is the end of my experiment.我的实验到此结束。 The need to test this arose when I came across an issue with an actual production project that used an internal timestamp with this unless condition.当我遇到一个实际生产项目的问题时,需要对此进行测试,该项目使用内部时间戳,除非条件。 FYI, the most obvious solution to this problem is to use a cache provider that actually provides TTL capabilities.仅供参考,这个问题最明显的解决方案是使用实际提供 TTL 功能的缓存提供程序。

(*) PS: I also tried few @caching combinations of @CacheEvict (with condition="#root.target.myNewExpiringCheckMethod()==true") and @Cacheable but it failed as well as the CacheEvict enforce the execution of the annotated method. (*) PS:我还尝试了@CacheEvict(条件=“#root.target.myNewExpiringCheckMethod()==true”)和@Cacheable 的几个@caching 组合,但它失败了,并且 CacheEvict 强制执行注释方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM