-
Project(Springboot) - 조회수 Count AOP_2카테고리 없음 2022. 11. 1. 01:54
Project(Springboot) - 조회수 Count AOP_1 바로가기
목적
지난번 1차 구현할때 발생한 문제인 DB의 조회수는 바로 올라가나 실제로 응답 하는 데이터에는 바로 반영이 안되는 부분의 해결을 위해 코드 수정을 진행하였다.
구현
먼저 지난번 구현때 사용했던 @Before Advice는 별도 구현이 필요없다 판단하여 삭제후 필요 코드는 @AfterReturning Advice에 병합하였다.
실제 응답데이터의 조회수 1증가 부분을 @AfterReturning Advice 내에서 구현 하는 방법을 선택하였다.
@Aspect @Component @Slf4j @RequiredArgsConstructor public class ViewCountAop { private final ViewCount viewCount; @AfterReturning(value = "@annotation(com.market.supercar.annotation.ViewCount) && args(brdSeq, ..)", returning = "result", argNames = "joinPoint,brdSeq,result") private void afterReturning(JoinPoint joinPoint, final Long brdSeq, ResponseEntity<?> result) {
@AfterReturning annotaion안에 returning keyword를 통해 Controller에서 빠져나오는 response를 캐치하였다.
이때, ResponseDto 가 어떤 것으로 사용되었는지 알 수 없기 때문에 공통적으로 사용할 필드를 상속 할 수 있는 부모객체를 만들어 다형성을 구현하여 어떤 ResponseDto가 return되는가에 상관없이 get/setter 사용을 할 수 있도록 하였다.
- 부모 클래스
public class ViewCountResponseDto { private int viewCount; }
- 자식 클래스
@SuperBuilder @AllArgsConstructor @RequiredArgsConstructor @Getter @Setter public class MagazineDetailResponseDto extends ViewCountResponseDto { private Long brdSeq; private String userNickName; private String title; private String contents; private List<String> magazineAttAttachmentUrls; @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy.MM.dd HH:mm", timezone = "Asia/Seoul") private LocalDateTime createdDate; private int cmtCount; @JsonProperty(value = "commentResponseDto") private MagazineCommentResponseDto magazineCommentResponseDto; }
- 부모클래스에 자식클래스를 업캐스팅
ViewCountResponseDto viewCountResponseDto = (ViewCountResponseDto) result.getBody(); Objects.requireNonNull(viewCountResponseDto).setViewCount(viewCountResponseDto.getViewCount() + 1);
리팩토링 완료한 조회수 AOP
@Aspect @Component @Slf4j @RequiredArgsConstructor public class ViewCountAop { private final ViewCount viewCount; @AfterReturning(value = "@annotation(com.market.supercar.annotation.ViewCount) && args(brdSeq, ..)", returning = "result", argNames = "joinPoint,brdSeq,result") private void afterReturning(JoinPoint joinPoint, final Long brdSeq, ResponseEntity<?> result) { String domainName = joinPoint.toShortString().split("\\\\(")[1].split("Controller")[0]; Cookie oldCookie = null; // request 의 Cookies 에서 "boardView" cookie 유무 확인 HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); Cookie[] cookies = Objects.requireNonNull(request).getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if (cookie.getName().equals("boardView")) { oldCookie = cookie; break; } } } // 중복 확인 후 cookie setting if (oldCookie == null || !oldCookie.getValue().contains("[" + brdSeq + "]")) { // responseDto 의 viewCount +1 ViewCountResponseDto viewCountResponseDto = (ViewCountResponseDto) result.getBody(); Objects.requireNonNull(viewCountResponseDto).setViewCount(viewCountResponseDto.getViewCount() + 1); // response cookie 설정 HttpServletResponse response = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getResponse(); // 해당 페이지 DB table 에 조회수 1 추가 viewCount.setViewCount(domainName, brdSeq); // 금일 자정까지 남은 시간 계산 int maxAge = (int)Duration.between(LocalTime.now(), LocalTime.of(23,59,59)).getSeconds()+1; if (oldCookie != null) { // 저장된 쿠키는 있지만 새로운 페이지를 조회하는 case oldCookie.setValue(oldCookie.getValue() + "[" + brdSeq + "]"); // 쿠키를 전송하기위한 웹서버의 URL 경로 지정 oldCookie.setPath("/"); // 기존 발행된 쿠키의 만료 시간을 리셋 oldCookie.setMaxAge(maxAge); // response 에 위에서 만든 cookie 저장 Objects.requireNonNull(response).addCookie(oldCookie); } else { // 저장된 쿠키가 없는 case Cookie newCookie = new Cookie("boardView", "[" + brdSeq + "]"); // 쿠키를 전송하기위한 웹서버의 URL 경로 지정 newCookie.setPath("/"); // 쿠키의 만료 시간을 설정 newCookie.setMaxAge(maxAge); // response 에 위에서 만든 cookie 저장 Objects.requireNonNull(response).addCookie(newCookie); } } } } @Component @RequiredArgsConstructor @Slf4j class ViewCount{ private final MagazineRepository magazineRepository; private final ProductRepository productRepository; private final PaparazziRepository paparazziRepository; // viewCountUp Process @Transactional public void setViewCount(String domainName, Long brdSeq) { switch (domainName) { case "Magazine" : magazineRepository.updateViewCount(brdSeq); break; case "Product" : productRepository.updateViewCount(brdSeq); break; case "Paparazzi" : paparazziRepository.updateViewCount(brdSeq); break; default: break; } } }
참고자료
스스로 생각해보기!
이번에 문제를 해결하는 과정에서 객체지향의 중요 개념중 하나인 다형성의 중요성에 대해 경험하는 계기가 되었다. 향후 고도화 과정에서 상속과, 다형성을 조금더 활용 할 방법을 고민해 보아야겠다.