어 나 갱수.

[DB] Spring Boot에서 Redis Cache를 구현하기 😤 본문

DB

[DB] Spring Boot에서 Redis Cache를 구현하기 😤

김경수 2024. 7. 9. 10:20
728x90

프로젝트를 하면서 캐싱 기능을 추가해 보기로 했습니다. 
Spring에서는 @Cacheable, @CachePut, @CacheEvict 어노테이션을 통해서 캐시를 구현할 수 있습니다.

 

Spring에서 캐시를 사용하려면 캐싱 기능을 사용할 스프링부트 애플리케이션에@EnableCaching이라는 어노테이션을 붙여준다. 이 어노테이션을 붙여주면 이제 해당 애플리케이션에서 캐싱 기능을 활성화시켜서 캐시를 통해 데이터를 불러올 수 있습니다.

@SpringBootApplication
@EnableCaching
class V2Application{
	val log = LoggerFactory.getLogger(this::class.java.name)!!
	@PostConstruct
	fun timeZone() {
		TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul"))
		log.info("Server Started At {}", Date())
	}
}

fun main(args: Array<String>) {
	runApplication<V2Application>(*args)
}

 

@Cacheable

@Cacheable 어노테이션은 캐시 데이터를 생성하고 캐시에 데이터를 저장하는 역할을 합니다. 아래 함수가 실행되면 반환 객체인 
ListBoardResDto가 캐시에 저장됩니다.

@Cacheable(
    value = ["boardList"],
    key = "'boardList'",
    cacheManager = "contentCacheManager",
)
override fun execute(): ListBoardResDto {
    val boardList = boardRepository.findAllByOrderByCreatedDateDesc()
                .map { BoardResDto.of(it) }

    val listBoardResDto = ListBoardResDto(boardList)

    return listBoardResDto

}

캐시를 조회하면 아래와 같이 캐시가 조회됩니다.

@CachePut

@CachePut 어노테이션은 캐시를 업데이트해 주는 역할을 합니다. 캐시를 업데이트하지 않는다면 데이터베이스에 데이터가 변경되는 삭제되든 서버는 데이터베이스에서 데이터를 조회하는 것이 아니라 캐시를 통해 데이터를 조회합니다. 그러면 데이터 적합성 부분에서 문제가 발생할 수 있습니다. 그렇기 때문에 캐시를 수정해 주는 작업도 굉장히 중요한 작업입니다.

 

@CachePut어노테이션이 명시된 메서드를 실행하면 key 값에 해당하는 데이터를 삭제하고 새롭게 반환되는 객체인 ListBoardResDto를 캐시에 저장합니다.

@CachePut(
    value = ["boardList"],
    key = "'boardList'",
    cacheManager = "contentCacheManager"
)
override fun execute(modifyBoardReqDto: ModifyBoardReqDto, boardId: Long): ListBoardResDto {
    val boardInfo: Board = boardRepository.findByIdOrNull(boardId)
        ?: throw BoardNotExistsException()

    toDto(modifyBoardReqDto)
        .let { boardInfo.updateBoard(title = it.title, content = it.content) }

    updateBoardCache(boardInfo)

    val boardList = boardRepository.findAllByOrderByCreatedDateDesc()
        .map { BoardResDto.of(it) }

    return ListBoardResDto(boardList)

}

 

이렇게 공지글을 수정하는 API를 요청하면 수정된 객체를 반환하면서

해당 key에 대한 캐시를 수정해 줍니다.

@CacheEvict

@CacheEvict 어노테이션은 캐시에 있는 해당 key에 해당하는 캐시 데이터를 삭제하는 기능을 합니다.

아래 코드는 공지글을 삭제하는 메서드입니다. 

@CacheEvict(
    cacheNames = ["boardList"],
    key = "'boardList'",
    cacheManager = "contentCacheManager"
)
override fun execute(boardId: Long) {
    val boardInfo: Board = boardRepository.findByIdOrNull(boardId)
        ?: throw BoardNotExistsException()

    val boardImages: List<BoardImage> = boardImageRepository.findAllByBoard_Id(boardId)

    if (boardImages.isEmpty()) {
        boardRepository.delete(boardInfo)
    } else {
        for (boardImage in boardImages) {
            s3Service.deleteFile(boardImage.url.substring(54))
            boardImageRepository.delete(boardImage)
        }
        boardRepository.delete(boardInfo)
    }

}

캐시를 삭제하게 된다면 아래와 같이 캐시가 조회되지 않습니다.

Redis Template 사용

@CachePut 어노테이션을 통해서 캐시를 업데이트하려면 업데이트하는 메서드에서 꼭 업데이트된 객체를 반환해주어야 합니다.
그렇지만 캐시를 적용하기 전 원래 코드에서는 수정된 객체를 반환하지 않습니다. 굳이 반환하지 않아도 되는 객체라고 판단하고 개발을 진행했기 때문입니다. 그렇지만 @CachePut 어노테이션을 사용하려면 반환객체가 필요하기 때문에 반환 객체를 넣어주어야겠다고 생각을 했습니다. 그렇지만 다시 생각해 보니 @CachePut 어노테이션을 사용하기 위해서 굳이 필요하지 않은 객체를 반환하는 것은 리소스 낭비라는 생각이 들었기 때문에 메서드에서 객체를 반환시키는 것보다는 다른 방법을 생각해 봤고 그 결과 캐시를 저장하고 삭제하고 수정하는 작업을 Redis Template을 통해서 직접 구현하기로 결정하였습니다.

@Service
class RedisCacheService(
    private val redisTemplate: RedisTemplate<String, Any>
) {
    fun getFromCache(key: String): Any? {
        return redisTemplate.opsForValue().get(key)
    }

    fun putToCache(key: String, value: Any) {
        redisTemplate.opsForValue().set(key, value)
    }

    fun initCache(pattern: String) {
        val keys = redisTemplate.keys(pattern)
        if(keys.isNotEmpty()) {
            redisTemplate.delete(pattern)
        }
    }
}

 

위와 같이 직접 구현을 하니 불필요한 리소스를 소모하지 않고도 캐시를 구현할 수 있었습니다.

728x90

'DB' 카테고리의 다른 글

[DB] 낙관적 락(Optimistic Lock), 비관적 락(Pessimistic Lock)  (0) 2024.08.20
[DB] Redis Cache 🔥  (0) 2024.07.08
[DB] INNER 조인 & OUTER조인 🤚  (0) 2024.02.29
[DB] 정규화란 ? 🐼  (1) 2024.02.04
[DB] 인덱스(Index)란? 😛  (1) 2024.01.31