【问题标题】:Caching of nested cacheable operation via SpringCache通过 SpringCache 缓存嵌套的可缓存操作
【发布时间】:2015-06-16 05:51:22
【问题描述】:

我的任务是为我们的一项服务使用 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;
        }
      };
    }
  }
}

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

我以某种方式理解嵌套操作的结果未缓存的原因,但我想知道是否有可用的配置,我还没有弄清楚,以启用对嵌套可缓存操作的返回值的缓存。

我知道通过重构并提供嵌套操作的返回值作为外部操作的参数是可行的,但这可能涉及更改许多操作(以及对它们进行单元测试)配置或其他在我们的具体案例中,解决方法(如果可用)会更好。

【问题讨论】:

    标签: java spring caching


    【解决方案1】:

    问题是您直接从methodB 访问methodA,因此这会阻止通过处理缓存机制的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;
                    }
                };
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-19
      • 1970-01-01
      • 1970-01-01
      • 2017-05-29
      • 2013-02-23
      • 2020-10-25
      • 1970-01-01
      相关资源
      最近更新 更多