【问题标题】:Spring Ehcache works only if method called in same requestSpring Ehcache 仅在同一请求中调用方法时才有效
【发布时间】:2018-01-21 08:53:22
【问题描述】:

我正在尝试缓存我的结果,但是对页面的每次调用(刷新)都会命中数据库。任何帮助表示赞赏,谢谢。

控制器

showPage(){
    MyPage<PhoneInfo> myPage = (MyPage<PhoneInfo>) phoneInfoService.findAllByOption(options, new MyPage<PhoneInfo>(page.intValue(), size.intValue()));
    //further experiment.. below does not hit db
    myPage = (MyPage<PhoneInfo>) phoneInfoService.findAllByOption(options, new MyPage<PhoneInfo>(page.intValue(), size.intValue()));
}

对服务方法的第二次调用没有进行 db 调用,这是正确的。 然而,页面刷新再次调用 showPage() 并且对服务的第一次调用再次命中 db。我希望它来自缓存。

我测试了 2 个单元测试,两个测试对服务方法的调用相同,测试 1 命中 db 而测试 2 跳过,这是正确的。测试中的多个服务调用也被跳过,这也是正确的。

规格

Spring 4.3.3.RELEASE、安全 4.2.3.RELEASE、Hibernate 5.2.8.Final、 Ehcache 2.10.4

缓存配置

@Configuration
@EnableCaching
public class CachingConfig {
    @Bean
    public CacheManager cacheManager(net.sf.ehcache.CacheManager cacheManager) {
        return new EhCacheCacheManager(cacheManager);
    }

    @Bean
    public EhCacheManagerFactoryBean ehCacheCacheManager() {
        EhCacheManagerFactoryBean cmfb = new EhCacheManagerFactoryBean();
        cmfb.setConfigLocation(new ClassPathResource("ehcache.xml"));
        cmfb.setShared(true);
        return cmfb;
    }
}

src/resources/ehcache.xml

<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   xsi:noNamespaceSchemaLocation="ehcache.xsd"     updateCheck="true"  monitoring="autodetect"     dynamicConfig="true">

    <diskStore path="java.io.tmpdir" />

    <cache name="myPageCache"       maxEntriesLocalHeap="1000"      maxEntriesLocalDisk="10000"         eternal="false"         diskSpoolBufferSizeMB="20"      timeToIdleSeconds="86400" timeToLiveSeconds="86400"         overflowToDisk="false"      memoryStoreEvictionPolicy="LFU"         transactionalMode="off">        <persistence strategy="localTempSwap" />    </cache>

</ehcache>

更新 1 下面列出服务方法

Cacheable(value = "myPageCache", key = "{#options, #pageable}")
public MyPage<PhoneInfo>findAllByOption(PhoneOption options, MyPage<PhoneInfo> pageable){
    MyPage<PhoneInfo> myPage = phoneInfoRepoImpl.findAll(options, pageable);
    myPage.setTotalRows(_phoneInfoService.getCount(options, pageable));
    return myPage;
}

@Cacheable(value = "myPageCache", key = "#options")
public int getCount(PhoneOption options, MyPage<PhoneInfo> pageable){
    return phoneInfoRepoImpl.getCount(options, pageable);
}

更新 2 单元测试

@Test
public void test1(){
    PhoneOption options = new PhoneOption();
    options.setName("239");
    options.setStatus('1');
    MyPage<PhoneInfo> myPage = phoneInfoService.findAllByOption(options, new MyPage<PhoneInfo>()); //hits db, Ok
    myPage = phoneInfoService.findAllByOption(options, new MyPage<PhoneInfo>()); //skips db, OK
    myPage = phoneInfoService.findAllByOption(options, new MyPage<PhoneInfo>(2, 20)); //hits db, OK as MyPage changed
}

@Test
public void test2(){
    PhoneOption options = new PhoneOption();
    options.setName("239");
    options.setStatus('1');
    MyPage<PhoneInfo> myPage = phoneInfoService.findAllByOption(options, new MyPage<PhoneInfo>()); //these options exactly same as Test1, skips db, OK!
    myPage = phoneInfoService.findAllByOption(options, new MyPage<PhoneInfo>()); //skips db, Ok
    myPage = phoneInfoService.findAllByOption(options, new MyPage<PhoneInfo>(2, 20)); //skips db, Ok
}

更新 3在所有测试对象中实现 hashCode 和 equals

现在在单元测试中,它一直在命中 db!所以行为变差了。 对于相等/相同的对象,hashcode 相同,equals 也可以正常工作,对于具有相似属性的对象返回 true。

【问题讨论】:

  • 假设您在 findAllByOption 方法上放置了@Cacheable 注解,那么它是否从缓存中选择取决于您传递的参数。如果它们对于每次页面刷新都相同,那么您肯定会从缓存中获取它。此外,您的缓存生存时间为 86400 毫秒。您的页面刷新调用频率是多少?
  • 嗨@Rakesh,是的,我在该方法上有Cacheable。在刷新期间传递完全相同的参数,对于每次页面刷新都相同,而不是从缓存中获取。为了测试,我会在几秒钟内刷新页面..也会发布服务方法
  • 我明白了,尽管您可能在选项对象中具有相同的值,但由于它始终是具有不同哈希键的新对象,它不会像您期望的那样工作。您需要有不同的密钥生成策略。
  • 你可以试试#options.field1 + #options.field2 ...

标签: hibernate spring-mvc spring-data ehcache


【解决方案1】:

构成键的元素需要定义正确的equalshashcode,这样即使最终得到不同的实例,相同的值也会导致正确的缓存命中。

【讨论】:

  • 实现了 equals & hascode,行为更糟糕,总是命中缓存.. 放入更新 3
  • 清理了 hashcode 和 equals,它帮助我朝着正确的方向前进,所以谢谢。
【解决方案2】:

您的密钥"{#options, #pageable}" 每次都将具有新值,即使您在这些对象中传递相同的值集,因为它们在每次页面刷新后都是全新的对象。因此,它们每次在第一次调用时都会被缓存。为避免这种情况,您需要更改密钥。尝试使用对象内的字段,例如 #object.field

【讨论】:

  • My Options 是一个有很多字段的类,例如 id、type、List 等,其中任何一个或全部都可以为空。在这种情况下如何生成密钥?尝试过#options.id + #options.code,但两者都是null,抛出错误'null'和'null'类型的对象之间不支持运算符'ADD'
  • Options.hashCode() 中混合所有字段是否有意义?那行得通吗?然后我可以使用#options 作为服务方法的关键吗?
  • 理想情况下,如果是这种情况,您不应该缓存您的结果,因为可以有多个组合,但如果您有一组固定的强制性字段,请使用它们来形成一个键。
  • Yes 选项实际上是一堆搜索选项,因此结果可能非常多样化;应用程序灵活且健壮,但对许多用户来说速度很慢,认为缓存会非常有益。强制性字段集需要是唯一的,因为它是关键,对吗?我的方案有更好的选择吗?
  • 为什么我说你不应该在你的场景中缓存,因为有多种组合,对于每个组合,你的输出都会被缓存,并且缓存大小可能很容易达到它的限制。正如您所说,它与搜索有关,Elasticsearch 可能是最合适的。
猜你喜欢
  • 2019-11-23
  • 2021-04-23
  • 1970-01-01
  • 2021-12-31
  • 2012-05-20
  • 1970-01-01
  • 1970-01-01
  • 2021-10-08
  • 2021-08-22
相关资源
最近更新 更多