1. 캐시 추상화?

이 튜토리얼에서는 Spring에서 Caching Abstraction사용하는 방법을 배우고 일반적으로 시스템 성능을 향상시킬 것입니다.

실제 메서드 예제에 대해 간단한 캐싱을 활성화하고 스마트 캐시 관리를 통해 이러한 호출의 성능을 실질적으로 개선 할 수있는 방법에 대해 논의 할 것입니다.

2. 시작하기

Spring에서 제공하는 핵심 캐싱 추상화는  spring-context  모듈에 있습니다. 따라서 Maven을 사용할 때 pom.xml  에는 다음 의존성이 포함되어야합니다.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.3</version>
</dependency>

흥미롭게도 spring-context-support 라는 또 다른 모듈이 있는데  , 이는 spring-context  모듈 위에 있으며 EhCache 또는 Caffeine 과 같은 지원을받는 몇 가지 CacheManagers를  더 제공합니다  . 이를 캐시 저장소로 사용하려면 대신 spring-context-support  모듈 을 사용해야합니다 .

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>5.3.3</version>
</dependency>

spring-context-support 모듈 은  spring-context 모듈에 전 이적으로 의존하기 때문에 spring-context  에 대한 별도의 의존성 선언이 필요하지 않습니다  .

2.1. Spring 부팅

Spring Boot를 사용하는 경우 spring-boot-starter-cache  스타터 패키지를 활용하여 캐싱 의존성을 쉽게 추가 할 수 있습니다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
    <version>2.4.0</version>
</dependency>

후드 아래에서 스타터는 spring-context-support  모듈을 가져옵니다  .

3. 캐싱 활성화

캐싱을 활성화하기 위해 Spring은 프레임 워크에서 다른 구성 수준 기능을 활성화하는 것과 마찬가지로 어노테이션을 잘 사용합니다.

구성 클래스에 @EnableCaching 어노테이션을 추가하기 만하면 캐싱 기능을 활성화 할 수 있습니다.

@Configuration
@EnableCaching
public class CachingConfig {

    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("addresses");
    }
}

물론 XML 구성으로 캐시 관리를 활성화 할 수도 있습니다.

<beans>
    <cache:annotation-driven />

    <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <bean 
                  class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" 
                  name="addresses"/>
            </set>
        </property>
    </bean>
</beans>

참고 : 캐싱을 활성화 한 후 최소 설정을 위해 cacheManager 를 등록해야합니다 .

3.1. Spring Boot 사용

Spring Boot를 사용할 때 EnableCaching  어노테이션 과 함께 클래스 경로에 스타터 패키지 만 있으면  동일한 ConcurrentMapCacheManager 가 등록  됩니다. 따라서 별도의 빈 선언이 필요하지 않습니다.

또한 하나 이상의 CacheManagerCustomizer <T>  Bean을 사용하여 자동 구성된  CacheManager  를  사용자 정의 할 수 있습니다 .

@Component
public class SimpleCacheCustomizer 
  implements CacheManagerCustomizer<ConcurrentMapCacheManager> {

    @Override
    public void customize(ConcurrentMapCacheManager cacheManager) {
        cacheManager.setCacheNames(asList("users", "transactions"));
    }
}

CacheAutoConfiguration  자동 구성 은 이러한  커 스터 마이저를 선택 하여 전체 초기화 전에 현재 CacheManager에  적용합니다 .

4. 어노테이션과 함께 캐싱 사용

캐싱을 활성화 한 후 다음 단계는 선언적 어노테이션을 사용하여 캐싱 동작을 메서드에 바인딩하는 것입니다.

4.1. @ 캐시 가능

메서드에 대한 캐싱 동작을 활성화하는 가장 간단한 방법은 @Cacheable 로 구분 하고 결과가 저장 될 캐시의 이름으로 매개 변수화하는 것입니다.

@Cacheable("addresses")
public String getAddress(Customer customer) {...}

getAddress에 () 호출은 먼저 캐시 확인합니다 주소를 실제로 메소드를 호출 한 후 결과를 캐싱하기 전에.

대부분의 경우 하나의 캐시로 충분하지만 Spring 프레임 워크는 매개 변수로 전달할 여러 캐시도 지원합니다.

@Cacheable({"addresses", "directory"})
public String getAddress(Customer customer) {...}

이 경우 캐시에 필요한 결과가 포함 된 경우 결과가 반환되고 메서드가 호출되지 않습니다.

4.2. @ CacheEvict

이제 모든 메소드를 @Cacheable 로 만드는 데 문제가 있습니까?

문제는 크기입니다. W 전자는 우리가 자주 필요가 없습니다 값으로 캐시를 채울 싶지 않아요 . 캐시는 상당히 커지고 빠르게 증가 할 수 있으며 오래되거나 사용되지 않는 많은 데이터를 보유 할 수 있습니다.

@CacheEvict 어노테이션을 사용하여 하나 이상의 / 모든 값을 제거하여 새 값을 캐시에 다시로드 할 수 있음을 나타낼 수 있습니다.

@CacheEvict(value="addresses", allEntries=true)
public String getAddress(Customer customer) {...}

여기서는 비울 캐시와 함께 allEntries 추가 매개 변수를 사용합니다 . 이렇게하면 캐시 주소 의 모든 항목이 지워지고 새 데이터를 준비합니다.

4.3. @ CachePut

@CacheEvict 는 오래된 항목과 사용하지 않는 항목을 제거하여 큰 캐시에서 항목을 찾는 오버 헤드를 줄이는 반면 , 캐시 에서 너무 많은 데이터 를 제거하지 않으려 고 합니다 .

대신 항목을 변경할 때마다 선택적으로 업데이트합니다.

으로 @CachePut의 어노테이션, 우리는 메소드 실행을 방해하지 않고 캐시의 내용을 업데이트 할 수 있습니다. 즉, 메서드가 항상 실행되고 결과가 캐시됩니다.

@CachePut(value="addresses")
public String getAddress(Customer customer) {...}

차이 @Cacheable@CachePut는 점이다 @Cacheable이 됩니다 방법을 실행 건너 반면, @CachePut는 것입니다 실제로이 방법을 실행 한 후 캐시에 그 결과를 넣습니다.

4.4. @ 캐싱

메서드 캐싱에 동일한 유형의 여러 어노테이션을 사용하려면 어떻게해야합니까? 잘못된 예를 살펴 보겠습니다.

@CacheEvict("addresses")
@CacheEvict(value="directory", key=customer.name)
public String getAddress(Customer customer) {...}

Java는 주어진 메소드에 대해 동일한 유형의 여러 어노테이션을 선언 할 수 없기 때문에 위 코드는 컴파일에 실패합니다.

위의 문제에 대한 해결 방법은 다음과 같습니다.

@Caching(evict = { 
  @CacheEvict("addresses"), 
  @CacheEvict(value="directory", key="#customer.name") })
public String getAddress(Customer customer) {...}

위의 코드 스 니펫에서 볼 수 있듯이 @Caching 을 사용 하여 여러 캐싱 어노테이션그룹화 하고이를 사용하여 자체 맞춤형 캐싱 로직을 구현할 수 있습니다.

4.5. @ CacheConfig

@CacheConfig 어노테이션을 사용하면 일부 캐시 구성을 클래스 수준의 단일 위치로 간소화 할 수 있으므로 여러 번 선언 할 필요가 없습니다.

@CacheConfig(cacheNames={"addresses"})
public class CustomerDataService {

    @Cacheable
    public String getAddress(Customer customer) {...}

5. 조건부 캐싱

경우에 따라 모든 상황에서 메서드에 대해 캐싱이 제대로 작동하지 않을 수 있습니다.

@CachePut 어노테이션 의 예제를 재사용 하면 메서드를 실행하고 매번 결과를 캐시합니다.

@CachePut(value="addresses")
public String getAddress(Customer customer) {...}

5.1. 조건 매개 변수

어노테이션이 활성 상태 일 때 더 많은 제어를 원하면 SpEL 표현식을 사용하는 조건 매개 변수로 @CachePut매개 변수화 하고 해당 표현식 평가에 따라 결과가 캐시되도록 할 수 있습니다.

@CachePut(value="addresses", condition="#customer.name=='Tom'")
public String getAddress(Customer customer) {...}

5.2. 매개 변수가 아닌 경우

또한 without 매개 변수 를 통한 입력이 아닌 메소드의 출력을 기반으로 캐싱을 제어 할 수 있습니다 .

@CachePut(value="addresses", unless="#result.length()<64")
public String getAddress(Customer customer) {...}

위의 어노테이션은 주소가 64 자 미만이 아닌 경우 주소를 캐시합니다.

모든 캐싱 어노테이션과 함께 조건비정상 매개 변수를 사용할 수 있다는 것을 아는 것이 중요합니다 .

이러한 종류의 조건부 캐싱은 큰 결과를 관리하는 데 매우 효과적 일 수 있습니다. 또한 모든 작업에 일반 동작을 적용하는 대신 입력 매개 변수를 기반으로 동작을 사용자 지정하는 데 유용합니다.

6. 선언적 XML 기반 캐싱

애플리케이션의 소스 코드에 액세스 할 수 없거나 캐싱 동작을 외부에 삽입하려는 경우 선언적 XML 기반 캐싱을 사용할 수도 있습니다.

다음은 XML 구성입니다.

<!-- the service that you wish to make cacheable -->
<bean id="customerDataService" 
  class="com.your.app.namespace.service.CustomerDataService"/>

<bean id="cacheManager" 
  class="org.springframework.cache.support.SimpleCacheManager"> 
    <property name="caches"> 
        <set> 
            <bean 
              class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" 
              name="directory"/> 
            <bean 
              class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" 
              name="addresses"/> 
        </set> 
    </property> 
</bean>
<!-- define caching behavior -->
<cache:advice id="cachingBehavior" cache-manager="cacheManager">
    <cache:caching cache="addresses">
        <cache:cacheable method="getAddress" key="#customer.name"/>
    </cache:caching>
</cache:advice>

<!-- apply the behavior to all the implementations of CustomerDataService interface->
<aop:config>
    <aop:advisor advice-ref="cachingBehavior"
      pointcut="execution(* com.your.app.namespace.service.CustomerDataService.*(..))"/>
</aop:config>

7. 자바 기반 캐싱

다음은 동등한 Java 구성입니다.

@Configuration
@EnableCaching
public class CachingConfig {

    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Arrays.asList(
          new ConcurrentMapCache("directory"), 
          new ConcurrentMapCache("addresses")));
        return cacheManager;
    }
}

그리고 다음은 CustomerDataService입니다 .

@Component
public class CustomerDataService {
 
    @Cacheable(value = "addresses", key = "#customer.name")
    public String getAddress(Customer customer) {
        return customer.getAddress();
    }
}

8. 요약

이 기사에서 우리는 Spring의 캐싱의 기초와 어노테이션으로 추상화를 잘 활용하는 방법에 대해 논의했다.

이 기사의 전체 구현은 GitHub 프로젝트 에서 찾을 수 있습니다 .