[팀프로젝트] 비동기방식으로 좋아요👍 구현하기

정상희·2023년 3월 1일
0
post-thumbnail

📍 이번 포스트에서는 좋아요 기능을 구현해보며

  • 동기 방식과 비동기 방식의 차이
  • 비동기로 구현하는 이유
  • 언어나 프레임워크가 아닌 비동기를 구현하는 방식인 Ajax

등등 에 대한 간단한 설명을 곁들이려고 한다.

개발환경

개발 툴 : SpringBoot 2.7.7
자바 : JAVA 11
빌드 : Gradle
템플릿 엔진 : Thymeleaf
spring MVC 구조


💬 비동기 방식은 왜 필요할까?

HTML 파일을 전체 새로고침하게 되면 그 안에 있는 많은 script, jpg 등의 컨텐츠를 새로 불러와야 한다. CDN이나 많은 javascript 이벤트가 있을 경우 서버가 매우 버벅거릴 것이다.

댓글을 다는 경우에는 해당 댓글 파트만 변경이 되면 되는데 굳이 다른 파트까지 변경시키게 되는 것이다. 이는 심각한 자원 낭비이다. 따라서 비동기 통신으로 이를 처리한다.

✔ Ajax란?

Ajax(Asynchronous JavaScript And XML)

  • 브라우저 내에서 비동기 기능(신호를 보냈을 때 새로운 html페이지를 로딩 할 필요 없이 동작 수행)을 제공하는 기법

  • 동적으로 페이지를 변화 시켜주는 기능
    즉, Ajax는 웹 페이지 전체를 다시 로딩하지 않고도, 웹 페이지의 일부분만을 갱신할 수 있게 해준다.

  • 최근에는 XML 보다 JSON을 더 많이 사용한다.

동기 방식 vs 비동기 방식

  • 동기 : 서버에 신호를 보냈을 때 응답이 들어와야 다음 동작 수행 가능
  • 비동기 : 신호를 보냈을 때 응답 상태와 상관없이 동작 수행 가능
    - 자료를 요청할 때 클라이언트의 진행 시간을 기다릴 필요 없이 작업을 수행할 수 있다는 장점이 있다.

✔ Ajax 동작 방식

브라우저가 Data를 요청 및 전송한다.
ex) 댓글로 예를 들면, 댓글 내용과 해당 댓글이 달린 글의 id값을 서버로 보낸다. 그리고 처리 결과를 받기를 기다린다.)

서버는 받은 Data를 로직을 따라 처리한다. 그리고 Json이라는 형식으로 다시 브라우저에게 Data를 전송한다.

브라우저는 처리 결과를 받고 event를 발생시켜 비동기적으로 html 파일의 일부를 변경시킨다.

이 때 Json 이라는 건 무엇일까?
데이터를 전달할 목적으로 사용하는 하나의 '형식' 중 하나이며
중괄호 ({})같은 형식으로 하고, 값을 ','로 나열하는 포맷이다.

실습으로 들어가보자!


🤗 실습

❗ 연관 관계 매핑


모임-좋아요, 회원-좋아요 테이블 관계는 모두 다대일 양방향 매핑으로 설정했다.

*JPA에서는 양쪽에서 단방향 매핑하는 방법으로 양방향 매핑을 구현한다.

❗ 좋아요 개수 출력하기(동기 방식)

게시글 "상세 페이지"에 들어갔을 때 현재 좋아요 개수가 출력(응답)되어야 한다.

흐름도는 위 사진과 같다.


CrewViewController

// 크루 게시물 상세 페이지
    @GetMapping("/view/v1/crews/{crewId}")
    public String detailCrew(@PathVariable Long crewId, Model model, @ModelAttribute("sportRequest") CrewSportRequest crewSportRequest,
                             @PageableDefault(page = 0, size = 1, sort = "lastModifiedAt", direction = Sort.Direction.DESC) Pageable pageable, Authentication authentication) {
			...

        try {
            // 좋아요
            int count = likeViewService.getLikeCrew(crewId);
            model.addAttribute("likeCnt", count);

            // 상세게시글 정보
            CrewDetailResponse details = list.getContent().get(0);
            model.addAttribute("details", details);
            ...
            }

        } catch (Exception e) {
            return "redirect:/index";
        }

        return "crew/read-crew";
    }

crewId를 파라메터로 넘긴다.

LikeViewService

public int getLikeCrew(Long crewId){
        Crew crew = crewRepository.findById(crewId).orElseThrow(()->new AppException(ErrorCode.CREW_NOT_FOUND,ErrorCode.CREW_NOT_FOUND.getMessage()));
        List<Like> num = likeRepository.findByCrew(crew);
        return num.size();
    }

해당 crew가 가진 like를 list로 받고, list.size()를 출력

LikeRepository

public interface LikeRepository extends JpaRepository<Like, Long> {
    List<Like> findByCrew(Crew crew);
}

read-crew.html

<div id="likeCnt" th:text="${likeCnt}"></div> 


현재 crew가 가진 좋아요 개수를 출력한다.

❗ 좋아요 누르기(⭐비동기 방식)

"👍" 버튼을 눌렀을 때 Ajax를 이용하여 API를 호출한 후
그 결과로 비동기 방식을 통해 좋아요 개수가 변경되어야 한다.

아래는 좋아요 버튼까지 포함한 html코드
read-crew.html

<div align="center">
  <form class="container">
    <button type="button" class="btn btn-light btn-lg" id="like-btn" onclick="likeBtn(crewId)">👍
    </button>
    <tr><div id="likeCnt" th:text="${likeCnt}"></div> </tr>
  </form>
</div>

ajax

function likeBtn(crewId){
        $.ajax({
            type : "POST",
            url: '/view/v1/crews/'+crewId+'/like',
            success: function(data) {
                console.log(data); 				// likeResponse값
                if(data.likeCheck == 1){ 		// likeResponse.likeCheck로 값에 접근
                    alert("좋아요를 눌렀습니다💕")
                    $("#likeCnt").empty();
                    $("#likeCnt").append(data.count);
                }
                else if (data.likeCheck == 0){
                    alert("좋아요를 취소했습니다✔")
                    $("#likeCnt").empty();
                    $("#likeCnt").append(data.count);

                }
            },
            error: function (request, status, error) {
                alert("로그인 후 좋아요가 가능합니다.")
            }
        }) ;
    }

url이 호출된 후 흐름도는 다음과 같다

CrewViewController

// 크루 좋아요 누르기
    @PostMapping("/view/v1/crews/{crewId}/like")
    public ResponseEntity likeCrew(@PathVariable Long crewId, Authentication authentication) {
        LikeViewResponse likeViewResponse = likeViewService.pressLike(crewId, authentication.getName());
        return new ResponseEntity<>(likeViewResponse, HttpStatus.OK);
    }

LikeResponse

@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class LikeViewResponse {
    private int likeCheck;    // like를 눌렀는지 확인하기 위한 필드
    private int count;		  // 현재 likeCnt
    private String userName;  // 좋아요 버튼을 누른 회원의 이름
}

LikeService
crewId와 userName을 파라메터로 넘겨받고, 해당 crew에 현재 로그인 되어있는 회원이 좋아요를 눌렀는지를 확인해본다.

@Transactional
    public LikeViewResponse pressLike(Long crewId, String userName){
        User user = userRepository.findByUserName(userName).orElseThrow(()->new AppException(ErrorCode.USERID_NOT_FOUND,ErrorCode.USERID_NOT_FOUND.getMessage()));
        Crew crew = crewRepository.findById(crewId).orElseThrow(()->new AppException(ErrorCode.CREW_NOT_FOUND,ErrorCode.CREW_NOT_FOUND.getMessage()));
        LikeViewResponse likeViewResponse = new LikeViewResponse();
        // 이미 좋아요한 상태라면 -> 좋아요 취소
        if(user.getLikes().stream().anyMatch(like -> like.getCrew().equals(crew))){
            likeRepository.deleteByUserAndCrew(user,crew);
            likeViewResponse.setLikeCheck(0); 

        } else {
            likeRepository.save(Like.builder().crew(crew).user(user).build());
            likeViewResponse.setLikeCheck(1);
                     ...
                }
            }
        }

        List<Like> num = likeRepository.findByCrew(crew);

        likeViewResponse.setCount(num.size());
        likeViewResponse.setUserName(user.getUsername());
        return likeViewResponse;
    }

LikeRepository

public interface LikeRepository extends JpaRepository<Like, Long> {
    void deleteByUserAndCrew(User user, Crew crew);

✔️ 결과 확인

좋아요를 처음 눌렀을 때 출력되는 json

좋아요를 다시 눌러서 취소할 경우 json

  • 좋아요 버튼을 누르면 새로고침 없이 likeCnt의 값을 likeResponse의 count 값으로 변경하여 출력(비동기 방식)

참고 문헌

https://devlogofchris.tistory.com/14
https://lshdv.tistory.com/73
https://chaelin1211.github.io/study/2021/04/14/thymeleaf-ajax.html

자세한 코드는 깃허브에서 올려놨으니 참고해주세요
https://github.com/sanghee2359/WorkoutMate

0개의 댓글