본문 바로가기

Server Programming/Spring Boot Backend Programming

[Spring 부트 - 영화 리뷰 프로젝트] 6. Ajax로 영화 리뷰 처리 (1) 리뷰 구성

반응형

리뷰 등록 순서

1. 버튼을 클릭해 리뷰 입력할 모달창을 띄운다.

2. 모달창에 별점주는 화면을 구성

3. 회원 아이디, 리뷰 점수, 내용 입력해 리뷰 등록

4. 리뷰 개수와 평균이 변경되었으므로, URL 재호출해서 갱신

 


진행 순서

  1. ReviewDTO 구성
    : Movie와 Member 참조하므로 단순 문자열 즉, 회원 mid와 mno를 참조하는 형태로 변경
  2. 작성한 리뷰의 평점과 내용을 변경할 수 있도록 Review 엔티티에 수정 메서드 추가
    1. changeGrade()
    2. changeText()
  3. ReviewService의 entityToDto()와 dtoToEntity() 메서드 정의 후, 추가 기능 작성
    1. 특정한 영화의 모든 리뷰를 가져오는 기능
    2. 새로운 영화 리뷰를 등록하는 기능
    3. 특정 영화 리뷰를 수정하는 기능
      [리뷰 엔티티 클래스를 수정한다.]
    4. 특정 영화 리뷰를 삭제하는 기능
  4. ReviewServiceImpl에서 ReviewService 메서드 구현
  5. RestController로 ReviewController 작성
    1. ReviewDTO는 JSON 형태로 변환되어 처리
    2. 새로운 영화 리뷰 등록도 JSON 폼새으로 전송 처리
  6. Read.html의 리뷰 모달창과 이미지 모달창 작성
  7. 별점 처리 라이브러리 적용

1. ReviewDTO 구성

package com.movie.boot4.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ReviewDTO {
    
    //리뷰 번호
    private Long reviewnum;
    
    //영화 번호
    private Long mno;
    
    //회원 번호
    private Long mid;
    
    //회원 정보
    private Long id;
    private String nickname;
    private String email;
    
    //리뷰 정보
    private int grade;
    private String text;
    private LocalDateTime regDate, modDate;
    
}

 

2. Review 엔티티 클래스 메서드 추가

package com.movie.boot4.entity;

import lombok.*;

import javax.persistence.*;

@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
//연관 관계의 엔티티 사용하지 않도록 설정
@ToString(exclude = {"movie", "member"})
public class Review extends BaseEntity{

    @Id
    @GeneratedValue (strategy = GenerationType.IDENTITY)
    private Long reviewnum;

    //다대일 관계에서는 항상 기본값이 즉시 로딩이므로, 지연 로딩으로 설정하는 작업 필요
    //-> 지연로딩을 사용하므로, 테스트시에 @Transactional 필요
    @ManyToOne (fetch = FetchType.LAZY)
    private Movie movie;

    @ManyToOne (fetch = FetchType.LAZY)
    private Member member;

    private int grade;

    private String text;

    //리뷰 수정을 위한 메서드 작성
    public void changeGrade(int grade){
        this.grade=grade;
    }
    public void changeText(String text){
        this.text=text;
    }
}

 

3. ReviewService에 메서드 정의

package com.movie.boot4.service;


import com.movie.boot4.dto.ReviewDTO;
import com.movie.boot4.entity.Member;
import com.movie.boot4.entity.Movie;
import com.movie.boot4.entity.Review;

import java.util.List;

public interface ReviewService {

    //특정 영화의 모든 리뷰를 가저온다.
    List<ReviewDTO> getListOfMovie(Long mno);
    
    //영화 리뷰를 추가
    Long register(ReviewDTO movieReviewDTO);
    
    //특정한 영화리뷰 수정
    void modify(ReviewDTO movieReviewDTO);
    
    //영화 리뷰 삭제
    void remove(Long reviewnum);
    
    default Review dtoToEntity(ReviewDTO movieReviewDTO){
        //영화 객체와 멤버 객체가 필요하다.
        //해당 영화객체는 영화리뷰DTO에서 mno을 이용해 객체 생성
        //해당 회원객체는 영화리뷰DTO에서 mid를 이용해 객체 생성
        Review movieReview = Review.builder()
                .reviewnum(movieReviewDTO.getReviewnum())
                .movie(Movie.builder().mno(movieReviewDTO.getMno()).build())
                .member(Member.builder().mid(movieReviewDTO.getMid()).build())
                .grade(movieReviewDTO.getGrade())
                .text(movieReviewDTO.getText())
                .build();
        return movieReview;
    }
    
    default ReviewDTO entityToDTO(Review review){
        ReviewDTO reviewDTO= ReviewDTO.builder()
                .reviewnum(review.getReviewnum())
                .mno(review.getMovie().getMno())
                .mid(review.getMember().getMid())
                .nickname(review.getMember().getNickname())
                .email(review.getMember().getEmail())
                .grade(review.getGrade())
                .text(review.getText())
                .regDate(review.getRegDate())
                .modDate(review.getModDate())
                .build();
        
        return reviewDTO;
    }
    
}

 

4.ReviewServiceImpl에서 메서드 구현

package com.movie.boot4.service;

import com.movie.boot4.dto.ReviewDTO;
import com.movie.boot4.entity.Movie;
import com.movie.boot4.entity.Review;
import com.movie.boot4.repository.ReviewRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
@Log4j2
@RequiredArgsConstructor
public class ReviewServiceImpl implements ReviewService{
    //interface와 달리 다른 곳에서 호출을 할 메서드이므로 public으로 선언
    //(1) 엔티티를 DTO로 변환
    //(2) DTO를 엔티티로 변환

    private final ReviewRepository reviewRepository;

    //특정 영화의 모든 리뷰를 가저온다. -> 엔티티로 리포지토리에서 원하는 리뷰를 찾아 DTO로 변환해 리스트로 반환
    @Override
    public List<ReviewDTO> getListOfMovie(Long mno){
        Movie movie=Movie.builder()
                .mno(mno)
                .build();
        List<Review> result=reviewRepository.findByMovie(movie);

        return result.stream()
                .map(movieReview -> entityToDTO(movieReview))
                .collect(Collectors.toList());
    }

    @Override
    //영화 리뷰를 추가 -> DTO를 엔티티로 변환해 리포지토리에 저장 후, DTO의 리뷰번호 반환
    public Long register(ReviewDTO movieReviewDTO){
        Review review=dtoToEntity(movieReviewDTO);

        reviewRepository.save(review);

        return movieReviewDTO.getReviewnum();
    }

    @Override
    //특정한 영화리뷰 수정 -> null일 수 있으므로 Optional 사용해, DTO로 리뷰를 찾고, 있으면 엔티티에서 수정 후, 리포지토리에 저장
    public void modify(ReviewDTO movieReviewDTO){
        Optional<Review> result=reviewRepository.findById(movieReviewDTO.getReviewnum());

        if(result.isPresent()){
            Review review = result.get();
            review.changeGrade(movieReviewDTO.getGrade());
            review.changeText(movieReviewDTO.getText());

            reviewRepository.save(review);
        }
    }

    @Override
    //영화 리뷰 삭제 -> 리포지토리에서 리뷰번호로 제거
    public void remove(Long reviewnum){
        reviewRepository.deleteById(reviewnum);
    }
}

 

5.ReviewController

  • RestController로 설계해 '/reviews/'로 시작되는 경로 처리
  • ReviewDTO는 JSON으로 변환되어 처리
  • 새로운 영화 리뷰 등록도 JSON으로 변환되어 전송 처리
방식 URL 결과 데이터 설명
GET /reviews/영화번호/all ReviewDTO 리스트 해당 영화의 모든 리뷰들을 반환
POST /reviews/영화번호 생성된 리뷰 번호 새로운 리뷰를 등록
PUT /reveiews/영화번호/영화리뷰번호 리뷰의 수정 성공 여부 리뷰 수정
DELETE /reveiews/영화번호/영화리뷰번호 리뷰 삭제 리뷰 삭제
package com.movie.boot4.controller;

import com.movie.boot4.dto.ReviewDTO;
import com.movie.boot4.service.ReviewService;
import lombok.RequiredArgsConstructor;
import lombok.extern.java.Log;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

//JSON 처리를 위한 RestController 설계
@RestController
//객체를 JSON으로  : RequestBody
//JSON을 객체로   : ResponseBody

//경로 처리를 위한 어노테이션
@RequestMapping("/reviews")
@Log4j2
@RequiredArgsConstructor
public class ReviewController {
    private final ReviewService reviewService;

//    해당 영화 모든 리뷰 반환, 새로운 리뷰 등록
//    RestController에서 수정 : PutMapping 사용
//    RestController에서 삭제 : DeleteMapping 사용

//    @GetMapping("/{mno}/all")
//    @PostMapping("/{mno}")
//    @PutMapping("/{mno}/{reviewnum}")
//    @DeleteMapping("/{mno}/{reviewnum}")

    //해당 영화 모든 리뷰 반환 -> mno을 getListOfMovie 메서드에 파라미터로 전달
    @GetMapping("/{mno}/all")
    //@PathVariable은 URL 경로에 변수를 넣는다.
    public ResponseEntity<List<ReviewDTO>> getList(@PathVariable("mno") Long mno){
        log.info("-------list-----------");
        log.info("MNO : "+mno);

        //getListOfMovie : 특정 영화의 모든 리뷰를 가저오는 메서드
        List<ReviewDTO> reviewDTOlist = reviewService.getListOfMovie(mno);

        return new ResponseEntity<>(reviewDTOlist, HttpStatus.OK);
    }

    //새로운 리뷰 등록 후 해당 리뷰 번호 반환 -> 요청 본문에 리뷰DTO를 register메서드의 파라미터로 전달
    @PostMapping("/{mno}")
    //스프링에서의 비동기 처리를 위한 어노테이션
    //요청본문 requestBody, 응답본문 responseBody
    public ResponseEntity<Long> addReview(@RequestBody ReviewDTO reviewDTO){
        log.info("-----------add MovieReview-------------");
        log.info("reviewDTO: "+ reviewDTO);

        Long reviewnum = reviewService.register(reviewDTO);

        return new ResponseEntity<>(reviewnum, HttpStatus.OK);
    }


    //RestController에서 수정 : PutMapping 사용
    //RestController에서 삭제 : DeleteMapping 사용
    //리뷰 수정 -> 경로에 변수를 넣고, 요청 본문을 전달하는데, 리뷰DTO를 modify 메서드에 파라미터로 전달
    @PutMapping("/{mno}/{reviewnum}")
    public ResponseEntity<Long> modifyReview(@PathVariable Long reviewnum, @RequestBody ReviewDTO reviewDTO){
        log.info("----------------modify MovieReview-------------"+ reviewnum);
        log.info("reviewDTO : "+ reviewDTO);

        reviewService.modify(reviewDTO);

        return new ResponseEntity<>(reviewnum, HttpStatus.OK);
    }

    //리뷰 삭제 -> 경로에 변수를 넣고, 리뷰번호를 remove메서드의 파라미터로 전달
    @DeleteMapping("/{mno}/{reviewnum}")
    public ResponseEntity<Long> removeReview(@PathVariable Long reviewnum){
        log.info("--------------modify removeReview--------------");
        log.info("reviewnum: "+ reviewnum);

        reviewService.remove(reviewnum);

        return new ResponseEntity<>(reviewnum, HttpStatus.OK);
    }
}

 

 

6. Read.html의 리뷰 모달창과 이미지 모달창

  • 리뷰 모달창 - 실제 영화 리뷰 처리
    • 회원 아이디
    • 별점 내용
    • 리뷰 내용
    • 여러 개의 버튼 존재하고 상황에 따라서 보이거나 사라짐
  • 이미지 모달창 - 단순 이미지 확인
    • 닫기 버튼 하나 존재
<!-- 리뷰 모달창과 이미지 모달창-->
        <div class="reviewModal modal" tabindex="-1" role="dialog">
            <div class="modal-dialog" role="document">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title">Movie Review</h5>

                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                            <span aria-hidden="true">&times;</span>
                        </button>
                    </div>
                    <div class="modal-body">
                        <div class="form-group">
                            <label >Reviewer ID</label>
                            <input type="text" class="form-control" name="mid" >
                        </div>
                        <div class="form-group">
                            <label >Grade <span class="grade"></span></label>
                            <div class='starrr'></div>
                        </div>
                        <div class="form-group">
                            <label >Review Text</label>
                            <input type="text" class="form-control" name="text" placeholder="Good Movie!" >
                        </div>
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
                        <button type="button" class="btn btn-primary reviewSaveBtn">Save changes</button>
                        <button type="button" class="btn btn-warning modifyBtn">Modify </button>
                        <button type="button" class="btn btn-danger removeBtn">Remove </button>
                    </div>
                </div>
            </div>
        </div>

        <div class="imageModal modal " tabindex="-2" role="dialog">
            <div class="modal-dialog modal-lg" role="document">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title">Picture</h5>

                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                            <span aria-hidden="true">&times;</span>
                        </button>
                    </div>
                    <div class="modal-body">

                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
                    </div>
                </div>
            </div>
        </div>

 

7. 별점 처리 라이브러리

https://github.com/dobtco/starrr

 

GitHub - dobtco/starrr: 1-5 star rating, in jQuery.

1-5 star rating, in jQuery. Contribute to dobtco/starrr development by creating an account on GitHub.

github.com

 

static 폴더에 starrr.js

css 폴더에 starrr.css 파일 추가

 

파일 링크 추가

<script th:src="@{/starrr.js}"></script>
<link th:href="@{/css/starrr.css}" rel="stylesheet">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/css/font-awesome.min.css">

 

강제로 reviewModal을 화면에 띄우도록 수정

 

<script>
    $(document).ready(function(e) {

        var grade = 0;
        var mno = [[${dto.mno}]];

        $('.starrr').starrr({
            rating: grade,
            change: function(e, value){
                if (value) {
                    console.log(value);
                    grade = value;
                }
            }
        });

        $(".reviewModal").modal("show"); //미리 보기용

    });
</script>

: starrr 라이브러리는 jQuery 플러그인으로, 별점의 값이 변하는 이벤트 처리 가능

 

-> grade가 해당 별점의 변수로 조회 화면에서 리뷰 입력용 모달창 확인 가능

-> Reviewer ID에 숫자로된 사용자 번호 입력

 


 

반응형