繁体   English   中英

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

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

我对 Cacheable 注释的除非条件有疑问。

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

首先,这个假设是真的吗?

编辑:

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

【我的理解】所以对于给定的key,方法会一直执行,直到unless条件不满足一次。 然后将为所有后续调用该方法返回缓存的值。

为了说明我的问题,我尝试将我的代码分解为四个简单的类:

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

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

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

4) 应用程序检索一个值(将被缓存),休眠一小段时间(不足以让缓存持续时间通过),检索一个值(应该是缓存的值),再次休眠(足够长要传递的缓存持续时间),再次检索一个值(应该是要缓存的新值)。

不幸的是,此代码无法按预期工作,因为检索到的值始终是已缓存的原始值。 为了确保逻辑按预期工作,我通过在应用程序 class 中将 retrieveTimestamp 替换为 retrieveTimestampBypass 来检查是否满足条件。 由于内部调用绕过 Spring 代理,retrieveTimestamp 方法和我输入的任何断点或日志实际上都被捕获/显示。

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

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

这里有很多细节,可能还有问题,但首先你应该删除

private long lastRetrieved;

来自 DummyDao class。 DummyDao 是 singleton 实例 lastRetrieved 字段不是线程安全的。

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

否则你应该看到以下日志

3000 < 1590064933733 = true

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

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

但是,这不是实际行为,因为正如文档所述,“@Cacheable 用于划分可缓存的方法 - 即将结果存储到缓存中的方法等后续调用(使用相同的参数),无需实际执行该方法即可返回缓存中的值。”

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

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


那是当我倾斜我无法根据内部时间戳上传缓存中的值否则。

每次执行该方法以计算最新值但返回缓存的值有什么意义(因为我设置了除非条件)? 无关紧要...

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


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

(*) PS:我还尝试了@CacheEvict(条件=“#root.target.myNewExpiringCheckMethod()==true”)和@Cacheable 的几个@caching 组合,但它失败了,并且 CacheEvict 强制执行注释方法。

暂无
暂无

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

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