当前位置: 首页 > 知识库问答 >
问题:

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

纪成礼
2023-03-14

我对可缓存批注的条件有问题。

从文档中,我了解到除非条件在被注释的方法被调用后得到验证,并且只有在不满足除非条件的情况下,方法返回的值才会被缓存(并实际返回)。否则,应返回缓存的值。

首先,这个假设是否正确?

编辑:

[来自Spring文档]顾名思义,@Cacheable用于划分可缓存的方法-即,将结果存储到缓存中的方法,因此在后续调用(使用相同的参数)时,缓存中的值将返回,而不必实际执行该方法。

[我的理解]因此,对于给定的键,该方法将始终执行,直到一次未满足“除非”条件。然后,将为该方法的所有后续调用返回缓存值。

为了说明我的问题,我试图把我的代码分成四个简单的类:

1) DummyObject,表示要缓存和检索的实例。它是一个时间戳包装器,用于显示缓存的最后一个值。toBeCached布尔值是一个标志,应该在除非条件中进行检查,以了解返回的实例是否应该缓存。

2) DummyDAO,它根据提供的键返回DummyObject实例。在检索实例时,DAO会检查最后一个检索到的值是什么时候,并验证是否应该缓存它(与提供的键无关。如果这个逻辑被“破坏”并不重要,因为我总是在我的示例中使用相同的键)。然后,DAO将返回的实例标记为tobeccached。如果该值被标记为缓存,DAO实际上会更新其上次检索的时间戳,因为实例最终应该由CachedDAO缓存(因为将不满足除非条件)。

3) DummyCachedDao,它调用DummyDAO来检索DummyObject的实例。如果实例被标记为Ecached,它应该缓存新返回的值。否则,它应该返回以前缓存的值。

4) 检索值(将被缓存)、短时间睡眠(不足以让缓存持续时间通过)、检索值(应该是缓存的值)、再次睡眠(足以让缓存持续时间通过)、再次检索值(应该是要缓存的新值)的应用程序

不幸的是,此代码不能按预期工作,因为检索到的值总是已缓存的原始值。为了确保逻辑按预期工作,我检查了除非条件是否满足,方法是在Application类中用检索时间戳旁路替换检索时间戳。由于内部调用绕过了Spring代理,检索时间戳方法和我输入的任何断点或日志实际上都被捕获/显示。

什么会导致该值不再被缓存?缓存是否需要先清除以前的值?

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);
  }
}

共有2个答案

桂学
2023-03-14

问题其实很简单。我的问题没有解决办法,这是理所当然的。

我最初的假设是“在每次调用被注释的方法之后,都会验证除非条件,并且只有在不满足除非条件的情况下,才会缓存(并实际返回)该方法返回的值。否则,应该返回缓存的值。”

然而,这不是实际的行为,因为正如留档所述,“@Cacheable用于划分可缓存的方法——也就是说,结果存储到缓存中的方法,因此在随后的调用(具有相同的参数)中无需实际执行该方法即可返回缓存。"

因此,对于给定的键,该方法将始终执行,直到一次未满足除非条件。然后,将为该方法的所有后续调用返回缓存值。

因此,在我的实验中,我尝试以不同的方式处理这个问题,尝试使用注释的组合(@Caching with@Cacheable和@CachePut,尽管文档建议不要这样做)。我检索的值始终是新值,而缓存中的值始终是预期值。(*)

这时,我倾斜到无法根据内部时间戳上传缓存中的值,该时间戳将在正在缓存的方法中生成,并且在满足除非条件或新条件的情况下,以相同的速度检索缓存值。

每次执行该方法以计算最新的值,但返回缓存的值(因为我设置了“除非”条件),这有什么意义?没有意义。。。

如果cacheable的条件是指定何时检索缓存的版本或检索/生成新版本,那么我想要实现的目标(如果周期到期,则更新缓存)是可能的。据我所知,可缓存只是指定何时需要首先缓存方法。

我的实验到此结束。当我在实际生产项目中遇到一个问题时,需要对其进行测试,该问题使用了带有此条件的内部时间戳。仅供参考,此问题最明显的解决方案是使用实际提供TTL功能的缓存提供程序。

(*)PS:我还尝试了@cacheexecute(带有条件=“#root.target.myNewExpiringCheckMethod()==true”)和@Cacheable的几个@cachecaching组合,但失败了,因为cacheexecute强制执行带注释的方法。

左丘季
2023-03-14

有这么多的细节和可能的问题在这里但首先你应该删除

private long lastRetrieved;

来自DummyDao类。DummyDao是一个单实例,lastRetrieved字段不是线程安全的。

正如您在第一次缓存项目后从日志中看到的,它将始终从那里检索,就像它在第一次调用中缓存一样。

否则,您应该看到下面的日志

3000 < 1590064933733 = true
 类似资料:
  • 我在Springboot中开发了一个RESTendpoint,它接受ID,并用进行响应。此endpoint用注释标记。现在有两件事可以发生在给定的终点。 情况1:请求ID存在于DB中,并产生一个需要进行重定向的URL。在这种情况下,应该缓存响应,以便在相同ID的连续请求时,可以从缓存提供结果 情况2:请求的ID在DB中不存在,因此重定向应该发生在特定的URL,在此场景中不应该进行缓存。 如果我从这

  • 问题内容: 这是我无法弄清楚的非常基本的查询。 假设我有一个两列的表格,如下所示: 我想获取所有具有1、2和3的不同用户ID 。使用上面的示例,我要返回的唯一结果是1。我该怎么做? 问题答案: 任何人阅读本:我的答案是简单明了的,并得到了“接受”的地位,但请不要去阅读答案通过@cletus给出。它具有更好的性能。 只是大声思考一下,@ cletus所描述的编写自联接的另一种方法是: 这对您来说可能

  • 问题内容: 我想从这些数据结构中按值删除满足某些条件的元素 从上一个问题中,我找到了使用的简洁答案,但是似乎需要精确匹配。 在这里,我想从表或映射中删除其值小于特定阈值的元素。您编写代码的方式是什么? 我只检查了两个答案,这些是关于map的答案,表情况如何?我的解决方案如下。 问题答案: Iterator iterator = hmap.values().iterator(); while (it

  • 问题内容: 好的,所以我有一个监视线程,该线程检查ArrayList的大小,并在该大小大于某个数字之后执行一些操作。我现在遇到的问题是,除非我的循环中有打印语句,否则大小值永远不会更新。这是一些代码来显示我到底要做什么。 上面的代码不起作用。它永远不会进入if语句。但是,这很好用: 编辑:getSize()代码: 注意:我有另一个正在运行的线程正在更新并添加到我的t类中的列表中。 有什么帮助吗?当

  • 我有一个dict1,我想从中删除为空的所有项目,这不仅意味着属性,而且意味着整个字典。 输出应如下所示: 注意:字典中可以有 N 个项目和/或同一字典中的 N 个键值对。此外,字典中可能有 N 个具有空值的 ,因此必须删除所有 b。

  • 问题内容: 是否可以在MySQL中执行UPDATE查询,仅在满足特定条件时才更新字段值?像这样: 换一种说法: 正确的方法是什么? 问题答案: 是! 这里有另一个例子: 之所以可行,是因为MySQL不会更新该行(如果没有更改),如docs中所述: 如果将列设置为其当前值,MySQL会注意到这一点,并且不会对其进行更新。