스프링부트 EhCache 2 사용 - 2

dragonappear·2022년 8월 16일
0

Cache

목록 보기
2/5

출처

제목: "[ SpringBoot ] SpringBoot의 기본 Cache 사용하기"
작성자: tistory(파미페럿)
작성자 수정일: 2021년7월24일
링크: https://pamyferret.tistory.com/8
작성일: 2022년8월16일

스프링부트 기본 Cache 설정

  • 원래는 캐시를 저장해야할 곳도 따로 마련해야하는데 스프링부트는 어플리케이션을 실행하면 해당 어플리케이션과 함께 살아있는 캐시 공간을 사용한다
  • 이 캐시 공간은 메모리를 차지하므로 많은 캐시를 저장하는 것에는 적합하지 않다
  • 또한 이 캐시 데이터는 어플리케이션이 죽으면 없어지는 캐시 데이터로, 어플리케이션이 죽어도 캐시 데이터가 사라지지 않고 죽은 어플리케이션을 다시 실행시키면 기존 캐시 데이터를 사용하게 하고 싶으면 따로 캐시 데이터를 저장하는 캐싱 서버를 두는 분산 캐싱을 해야 한다.

1. SpringBoot에 종속성 추가하기

gradle

implementation 'org.springframework.boot:spring-boot-starter-cache'

2. @EnableCacheing 설정

  • 캐싱에 필요한 종속들을 추가하면 이제 캐시와 관련된 어노테이션을 사용할 수 있다.
  • 캐싱 기능을 사용할 수프링부트 어플리케이션에 @EnableCahging 이라는 어노테이션을 달아준다
  • 이 어노테이션을 붙여주면 이제 해당 어플리케이션의 캐싱 기능은 활성화되어 DB에서 데이터를 읽어오는 부분에서 어노테이션을 사용해 캐싱 기능을 이용할 수 있다.

물론 이 @EnableCaching 어노테이션을 스프링부트 어플리케이션 부분에 붙이지 않고 따로 설정파일 역할을 하는 클래스에 생성해서 사용하거나, XML 파일에 별도로 캐싱기능을 사용할 부분에 대해 CacheManager 빈을 만들어 좀 더 세부적으로 설정할 수 있는 것 같지만 우선 이번에는 간단하게 캐싱 기능을 사용해보고 익히는 것이므로 그냥 스프링 어플리케이션에 어노테이션을 붙여 해당 어플리케이션에 캐싱 기능을 모두 활성화하는 것으로 하겠다.

어노테이션

@Cacheable: 캐시 데이터 생성 및 사용

위의 기본 설정을 끝냈으면 이제 스프링부트의 기본 캐싱 기능을 사용할 수 있다.

간단하게 계정 정보를 저장하는 테이블을 생성하였다.

AccountService에 모든 계정 조회 하는 메서드를 생성한다.

@Slf4j
@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class AccountService {
    private final AccountJpaRepository accountJpaRepository;
   
    public List<Account> getList() {
        return accountJpaRepository.findAll();
    }

위 메서드를 호출할때마다 select를 하는것을 확인할 수 있다.

@Cacheable 어노테이션은 처음에 캐시 데이터를 만들고, 그 다음부터는 DB에서가 아닌 캐시에서 데이터를 가져오도록 설정하는 어노테이션이다.

@Cacheable에는 여러 파라미터 옵션이 있지만, 일단 기본 사용법을 알아보자

	@Cacheable("account")
    public List<Account> getList() {
        return accountJpaRepository.findAll();
    }

    @Cacheable(value = "account",key = "#id")
    public Account get(Long id) {
        return accountJpaRepository.findById(id).orElseThrow(() -> new RuntimeException("조회 실패"));
    }
  • @Cacheable 어노테이션은 위와 같이 기본으로 텍스트 값을 넣어줘야하는데 그게 바로 캐시 데이터 저장 공간의 이름이 된다.

  • 물론 value, key 파라미터로 명시를 해줄 수 있다. value는 캐시 데이터의 이름이 되고 key는 캐시 데이터의 키 값이 된다.

  • get 에서는 key'#id'라고 지정을 해놨는데 이럴 경우 get(Long id)에서 받는 파라미터 id 값대로 캐시 데이터가 저장되어 추후 각 캐시 데이터를 key 값에 따라 부분 업데이트를 할 수 있다.

  • 즉, 아래와 같이 menu안에 key value와 같이 캐시 데이터가 저장되는 것이다.

  • 만일 키를 따로 설정을 안 하면 해당 메소드의 파라미터로 들어오는 값들로 키가 설정된다.

위와 같이 캐싱 기능을 사용하겠다고 설정을 하고 다시 여러 번 호출하면 아래와 같이 select는 처음 한 번만 이뤄지고 그 후에는 select가 이뤄지지 않는 것을 확인할 수 있다.

즉, @Cacheable 어노테이션으로 캐싱 기능을 사용하게 되었고, 이로 인해서 처음에는 DB에 가서 데이터를 읽어 'account'라는 이름으로 캐시를 저장하지만 그 후에는 DB에서 데이터를 읽지 않고 저장해놓은 캐시 데이터를 읽는 것이다. 이로 인해 DB로 가는 시간을 줄일 수 있게 되었다.

@CachePut: key 값에 따른 캐시 데이터 부분 업데이트

  • 만일 DB의 데이터가 업데이트되면 자동으로 캐시 데이터를 업데이트를 해주지는 않는다.

  • @Cacheable은 처음에 캐시에 데이터를 넣으면 그 후로 DB가 변경되든 말든 신경 쓰지 않고 저장된 캐시 데이터만 가져온다.

  • 위의 리스트를 가져왔던 캐시 데이터의 경우 부분 캐시 데이터 부분 업데이트보다는 캐시데이터를 전체 삭제해야하므로 @CachePut 예시에서는 계정 하나를 가져오는 아래 함수를 통해 캐시 데이터 부분 업데이트를 해본다.

    // 계정 업데이트 하기
    @Transactional
    @CachePut(value = "account", key = "#dto.id")
    public Account update(AccountUpdateDto dto) {
        Account account = accountJpaRepository.findById(dto.getId()).orElseThrow(() -> new RuntimeException("조회 실패"));
        account.changeName(dto.getName());
        return account;
    }
  • 위 코드를 보면 @CachePut(value = "account", key = "#dto.id")라고 어노테이션을 설정해놓은 것을 확인할 수 있다.

  • 여기서 value는 위에서 설명했듯이 캐시 데이터 이름이다. 위에서 @Cacheable'account'라고 설정을 해놨으니 똑같이 'account'라고 설정을 해줘야 거기서 데이터를 읽을 수 있다.

  • 여기서 중요한 것은 key 값인데 이 key 값이 위의 get(Long id)에서 id를 key 값으로 해서 캐시 데이터를 저장했으므로 update(AccountUpdateDto dto)에서도 똑같이 해당 id 값의 캐시를 업데이트 해야한다.

  • 그러면 key 값을 똑같이 id로 해야하는데 이번에는 파라미터를 Long 형식이 아닌 별도 지정한 DTO인 AccountUpdateDto로 받아온다.

  • 이와 같이 dto 안의 값을 key 값으로 사용해야할 경우에는 '#dto.id'와 같이 해당 'dto.${field}' 형식으로 작성을 해주면 된다.

참고:
key와 cache 어노테이션의 추가 파라미터인 condition(캐싱 조건)에 쓰는 문법은 spel 문법이다.
https://www.baeldung.com/spring-expression-language

가끔가다 보면 캐시 데이터를 업데이트 했는데 다시 캐시 데이터를 읽어보면 null 값이 나온다는 경우가 있다. 그것은 캐시 데이터를 업데이트 해주는 곳에서 업데이트 해줄 캐시 데이터를 return 하지 않기 때문이다.

// 메뉴 업데이트 하기
@CachePut(value = "menu", key = "#menuRequest.id")
public void updateMenu(MenuRequest menuRequest) {
	...	
}

즉, 위와 같이 함수의 반환형을 void로 해놓고 아무 데이터도 반환하지 않으면 캐시에 저장될 데이터 또한 없어 해당 key 값의 캐시 데이터는 null로 업데이트가 되어 버린다.

따라서 아래와 같이 굳이 반환할 필요가 없는 함수여도 캐시 데이터 저장을 위해 캐시 데이터에 저장해줄 값을 반환해야 한다.

// 메뉴 업데이트 하기
@CachePut(value = "menu", key = "#menuRequest.id")
public MenuList updateMenu(MenuRequest menuRequest) {
	....
		
	return menuList;
}

@CacheEvict: 해당 캐시 데이터 모두 지우기

  • 위에서 리스트를 반환하는 함수에 캐시 데이터를 적용 해놓으면 캐시 부분 업데이트가 힘들다고 했다. 그러면 어떻게 해야할까? 간단하다.
  • 캐시 데이터를 모두 지우고 refresh 하면 된다. 캐시 데이터를 지우는 기능 바로 그것을 하는 것이 @CacheEvict 어노테이션이 하는 역할이다.
// 메뉴 하나 삭제하기
	@CacheEvict(value = "menu", allEntries = true)
	public void deleteOneMenu(int id) {
		this.menuListRepository.deleteById(id);
	}
  • @CacheEvict 어노테이션을 보면 allEntries라고 눈에 띄는 파라미터가 있는데 이 파라미터를 true로 설정하면 어노테이션이 붙어 있는 메소드에서 어떤 파라미터를 받더라도 그냥 해당 캐시 데이터를 다 삭제해버린다.

  • 즉, 위에서 id 값을 파라미터로 받아 id를 key 값으로 저장해놓은 캐시 데이터를 구분할 수 있더라도 'allEntries = true'로 인해 'menu' 안의 모든 캐시 데이터가 지워지는 것이다.

  • 즉, @CacheEvict 어노테이션으로 인해 menu 캐시 데이터가 모두 지워져 다시 DB에서 데이터를 가져와 menu 캐시 데이터로 저장했다는 것을 알 수 있다.

  • 만일 위에서 캐시 데이터의 key 값을 id로 지정한 상황에서 해당 id 값의 캐시만 지우고 싶다면 아래와 같이 어노테이션을 사용하면 된다.

@CacheEvict(value = "menu", key = "#id")

그러면 해당 key 값에 대한 캐시 데이터만 삭제가 되고 menu 캐시에 있는 다른 데이터들은 남아 있다.

  • 만일 @CacheEvict에 key 파라미터와 allEntries 파라미터를 같이 사용할 경우에 에러는 발생하지 않지만 key 파라미터는 힘을 쓰지 못하고 allEntries만 힘을 발휘해 해당 캐시에 있는 모든 데이터를 다 삭제해버린다.
  • 즉, 위를 예를 들면 3번 메뉴만 삭제했지만 menu 캐시 데이터가 다 사라져 메뉴 리스트를 가져올 때 DB에 다시 갔다 온다는 것이다.

여러 개의 캐시 기능 및 value 사용하기

캐싱 기능을 사용하다보면 여러 개의 key 또는 캐시 기능을 사용해야할 때가 있다. 그럴 때는 어노테이션을 아래와 같이 작성해주면 된다.

@Caching(
	cacheable = {
			@Cacheable( value = {"name1", "name2"}, key = "key"),
			@Cacheable("name3")
	}
)

위와 같이 어노테이션을 작성하면 @Cacheable이 총 2개가 동작하게 되고 그 중 하나는 name1, name2에 각각 key라는 key 값을 가지게 캐시 데이터가 저장되고 name3에는 key 값을 별도 지정하지 않은 캐시 데이터가 저장된다.

0개의 댓글