[SPRING] SpringCache를 통해 캐시 된 중첩 된 작업 캐싱
SPRINGSpringCache를 통해 캐시 된 중첩 된 작업 캐싱
DB 조회 횟수를 줄이기 위해 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;
}
};
}
}
}
두 가지 방법의 실제 작업은 캐싱을 테스트해야하기 때문에 테스트 케이스 자체에서는 중요하지 않습니다.
나는 어떻게 든 중첩 된 오퍼레이션의 결과가 캐쉬되지 않는 이유를 이해하지만, 중첩 된 캐쉬 가능한 오퍼레이션의 리턴 값에 캐싱을 사용 가능하게하기 위해 사용할 수있는 구성이 있는지 궁금하다.
리팩터링을 통해 중첩 된 작업의 반환 값을 외부 작업에 대한 인수로 제공한다는 것을 알고 있지만이 작업은 여러 가지 작업 (단위 테스트뿐 아니라)을 변경하기 위해 구성 또는 기타 해결 방법 (if if 사용 가능) 우리의 구체적인 경우에 바람직합니다.
해결법
-
==============================
1.문제는 methodB에서 직접 methodA에 액세스하고 있으므로 캐싱 메커니즘을 처리하는 Java 프록시를 통과하지 못하는 것입니다. 또한 @EnableCaching 주석을 추가하지 않았으므로 테스트에서 전혀 캐싱이 없었습니다.
문제는 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; } }; } } }
from https://stackoverflow.com/questions/29562642/caching-of-nested-cacheable-operation-via-springcache by cc-by-sa and MIT license
'SPRING' 카테고리의 다른 글
[SPRING] 메이븐 프로젝트에 대한 종속성으로 로컬 non-maven 프로젝트를 추가하는 방법은 무엇입니까? (0) | 2019.02.24 |
---|---|
[SPRING] 어떻게 스프링 부팅 응용 프로그램에 CSS와 JS를 추가할까요? (0) | 2019.02.24 |
[SPRING] @Transactional을 가진 @Service에 모의 삽입하는 법 (0) | 2019.02.24 |
[SPRING] JUnit / JPA / Hibernate / Struts 및 Spring 통합 테스트에서 세션 열기 유지 - 세션 또는 세션 종료 없음 - 지연 초기 화 예외 (0) | 2019.02.24 |
[SPRING] Tomcat 서버 시작 완료시 콜백 (0) | 2019.02.24 |