ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Project(Springboot) - 조회수 Count AOP_2
    카테고리 없음 2022. 11. 1. 01:54

    Project(Springboot) - 조회수 Count AOP_1 바로가기

     

    Project(Springboot) - 조회수 Count AOP_1

    목적 Project(Springboot) - 조회수 Count AOP_2 바로가기 Project(Springboot) - 조회수 Count AOP_2 목적 지난번 1차 구현할때 발생한 문제인 DB의 조회수는 바로 올라가나 실제로 응답 하는 데이터에는 바로 반영

    real-coding.tistory.com

    목적

    지난번 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;
            }
        }
    
    }
    

     

    참고자료

    스스로 생각해보기!

    이번에 문제를 해결하는 과정에서 객체지향의 중요 개념중 하나인 다형성의 중요성에 대해 경험하는 계기가 되었다. 향후 고도화 과정에서 상속과, 다형성을 조금더 활용 할 방법을 고민해 보아야겠다.

    댓글

Designed by Tistory.