当前位置: 首页 > 面试题库 >

通过SpringCache缓存嵌套的可缓存操作

乌鸿宝
2023-03-14
问题内容

我的任务是利用SpringCache作为我们的一项服务,以减少数据库查找的次数。在测试实现时,我注意到一些可缓存操作通过日志语句多次调用。调查显示,如果在可缓存的方法中调用了可缓存的操作,则嵌套操作根本不会被缓存。因此,嵌套操作的后续调用将导致进一步的查找。

下面列出了描述问题的简单单元测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringCacheTest.Config.class} )
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class SpringCacheTest {

  private final static String CACHE_NAME = "testCache";
  private final static Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
  private final static AtomicInteger methodInvocations = new AtomicInteger(0);

  public interface ICacheableService {

    String methodA(int length);
    String methodB(String name);
  }

  @Resource
  private ICacheableService cache;

  @Test
  public void testNestedCaching() {

    String name = "test";
    cache.methodB(name);
    assertThat(methodInvocations.get(), is(equalTo(2)));

    cache.methodA(name.length());
    // should only be 2 as methodA for this length was already invoked before
    assertThat(methodInvocations.get(), is(equalTo(3)));
  }

  @Configuration
  public static class Config {

    @Bean
    public CacheManager getCacheManager() {
      SimpleCacheManager cacheManager = new SimpleCacheManager();
      cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache(CACHE_NAME)));
      return cacheManager;
    }

    @Bean
    public ICacheableService getMockedEntityService() {
      return new ICacheableService() {
        private final Random random = new Random();

        @Cacheable(value = CACHE_NAME, key = "#root.methodName.concat('_').concat(#p0)")
        public String methodA(int length) {
          methodInvocations.incrementAndGet();
          LOG.debug("Invoking methodA");
          char[] chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
          StringBuilder sb = new StringBuilder();
          for (int i=0; i<length; i++) {
            sb.append(chars[random.nextInt(chars.length)]);
          }
          String result = sb.toString();
          LOG.debug("Returning {} for length: {}", result, length);
          return result;
        }

        @Cacheable(value = CACHE_NAME, key = "#root.methodName.concat('_').concat(#p0)")
        public String methodB(String name) {
          methodInvocations.incrementAndGet();
          LOG.debug("Invoking methodB");

          String rand = methodA(name.length());
          String result = name+"_"+rand;
          LOG.debug("Returning {} for name: {}", result, name);
          return result;
        }
      };
    }
  }
}

这两种方法的实际工作对于测试用例本身并不重要,因为仅应测试缓存。

我以某种方式理解了为什么不缓存嵌套操作的结果的原因,但是我想知道是否有可用的配置(我尚未弄清楚)来启用对嵌套可缓存操作的返回值的缓存。

我知道通过重构并提供嵌套操作的返回值作为外部操作的参数将起作用,但是因为这可能涉及更改一些操作(以及对其进行单元测试),配置或其他解决方法(如果在我们的具体情况下更可取)。


问题答案:

问题在于您是methodA直接从中访问的methodB,因此这将阻止通过处理缓存机制的Java代理。此外,您没有添加@EnableCaching注释,因此测试中实际上根本没有缓存。

以下测试表明,如果您正确地检查了Spring创建的代理,则嵌套缓存模式将按预期工作:

import static org.junit.Assert.*;

import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { SpringCacheTest.Config.class })
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class SpringCacheTest {

    private final static String CACHE_NAME = "testCache";
    private final static AtomicInteger methodInvocations = new AtomicInteger(0);

    public interface ICacheableService {

        String methodA(int length);

        String methodB(String name);
    }

    @Resource
    private ICacheableService cache;

    @Test
    public void testNestedCaching() {

        String name = "test";
        cache.methodB(name);
        assertEquals(methodInvocations.get(), 2);

        cache.methodA(name.length());
        // should only be 2 as methodA for this length was already invoked before
        assertEquals(methodInvocations.get(), 2);
    }

    @Configuration
    @EnableCaching
    public static class Config {

        @Bean
        public CacheManager getCacheManager() {
            SimpleCacheManager cacheManager = new SimpleCacheManager();
            cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache(CACHE_NAME)));
            return cacheManager;
        }

        @Bean
        public ICacheableService getMockedEntityService() {
            return new ICacheableService() {
                private final Random random = new Random();

                @Autowired
                ApplicationContext context;

                @Override
                @Cacheable(value = CACHE_NAME, key = "#root.methodName.concat('_').concat(#p0)")
                public String methodA(int length) {
                    methodInvocations.incrementAndGet();
                    System.out.println("Invoking methodA");
                    char[] chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < length; i++) {
                        sb.append(chars[random.nextInt(chars.length)]);
                    }
                    String result = sb.toString();
                    System.out.println("Returning " + result + " for length: " + length);
                    return result;
                }

                @Override
                @Cacheable(value = CACHE_NAME, key = "#root.methodName.concat('_').concat(#p0)")
                public String methodB(String name) {
                    methodInvocations.incrementAndGet();
                    System.out.println("Invoking methodB");
                    ICacheableService cache = context.getBean(ICacheableService.class);
                    String rand = cache.methodA(name.length());
                    String result = name + "_" + rand;
                    System.out.println("Returning " + result + " for name: " + name);
                    return result;
                }
            };
        }
    }
}


 类似资料:
  • 我正在使用注释来缓存我的方法的结果。出于性能原因,我想缓存从方法返回的和非null值。 但是这里的问题是Spring缓存非空值,但由于某种原因没有缓存空值。 这是我的密码: 我什么都试过了。就连我 但这也没什么帮助。有关于这个的指示吗?

  • 我有一个Spring应用程序,它使用MyBatis进行持久化。我使用ehcache是因为速度对于这个应用程序很重要。我已经设置并配置了MyBatis和Ehcache。我使用一个名为“mybatis”的单一缓存,因为否则为每个实体创建单独的缓存将是荒谬的。 这是我的电子缓存。xml。 这是我的mybatis映射器界面的一个示例。 因为我有一个共享缓存,所以我需要一种方法使我的密钥对域对象是唯一的。作

  • 我在Spring3.1中使用@Cacheable。我对Cacheable中的值和键映射参数有点混淆。以下是我正在做的: 这里发生的情况是,第二个方法依赖于第一个方法的选定值,但问题是假设当我传递zoneMastNo=1和areaMastNo=1时,第二个方法返回第一个方法结果。事实上,我有很多服务,因此,我希望使用公共值来缓存特定的用例。现在我的问题是: 我如何解决这个问题 对每个服务都使用cac

  • 我有一个spring/hibernate项目,我试图通过ehcache和terracotta将二级缓存添加到hibernate。一切似乎都很好,我甚至可以在terracota控制台中看到我试图缓存的实体的条目。但根据数据库的统计数据和日志,根本没有缓存任何内容! 负载命中率是0%,负载统计也是0。我做错了什么? 这是我所做的,我通过maven添加了所需的罐子。 更改了我的Hibernate属性以启

  • 问题内容: 我们希望在生产部署中缓存崩溃,但不要浪费大量时间来弄清楚这样做的系统。我的想法是将具有当前版本号的paras应用于css和js文件的末尾: 两个问题:这会有效地打破缓存吗?由于参数表明这是动态内容,因此该参数会导致浏览器从不缓存该URL的响应吗? 问题答案: 参数表示查询字符串,因此浏览器将认为这是从到的新路径。因此导致它从文件而不是从缓存加载。如你所愿。 而且,浏览器将假定下次调用时

  • 当你使用本地(在内存中)缓存时,服务器可以缓存一些信息并快速地检索它,但是其他服务器不能访问这个缓存数据,他们需要到数据库中查询同样的信息。 如果你喜欢使用分布式缓存让其他服务器访问缓存的数据,由于它有一些序列化/反序列化和网络延迟开销,则需要注意:在某些情况下,它可能会降低性能。 缓存需要处理的另一个问题:缓存失效。 There are only two hard things in Compu