Skip to content

Commit

Permalink
[Feat/#187] Implement Caching for viewCount (#191)
Browse files Browse the repository at this point in the history
* [Feat/#187] Implement Caching for viewCount in Question

* [Feat/#187] Implement Caching for viewCount in Siren

* [Refactor/#187] Implement SirenCacheService

* [Feat/#187] Remove @Modifying and @transactional from QuestionRepository

* [Feat/#187] Update fixedRate in @scheduled

* [Refactor/#187] Extract cache operations to CacheService

* [Refactor/#187] Combine view count retrieval and increment logic

* [Feat/#187] Add CacheConfig for Redis caching

* [Refactor/#187] Remove useless log

* [Refactor/#187] Remove getData method in RedisService
  • Loading branch information
ahnsugyeong authored May 1, 2024
1 parent 2bbb961 commit 392be13
Show file tree
Hide file tree
Showing 18 changed files with 225 additions and 41 deletions.
5 changes: 3 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'com.h2database:h2'
Expand Down Expand Up @@ -72,6 +71,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'

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

// view
implementation 'org.springframework.boot:spring-boot-starter-freemarker'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
Expand All @@ -80,7 +82,6 @@ dependencies {
implementation 'org.webjars.bower:axios:0.17.1'
implementation 'com.google.code.gson:gson:2.8.0'


// WebSocket, Kafka, Stomp, MongoDB
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.webjars:sockjs-client:1.1.2'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.example.waggle.domain.board.question.repository;

import com.example.waggle.domain.board.question.entity.Question;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface QuestionRepository extends JpaRepository<Question, Long>, QuestionQueryRepository {

Expand All @@ -19,7 +20,10 @@ public interface QuestionRepository extends JpaRepository<Question, Long>, Quest

Page<Question> findPageByMemberId(Long memberId, Pageable pageable);

@Query("SELECT q.viewCount FROM Question q WHERE q.id = :boardId")
Long findViewCountByBoardId(@Param("boardId") Long boardId);

void deleteAllByMemberUsername(String username);
@Query("update Question q set q.viewCount = :viewCount where q.id = :boardId")
void applyViewCntToRDB(@Param("boardId") Long boardId, @Param("viewCount") Long viewCount);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.example.waggle.domain.board.question.service;

import com.example.waggle.domain.board.question.repository.QuestionRepository;
import com.example.waggle.domain.member.service.RedisService;
import java.util.Objects;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CachePut;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Transactional
@RequiredArgsConstructor
@Service
public class QuestionCacheService {

private final RedisService redisService;
private final QuestionRepository questionRepository;
private final String VIEW_COUNT_PREFIX = "viewCount::";
private final String BOARD_PREFIX = "board::";

@CachePut(value = "viewCounts", key = "#boardId")
public Long applyViewCountToRedis(Long boardId) {
String viewCountKey = VIEW_COUNT_PREFIX + boardId;
String currentViewCount = redisService.getValue(viewCountKey);
if (currentViewCount != null) {
return redisService.increment(viewCountKey);
} else {
Long initialViewCount = questionRepository.findViewCountByBoardId(boardId);
return initialViewCount + 1;
}
}

@Scheduled(fixedRate = 1000 * 60 * 3)
public void applyViewCountToRDB() {
Set<String> viewCountKeys = redisService.getKeysByPattern(VIEW_COUNT_PREFIX + "*");
if (Objects.requireNonNull(viewCountKeys).isEmpty()) {
return;
}

for (String viewCntKey : viewCountKeys) {
Long boardId = extractBoardIdFromKey(viewCntKey);
Long viewCount = Long.parseLong(redisService.getValue(viewCntKey));
questionRepository.applyViewCntToRDB(boardId, viewCount);
redisService.deleteData(viewCntKey);
redisService.deleteData(BOARD_PREFIX + boardId);
}
}

private static Long extractBoardIdFromKey(String key) {
return Long.parseLong(key.split("::")[1]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,4 @@ Long updateQuestion(Long boardId,

void deleteQuestion(Long boardId, Member member);

void increaseQuestionViewCount(Long boardId);

}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.example.waggle.domain.board.question.service;

import static com.example.waggle.domain.board.service.BoardType.QUESTION;

import com.example.waggle.domain.board.ResolutionStatus;
import com.example.waggle.domain.board.question.entity.Question;
import com.example.waggle.domain.board.question.repository.QuestionRepository;
Expand All @@ -10,13 +12,12 @@
import com.example.waggle.global.exception.handler.QuestionHandler;
import com.example.waggle.global.payload.code.ErrorStatus;
import com.example.waggle.web.dto.question.QuestionRequest;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static com.example.waggle.domain.board.service.BoardType.QUESTION;

@Slf4j
@RequiredArgsConstructor
@Transactional
Expand Down Expand Up @@ -88,13 +89,6 @@ public void deleteQuestion(Long boardId, Member member) {
questionRepository.delete(question);
}

@Override
public void increaseQuestionViewCount(Long boardId) {
Question question = questionRepository.findById(boardId)
.orElseThrow(() -> new QuestionHandler(ErrorStatus.BOARD_NOT_FOUND));
question.increaseViewCount();
}

private Question buildQuestion(QuestionRequest createQuestionRequest, Member member) {
return Question.builder()
.title(createQuestionRequest.getTitle())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.example.waggle.domain.board.question.entity.Question;
import com.example.waggle.domain.board.question.repository.QuestionRepository;
import com.example.waggle.domain.member.service.RedisService;
import com.example.waggle.domain.recommend.repository.RecommendRepository;
import com.example.waggle.global.exception.handler.QuestionHandler;
import com.example.waggle.global.payload.code.ErrorStatus;
Expand All @@ -26,6 +27,7 @@ public class QuestionQueryServiceImpl implements QuestionQueryService {

private final QuestionRepository questionRepository;
private final RecommendRepository recommendRepository;
private final RedisService redisService;

@Override
public List<Question> getAllQuestion() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;

public interface SirenRepository extends JpaRepository<Siren, Long>, SirenQueryRepository {

Expand All @@ -28,4 +32,12 @@ public interface SirenRepository extends JpaRepository<Siren, Long>, SirenQueryR

List<Siren> findAllByOrderByStatusAsc();

@Query("SELECT s.viewCount FROM Siren s WHERE s.id = :boardId")
Long findViewCountByBoardId(@Param("boardId") Long boardId);

@Transactional
@Modifying
@Query("update Siren s set s.viewCount = :viewCount where s.id = :boardId")
void applyViewCntToRDB(@Param("boardId") Long boardId, @Param("viewCount") Long viewCount);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.example.waggle.domain.board.siren.service;

import com.example.waggle.domain.board.siren.repository.SirenRepository;
import com.example.waggle.domain.member.service.RedisService;
import java.util.Objects;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CachePut;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Transactional
@RequiredArgsConstructor
@Service
public class SirenCacheService {

private final RedisService redisService;
private final SirenRepository sirenRepository;
private final String VIEW_COUNT_PREFIX = "viewCounts::";
private final String BOARD_PREFIX = "board::";

@CachePut(value = "viewCounts", key = "#boardId")
public Long applyViewCountToRedis(Long boardId) {
String viewCountKey = VIEW_COUNT_PREFIX + boardId;
String currentViewCount = redisService.getValue(viewCountKey);
if (currentViewCount != null) {
return redisService.increment(viewCountKey);
} else {
Long initialViewCount = sirenRepository.findViewCountByBoardId(boardId);
return initialViewCount + 1;
}
}

@Scheduled(fixedRate = 1000 * 60 * 3)
public void applyViewCountToRDB() {
Set<String> viewCountKeys = redisService.getKeysByPattern(VIEW_COUNT_PREFIX + "*");
if (Objects.requireNonNull(viewCountKeys).isEmpty()) {
return;
}

for (String viewCntKey : viewCountKeys) {
Long boardId = extractBoardIdFromKey(viewCntKey);
Long viewCount = Long.parseLong(redisService.getValue(viewCntKey));
sirenRepository.applyViewCntToRDB(boardId, viewCount);
redisService.deleteData(viewCntKey);
redisService.deleteData(BOARD_PREFIX + boardId);
}
}

private static Long extractBoardIdFromKey(String key) {
return Long.parseLong(key.split("::")[1]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,4 @@ Long updateSiren(Long boardId,

void deleteSiren(Long boardId, Member member);

void increaseSirenViewCount(Long boardId);

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.example.waggle.domain.board.siren.service;

import static com.example.waggle.domain.board.service.BoardType.SIREN;

import com.example.waggle.domain.board.ResolutionStatus;
import com.example.waggle.domain.board.service.BoardService;
import com.example.waggle.domain.board.siren.entity.Siren;
Expand All @@ -9,6 +11,7 @@
import com.example.waggle.domain.media.service.MediaCommandService;
import com.example.waggle.domain.member.entity.Gender;
import com.example.waggle.domain.member.entity.Member;
import com.example.waggle.domain.member.service.RedisService;
import com.example.waggle.domain.recommend.repository.RecommendRepository;
import com.example.waggle.global.exception.handler.QuestionHandler;
import com.example.waggle.global.exception.handler.SirenHandler;
Expand All @@ -19,8 +22,6 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static com.example.waggle.domain.board.service.BoardType.SIREN;

@Slf4j
@RequiredArgsConstructor
@Transactional
Expand All @@ -32,6 +33,7 @@ public class SirenCommandServiceImpl implements SirenCommandService {
private final BoardService boardService;
private final CommentCommandService commentCommandService;
private final MediaCommandService mediaCommandService;
private final RedisService redisService;

@Override
public Long createSiren(SirenRequest createSirenRequest, Member member) {
Expand Down Expand Up @@ -86,13 +88,6 @@ public void deleteSiren(Long boardId, Member member) {
sirenRepository.delete(siren);
}

@Override
public void increaseSirenViewCount(Long boardId) {
Siren siren = sirenRepository.findById(boardId)
.orElseThrow(() -> new SirenHandler(ErrorStatus.BOARD_NOT_FOUND));
siren.increaseViewCount();
}

private Siren buildSiren(SirenRequest createSirenRequest, Member member) {
return Siren.builder()
.title(createSirenRequest.getTitle())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ public interface SirenQueryService {
Page<Siren> getPagedSirenListByCategory(SirenCategory category, Pageable pageable);

Siren getSirenByBoardId(Long boardId);

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.example.waggle.domain.board.siren.entity.Siren;
import com.example.waggle.domain.board.siren.entity.SirenCategory;
import com.example.waggle.domain.board.siren.repository.SirenRepository;
import com.example.waggle.domain.member.service.RedisService;
import com.example.waggle.domain.recommend.repository.RecommendRepository;
import com.example.waggle.global.exception.handler.SirenHandler;
import com.example.waggle.global.payload.code.ErrorStatus;
Expand All @@ -28,6 +29,7 @@ public class SirenQueryServiceImpl implements SirenQueryService {

private final SirenRepository sirenRepository;
private final RecommendRepository recommendRepository;
private final RedisService redisService;

@Override
public List<Siren> getAllSiren() {
Expand Down Expand Up @@ -83,4 +85,5 @@ public Siren getSirenByBoardId(Long boardId) {
return sirenRepository.findById(boardId)
.orElseThrow(() -> new SirenHandler(ErrorStatus.BOARD_NOT_FOUND));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ public void decrementRecommendCnt(Long boardId) {
public Long getRecommendCnt(Long boardId) {
HashOperations<String, String, Long> hashOperations = redisTemplate.opsForHash();
RecommendationHashKey recommendationHashKey = buildHashKey(boardId);
log.info("class = {}", hashOperations.get(recommendationHashKey.getKey(), recommendationHashKey.getHashKey()).getClass());
return hashOperations.get(recommendationHashKey.getKey(), recommendationHashKey.getHashKey());
}

Expand Down Expand Up @@ -125,7 +124,7 @@ public void setRecommend(Long memberId, Long boardId) {
// setOperations.add(boardSetKey.getKey(), boardSetKey.getValue());
}

private Set<String> getKeysByPattern(String pattern) {
public Set<String> getKeysByPattern(String pattern) {
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
ScanOptions scanOptions = ScanOptions.scanOptions().match(pattern).build();
Cursor<byte[]> cursor = connection.scan(scanOptions);
Expand Down Expand Up @@ -215,4 +214,13 @@ private RecommendationSetKey buildMemberSetKey(Long memberId, Long boardId) {
// .build();
// }

public Long increment(String key) {
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
return valueOperations.increment(key);
}

public void deleteData(String key) {
redisTemplate.delete(key);
}

}
Loading

0 comments on commit 392be13

Please sign in to comment.