์•ˆ๋…•ํ•˜์„ธ์š”~ ์˜ค๋žœ๋งŒ์— ํฌ์ŠคํŒ…์„ ํ•˜๊ฒŒ๋˜๋„ค์š”.

์˜ค๋Š˜์€ ์Šคํ”„๋ง๋ถ€ํŠธ ๊ธฐ๋ฐ˜์˜ ํ”„๋กœ์ ํŠธ์—์„œ ์Šคํ”„๋ง์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ๊ณต์œ ๋“œ๋ฆฌ๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์Šคํ”„๋ง์บ์‹œ๋Š” ๋ ˆ๋””์Šค, ์นดํŽ˜์ธ, ehcache ๋“ฑ๊ณผ ์—ฐ๊ณ„ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋Š”๋ฐ์š”,

์ด๋ฒˆ  ํฌ์ŠคํŒ…์—์„œ๋Š” ehcache์™€ ์—ฐ๋™ํ•˜์—ฌ ์‚ฌ์šฉํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

์šฐ์„  ๊ธฐ๋ณธ์ ์ธ ๊ฐœ๋ฐœํ™˜๊ฒฝ์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • IntelliJ IDEA 2021.3.3 (Ultimate Edition)
  • SpringBoot 2.7.3
  • Java 18 (OpenJDK)

1. ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

์šฐ์„  ๋นˆ ๊นกํ†ต ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด ๋ณผ๊ฒŒ์š”.

์ด๋ฏธ ์‚ฌ์šฉํ•  ํ”„๋กœ์ ํŠธ๊ฐ€ ์žˆ๋‹ค๋ฉด ๊ฑด๋„ˆ๋›ฐ์…”๋„ ์ข‹์Šต๋‹ˆ๋‹ค :)

์ž๋ฐ” 18, ๊ทธ๋ž˜๋“ค ์„ ํƒ
์•„๋ฌด๋Ÿฐ ์˜์กด์„ฑ์„ ์„ ํƒํ•˜์ง€ ์•Š๊ณ  ๊นจ๋—ํ•œ ํ”„๋กœ์ ํŠธ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค
ํ”„๋กœ์ ํŠธ ๋นŒ๋“œ๊ฐ€ ์™„๋ฃŒ๋œ ํ›„ ํ”„๋กœ์ ํŠธ ํŠธ๋ฆฌ

์ž, ๋นˆ๊นกํ†ต ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

2. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€ 

์ด์ œ ๊ตฌํ˜„ ๋ฐ ํ…Œ์ŠคํŠธ์— ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์„ ์ถ”๊ฐ€ํ•ด๋ณผ๊ฒŒ์š”.

์šฐ์„  ํ˜„์žฌ build.gradle ํŒŒ์ผ์€ ์•„๋ž˜์™€ ๊ฐ™์ด ๋˜์–ด์žˆ์„๊ฑฐ์—์š”

plugins {
    id 'org.springframework.boot' version '2.7.3'
    id 'io.spring.dependency-management' version '1.0.13.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '18'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

์ €๊ธฐ์— ์•„๋ž˜ 5๊ฐœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์„ ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.

implementation 'org.ehcache:ehcache:3.10.1'
implementation 'org.springframework:spring-context-support:5.3.22'

implementation 'org.projectlombok:lombok:1.18.24'
annotationProcessor 'org.projectlombok:lombok:1.18.24'

implementation 'ch.qos.logback:logback-classic:1.2.11'

์™œ ์ € ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ํ•„์š”ํ•œ์ง€ ํ•˜๋‚˜์”ฉ ๋ณผ๊ฒŒ์š”.

implementation 'org.ehcache:ehcache:3.10.1' jcache ๊ตฌํ˜„์ฒด์ธ ehcache ์‚ฌ์šฉ์„ ์œ„ํ•จ
implementation 'org.springframework:spring-context-support:5.3.22' ์Šคํ”„๋ง์บ์‹œ์˜ ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•จ
implementation 'org.projectlombok:lombok:1.18.24'
annotationProcessor 'org.projectlombok:lombok:1.18.24'
lombok ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•จ
implementation 'ch.qos.logback:logback-classic:1.2.11' ๋กœ๊น…์„ ์œ„ํ•จ

์ด๋ ‡๊ฒŒ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•œ ๋’ค ์บ์‹œ์„ค์ •์„ ์ข€ ํ•ด์ฃผ๋„๋ก ํ• ๊ฒŒ์š”

3. ์บ์‹œ ์„ค์ •

com.example.demo.config ํŒจํ‚ค์ง€๋ฅผ ๋งŒ๋“  ๋’ค ์•„๋ž˜์™€ ๊ฐ™์ด CacheConfig ํด๋ž˜์Šค๋ฅผ ์ž‘์„ฑํ•ด์ค๋‹ˆ๋‹ค.

package com.example.demo.config;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.jcache.JCacheCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.cache.Cache;
import javax.cache.Caching;
import javax.cache.configuration.MutableConfiguration;
import javax.cache.expiry.CreatedExpiryPolicy;
import javax.cache.expiry.Duration;
import javax.cache.spi.CachingProvider;
import java.util.concurrent.TimeUnit;

@EnableCaching
@Configuration
public class CacheConfig {

    private final CachingProvider cachingProvider = Caching.getCachingProvider();
    private final javax.cache.CacheManager cacheManager = cachingProvider.getCacheManager();

    @Bean
    public CacheManager cacheManager() {
        return new JCacheCacheManager(cacheManager);
    }

    @Bean
    public Cache<Integer, Integer> commonCache() {
        MutableConfiguration<Integer, Integer> configuration =
                new MutableConfiguration<Integer, Integer>()
                        .setTypes(Integer.class, Integer.class)
                        .setStoreByValue(false)
                        .setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS, 5)));
        return cacheManager.createCache("commonCache", configuration);
    }
}

์œ„ ๋‚ด์šฉ์„ ๊ฐ„๋žตํžˆ ์‚ดํŽด๋ณด๋ฉด cacheManager ๋นˆ์„ ํ•˜๋‚˜ ๋“ฑ๋กํ•ด์ฃผ๊ณ  ๊ทธ ์บ์‹œ๋งค๋‹ˆ์ €์— commonCache๋ผ๋Š” ์ด๋ฆ„์˜ ์บ์‹œ ๋นˆ์„ ํ•˜๋‚˜ ๋“ฑ๋กํ•ด์ฃผ๋Š” ๊ฒ๋‹ˆ๋‹ค. ์ด commonCache๊ฐ€ key, value ํŽ˜์–ด๋ฅผ ์ €์žฅํ•˜๋Š” ํ•˜๋‚˜์˜ ์„œ๋ž์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜์‹œ๋ฉด๋˜๊ณ ์š”. ์—ฌ๊ธฐ์„œ๋Š” ํ‚ค์™€ ๊ฐ’์˜ ํƒ€์ž…์ด ๋ชจ๋‘ Integer ์ด๊ณ  ๋งŒ๋ฃŒ์ •์ฑ…์€ ์ƒ์„ฑ์‹œ๊ฐ„์„ ๊ธฐ์ค€์œผ๋กœ 5์ดˆ๋กœ ์„ค์ •ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

 

4. API ๊ตฌํ˜„ ๋ฐ ํ…Œ์ŠคํŠธ

์ด์ œ commonCache์— ๊ฐ’์„ ์ €์žฅํ•˜๊ณ  ๋งŒ๋ฃŒ๋˜๊ธฐ ์ „๊นŒ์ง€ ์บ์‹œ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์„œ๋น„์Šค ํด๋ž˜์Šค๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.

package com.example.demo.service;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class CacheService {

    @Cacheable(cacheNames = "commonCache", cacheManager = "cacheManager", key = "#code")
    public Integer getCachedValue(int code) {
        System.out.println("๊ณ„์‚ฐ์ค‘....");
        // DB ์กฐํšŒ ๋“ฑ์˜ ๋กœ์ง...
        return (int) (Math.random() * 10);
    }
}

com.example.demo.service ํŒจํ‚ค์ง€๋ฅผ ๋งŒ๋“ค๊ณ  CacheService ํด๋ž˜์Šค๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

getCachedValue ๋ฉ”์„œ๋“œ์— @Cacheable ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ์ฃผ๊ณ  commonCache๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  cacheManager๋Š” CacheConfig์— ๋“ฑ๋กํ–ˆ๋˜ cacheManager ์ด๋ฆ„์„ ์ ์–ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  key๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ๋ฐ›๋Š” code ๊ฐ’์„ ์‚ฌ์šฉํ•˜๊ฒ ๋‹ค๊ณ  ์„ ์–ธํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ key๋กœ ์–ด๋–ค ๊ฐ์ฒด์˜ ๋ฉค๋ฒ„๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค๋ฉด key = "#obj.employeeId" ์™€ ๊ฐ™์ด ์‚ฌ์šฉ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. (๊ณต์‹๋ฌธ์„œ ์ฐธ๊ณ )

CacheService.getCachedValue ๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด code ๊ฐ’์„ key๋กœํ•˜์—ฌ return๋œ Integer ๊ฐ’์„ commonCache์— ์ €์žฅํ•˜๊ฒŒ๋˜๊ณ  ๋งŒ๋ฃŒ์‹œ๊ฐ„์ธ 5์ดˆ ์ด๋‚ด์— ๋™์ผํ•œ key๊ฐ’(์—ฌ๊ธฐ์„œ๋Š” code ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’)์œผ๋กœ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์ด ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ํƒ€์ง€์•Š๊ณ  ๋ฐ”๋กœ ์บ์‹œ์— ์ €์žฅ๋˜์–ด์žˆ๋Š” ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ™•์ธํ•˜๊ธฐ์œ„ํ•ด ๊ณ„์‚ฐ์ค‘.... ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๋งˆ์ง€๋ง‰์œผ๋กœ ์œ„ ์„œ๋น„์Šค๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ํ…Œ์ŠคํŠธ API ๋ฅผ ๋งŒ๋“ค์–ด ์ค„๊ฒŒ์š”.

com.example.demo.controller ํŒจํ‚ค์ง€๋ฅผ ์‹ ๊ทœ์ถ”๊ฐ€ํ•˜๊ณ  TestController๋ฅผ ์ƒ์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

package com.example.demo.controller;

import com.example.demo.service.CacheService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/test")
@RequiredArgsConstructor
public class TestController {

    private final CacheService cacheService;

    @GetMapping("/cache")
    public Integer getCachedValue(int code) {
        log.info("Request accepted for code {}", code);
        return cacheService.getCachedValue(code);
    }
}

API ํ˜ธ์ถœ์ด ๋“ค์–ด์˜ฌ๋•Œ๋งˆ๋‹ค Request accepted for code X ๋กœ๊ทธ๊ฐ€ ์ถœ๋ ฅ๋˜๋„๋ก ํ•˜๊ณ , ์บ์‹œ์„œ๋น„์Šค์˜ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด์ œ DemoApplication์„ ์‹คํ–‰ํ•˜๊ณ  API๋ฅผ ํ˜ธ์ถœํ•ด๋ณผ๊ฒŒ์š”.

2022-09-14 12:32:59.783  INFO 47720 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 0.778 seconds (JVM running for 0.997)
2022-09-14 12:33:06.871  INFO 47720 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-09-14 12:33:06.871  INFO 47720 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-09-14 12:33:06.872  INFO 47720 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2022-09-14 12:33:06.882  INFO 47720 --- [nio-8080-exec-1] c.e.demo.controller.TestController       : Request accepted for code 1
๊ณ„์‚ฐ์ค‘....
2022-09-14 12:33:08.270  INFO 47720 --- [nio-8080-exec-2] c.e.demo.controller.TestController       : Request accepted for code 1
2022-09-14 12:33:09.186  INFO 47720 --- [nio-8080-exec-3] c.e.demo.controller.TestController       : Request accepted for code 1
2022-09-14 12:33:10.017  INFO 47720 --- [nio-8080-exec-4] c.e.demo.controller.TestController       : Request accepted for code 1
2022-09-14 12:33:10.880  INFO 47720 --- [nio-8080-exec-5] c.e.demo.controller.TestController       : Request accepted for code 1
2022-09-14 12:33:11.764  INFO 47720 --- [nio-8080-exec-6] c.e.demo.controller.TestController       : Request accepted for code 1
2022-09-14 12:33:12.631  INFO 47720 --- [nio-8080-exec-7] c.e.demo.controller.TestController       : Request accepted for code 1
๊ณ„์‚ฐ์ค‘....
2022-09-14 12:33:13.538  INFO 47720 --- [nio-8080-exec-8] c.e.demo.controller.TestController       : Request accepted for code 1
2022-09-14 12:33:14.408  INFO 47720 --- [nio-8080-exec-9] c.e.demo.controller.TestController       : Request accepted for code 1

ํ˜ธ์ถœ์€ ๋ชจ๋‘ code๊ฐ’ 1์„ ๋„ฃ์–ด์„œ ํ•˜์˜€๊ณ , ์ตœ์ดˆ ํ˜ธ์ถœ์„ 12:33:06.882 ์— ํ•˜๋ฉด์„œ ์„œ๋น„์Šค ๋‚ด๋ถ€๋กœ์ง์„ ์‹คํ–‰ํ•˜์—ฌ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜์˜€์Šต๋‹ˆ๋‹ค. (๊ณ„์‚ฐ์ค‘.... ์ถœ๋ ฅ์œผ๋กœ ํ™•์ธ)

์ดํ›„ 12:33:11.764 ๊นŒ์ง€๋Š” ๊ณ„์‚ฐ์ค‘.... ์ด ์ถœ๋ ฅ๋˜์ง€ ์•Š์€ ๊ฒƒ์œผ๋กœ ๋ณด์•„ ์บ์‹ฑ๋˜์–ด์žˆ๋Š” ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์‘๋‹ตํ•œ๊ฑธ๋กœ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5์ดˆ๊ฐ€ ์ง€๋‚œ 12:33:12.631 ์— ๋“ค์–ด์˜จ ํ˜ธ์ถœ๊ฑด์€ ๊ณ„์‚ฐ์ค‘.... ์ด ์ถœ๋ ฅ๋œ๊ฑธ๋กœ ๋ณด์•„ ์‘๋‹ต๊ฐ’์— ๋ณ€๋™์ด ๋ฐœ์ƒํ–ˆ์„ ๊ฒƒ์œผ๋กœ ์ถ”์ธก๋ฉ๋‹ˆ๋‹ค. (๋žœ๋ค๊ฐ’์œผ๋กœ ์‘๋‹ตํ•˜๋Š” ์„œ๋น„์Šค๋‹ˆ ์‘๋‹ต๊ฐ’์— ๋ณ€๋™์ด ์—†์„ ์ˆ˜๋„ ์žˆ์ฃ )

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฒƒ์€ ์‘๋‹ต๊ฐ’์— ๋ณ€๋™์ด ์žˆ๋Š๋ƒ๊ฐ€ ์•„๋‹ˆ๋ผ ์บ์‹œ๋˜์–ด์žˆ๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŒ๋ฃŒ๋˜๊ธฐ ์ „๊นŒ์ง€ ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€๋กœ์ง์„ ์‹คํ–‰ํ•˜์ง€ ์•Š๊ณ  ์บ์‹œ์—์„œ ์กฐํšŒ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฆฌํ„ดํ•ด์ค€๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค.

 

์ด์ƒ์œผ๋กœ 5๋ถ„๋งŒ์— ์Šคํ”„๋ง์บ์‹œ์™€ ehcache๋ฅผ ์ด์šฉํ•˜์—ฌ ์บ์‹œ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค.

 

์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค~  ๐Ÿ™‡