2020. 3. 3. 16:30ㆍWeb/Spring
오늘 다루어볼 내용은 spring cache를 이용할때, 쉽게 놓쳐 실수 할 수 있는 @CacheEvict이다. @CacheEvict는 캐시 되어 있는 내용을 refresh, 정확히는 삭제하는 어노테이션인데 명시적으로 캐시 키값을 명시해주지 않으면 발생할 수 있는 실수 있다. 바로 예제를 살펴본다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
package com.example.cache;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@EnableCaching
@SpringBootApplication
@RestController
@RequiredArgsConstructor
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
private final CacheService cacheService;
@GetMapping("/")
public Mono<String> cacheTest(@RequestParam("id") String id) {
return Mono.just(cacheService.cacheTest(id));
}
@GetMapping("/refresh")
@CacheEvict(value = "testCache")
public Mono<String> cacheRefresh() {
return Mono.just("refresh");
}
@Component
public static class CacheService{
@Cacheable("testCache")
public String cacheTest(String id) {
return id+(Math.random()*100);
}
}
}
|
cs |
- 15Line - spring cache를 사용하겠다라는 어노테이션
- 40Line - testCache라는 value로 cacheTest 메서드의 결과를 캐시한다. 하지만 여기서 쉽게 지나칠수 있는 것은 cacheTest 메서드의 String id 값이 "cacheTest"라는 캐시의 키값이 되어 들어간다. 이말은 같은 "cacheTest" value이지만 키값에 따라 데이터가 여러건 캐시될 수 있다라는 뜻이다.
- 33Line - value가 "testCache"인 캐시를 삭제한다. 여기서 또한 쉽게 놓칠 수 있는 부분이 있다. 해당 메서드는 파라미터가 없는데, 이때 value = "testCache"이며 key가 0인 캐시 데이터를 지우게 된다.
바로 요청을 날려서 결과를 보자.
계속 날려도 같은 값이 온다. 그리고 refresh 요청을 보내보자.
이후에 다시 기존 api를 호출해보자.
똑같다. 이 말은 캐시가 삭제되지 않았다는 것이다. 왜그럴까? 이유는 "testCache"인 캐시에 key가 ab인 캐시가 생성되어 있는데, 캐시는 "testCache"인 key가 ab가 아닌 캐시를 삭제하려고 하고 있기 때문이다. 해결방법은 무엇일까? 그것은 캐시를 삭제하는 메서드에도 똑같이 key값을 가지는 매개변수를 받던가 혹은 @CacheEvict(value="testCache",key="ab")를 넣어준다. 하지만 수동으로 넣는 것은 유지보수 측면으로 좋지 않으니, 캐시를 삭제할때 key값을 메서드 매개변수로 받도록 한다.
그리고 한가지더 방법은 @CacheEvict(value="testCache", allEntries=true)로 지워주는 것도 방법이다. 이것은 key값 상관없이 해당 value로 들어간 모든 캐시데이터를 지우는 것이다.
방법1)
캐시를 지우는 메서드에 캐시를 생성하는 메서드와 같이 key값이 되는 매개변수를 넣어준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
package com.example.cache;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@EnableCaching
@SpringBootApplication
@RestController
@RequiredArgsConstructor
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
private final CacheService cacheService;
@GetMapping("/")
public Mono<String> cacheTest(@RequestParam("id") String id) {
return Mono.just(cacheService.cacheTest(id));
}
@GetMapping("/refresh")
@CacheEvict(value = "testCache")
public Mono<String> cacheRefresh(@RequestParam("id") String id) {
return Mono.just("refresh");
}
@Component
public static class CacheService{
@Cacheable("testCache")
public String cacheTest(String id) {
return id+(Math.random()*100);
}
}
}
|
cs |
방법2)
해당 value로 생성된 모든 캐시 엔트리를 지운다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
package com.example.cache;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@EnableCaching
@SpringBootApplication
@RestController
@RequiredArgsConstructor
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
private final CacheService cacheService;
@GetMapping("/")
public Mono<String> cacheTest(@RequestParam("id") String id) {
return Mono.just(cacheService.cacheTest(id));
}
@GetMapping("/refresh")
@CacheEvict(value = "testCache", allEntries = true)
public Mono<String> cacheRefresh() {
return Mono.just("refresh");
}
@Component
public static class CacheService{
@Cacheable("testCache")
public String cacheTest(String id) {
return id+(Math.random()*100);
}
}
}
|
cs |
여기까지 @CacheEvict를 사용할때 유의해야할 점이었다. 마지막으로 무거운 로직에 캐시를 무조건 해야한다는 생각은 버려야한다. 아무리 무거운 로직이라도 자주 변경되는 데이터라면 캐시하면 오히려 성능 저하로 이어진다. 올바르게 용도에 맞게 캐시를 사용하도록 하자!