[Spring Boot] 20. 리뷰 읽기 · 작성 · 삭제 ①

shr·2022년 3월 3일
0

Spring

목록 보기
19/23
post-thumbnail

리뷰 읽기 · 작성 · 삭제 ①


  1. 리뷰 테이블 생성

    상품은 제조사, 상품명, 상품 정보 등을 가진다. 상품에는 리뷰를 작성할 수 있다. 리뷰는 작성자, 작성일, 내용, 별점 등을 가진다. 리뷰를 출력할 때는 상품의 별점 평균을 출력한다. 이때 상품과 리뷰에서 반복되는 컬럼을 분리해 리뷰 테이블을 생성한다. 이를 제 1 정규화라고 한다.

    💡 제 1 정규화

    반복되는 컬럼을 분리한다. 분리된 테이블이 자식 테이블이 된다. 원 테이블(부모)와 식별 관계로 연결하고 부모의 기본 키를 앞으로 위치한다.

    create table review (
        pno number(7),
        rno number(7),
        content varchar2(100 char),
        writer varchar2(10 char),
        writeday date,
        star number(1),
        -- 복합 키
        constraint review_pk primary key(pno, rno)
    );

    이때 리뷰 테이블의 상품 번호(pno)는 외래 키이고, 상품 번호와 리뷰 번호(rno)는 복합 키가 된다. 리뷰를 보기 위해서는 상품을 통해 들어와야 하므로 상품 번호가 필요하고, 리뷰를 삭제할 때는 해당 리뷰 번호가 있어야 삭제가 가능하므로 리뷰 번호 역시 필요하기 때문이다.

    💡 외래키

    서로 관련된 두 테이블에 공통으로 존재하며 두 테이블을 연결하는 컬럼을 말한다.

    💡 복합키

    기본 키가 되지 못하는 컬럼들을 묶어 기본 키처럼 사용하는 것을 말한다.


  1. 엔티티, DAO, DTO 설정
  • src/main/java - com.example.demo.entity - Review

    package com.example.demo.entity;
    
    import java.time.*;
    import java.time.format.*;
    
    import com.example.demo.dto.*;
    
    import lombok.*;
    
    @Data
    @AllArgsConstructor
    @Builder
    @Accessors(chain = true)
    public class Review {
        private Integer pno;
        private Integer rno;
        private String content;
        private String writer;
        private LocalDate writeday;
        private Integer star;
    
        // ReviewDto.MvcRead로 변환하는 메소드
        public ReviewDto.MvcRead toMvcRead() {
            DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
            String writeday = dtf.format(this.writeday);
            return new ReviewDto.MvcRead(this.rno, this.content, this.writer, writeday, this.star);
        }
    
        // ReviewDto.RestRead로 변환하는 메소드
        public ReviewDto.RestRead toRestRead() {
            return new ReviewDto.RestRead(pno, rno, content, writer, writeday, star);
        }
    }
  • src/main/java - com.example.demo.dao - ReviewDao

    package com.example.demo.dao;
    
    import java.util.*;
    
    import org.apache.ibatis.annotations.*;
    
    import com.example.demo.entity.*;
    
    public interface ReviewDao {
        // MVC - 리뷰 출력
        @Select("select * from review where pno=#{pno}")
        public List<Review> findByPno(Integer pno);
    
        // REST
        // 리뷰 작성
        @SelectKey(statement="select count(rno)+1 from review", keyProperty="rno", before=true, resultType=Integer.class)
        @Insert("insert into review(pno,rno,content,writer,writeday,star) values(#{pno},#{rno},#{content},#{writer},#{writeday},#{star})")
        public Integer save(Review review);
    
        // 리뷰 삭제
        @Select("select * from review where pno=#{pno} and rno=#{rno}")
        public Review findByPnoAndRno(Integer pno, Integer rno);
    }
  • src/main/java - com.example.demo.dto - ReviewDto

    package com.example.demo.dto;
    
    import java.time.*;
    
    import com.fasterxml.jackson.annotation.*;
    
    import lombok.*;
    
    @NoArgsConstructor(access = AccessLevel.PRIVATE)
    public class ReviewDto {
        @Data
        @AllArgsConstructor
        public static class MvcRead {
            private Integer rno;
            private String content;
            private String writer;
            private String writeday;
            private Integer star;
        }
    
        @Data
        @AllArgsConstructor
        public static class RestRead {
            @JsonIgnore
            private Integer pno;
            private Integer rno;
            private String content;
            private String writer;
            @JsonFormat(pattern="yyyy-MM-dd")
            private LocalDate writeday;
            private Integer star;
        }
    }

    상품 번호(pno)는 리뷰에 출력하지 않고, 날짜는 "yyyy-MM-dd" 형식으로 바꿔서 출력하기 위해 DTO를 생성했다. MVC 방식과 REST 방식의 차이를 알 수 있다.

    📝 REST 방식은 객체를 JSON으로 변환해 출력하고, 변환을 담당하는 객체는 MessageConverter이다. 부트에서는 이를 Jackson이 담당하고, Jackson은 어노테이션을 기반으로 MessageConverter를 설정한다. 따라서 위처럼 어노테이션 설정으로 출력 형식 변환이 가능하다. 반면 MVC 방식의 응답 형식은 자바 객체로, 자바 객체에서 서식을 지정해 주면 문자열로 출력이 가능하다. 따라서 writeday의 타입을 String으로 지정해 주었다.


  1. 서비스 설정
  • src/main/java - com.example.demo.service - ReviewService

    package com.example.demo.service;
    
    import java.util.*;
    import java.util.stream.*;
    
    import org.springframework.stereotype.*;
    
    import com.example.demo.dao.*;
    import com.example.demo.dto.*;
    import com.example.demo.entity.*;
    
    import lombok.*;
    
    @AllArgsConstructor
    @Service
    public class ReviewService {
        private ReviewDao reviewDao;
        private ProductDao productDao;
    
        // 리뷰 읽기
        public List<ReviewDto.MvcRead> readByPno(Integer pno) {
            return reviewDao.findByPno(pno).stream().map(review->review.toMvcRead()).collect(Collectors.toList());
        }
    
        // 리뷰 작성 (로그인 아이디를 글쓴이로 추가해서 저장한다.)
        public List<ReviewDto.RestRead> write(ReviewDto.Write dto, String loginId) {
    			Review review = dto.toEntity();
    			review.setWriter(loginId).setWriteday(LocalDate.now());
    			dao.save(review);
    
            //  제품(Product)의 countOfReview 1 증가, sumOfStar 증가, countOfStar 1 증가
            Product product = productDao.findById(review.getPno());
            Product param = Product.builder().countOfStar(product.getCountOfStar()+1)
                    .countOfReview(product.getCountOfReview()+1).sumOfStar(product.getSumOfStar()+review.getStar())
                    .pno(review.getPno()).build();
            productDao.updateReview(param);
    
            return reviewDao.findByPno(review.getPno()).stream().map(r->r.toRestRead()).collect(Collectors.toList());
        }
    
        // 리뷰 삭제 
        public List<ReviewDto.RestRead> deleteByRno(Integer rno, Integer pno, String loginId) {
            Review review = reviewDao.findByPnoAndRno(pno, rno);
            if(review.getWriter().equals(loginId)==false)
                return null;
            reviewDao.deleteByRno(rno);
    
            //  제품(Product)의 countOfReview 1 감소, sumOfStar 감소, countOfStar 1 감소
            Product product = productDao.findById(review.getPno());
            Product param = Product.builder().countOfStar(product.getCountOfStar()-1)
                    .countOfReview(product.getCountOfReview()-1).sumOfStar(product.getSumOfStar()-review.getStar())
                    .pno(pno).build();
            productDao.updateReview(param);
    
            return reviewDao.findByPno(pno).stream().map(r->r.toRestRead()).collect(Collectors.toList());
        }

    💡 stream()

    배열의 원소를 하나씩 꺼내 여러 개의 람다식으로 처리할 수 있는 반복문이다. 체이닝 형식으로 메소드를 연속 사용할 수 있다.

    💡 map()

    형 변환을 해 주는 stream의 메소드이다. stream().map(review -> review.toMvcRead()) 이렇게 작성했다면 stream으로 꺼낸 review 원소를 MvcRead 객체로 변환한다는 것이다.

    📝 람다식으로 짧게 작성하기

    List<ReviewDto.MvcRead> result = new ArrayList<>();
    List<Review> list = dao.findByPno(pno);
    for(Review review:list) {
        result.add(review.toMvcRead());
    }
    return result;

    🔽 짧게 작성하기

    reviewDao.findByPno(pno).stream().map(review->review.toMvcRead()).collect(Collectors.toList());

  1. 상품(Product)에서의 변경 사항
  • src/main/java - com.example.demo.dao - ProductDao

        public Product findById(Integer pno);
    
        public void updateReview(Product param);

    리뷰의 상품 번호로 상품을 찾고, 리뷰가 작성 혹은 삭제로 업데이트 되면 상품의 전체 리뷰 수(countOfReview), 별점의 총 합(sumOfStar), 별점을 매겨진 횟수(countOfStar)에도 업데이트가 있어야 한다.

  • src/main/resources - mapper - productMapper.xml

        <select id="findById" resultType="product">
            select * from product where pno=#{pno}
        </select>
    
        <update id="updateReview">
            update product set countOfStar=#{countOfStar}, countOfReview=#{countOfReview}, sumOfStar=#{sumOfStar}
            where pno=#{pno}
        </update>
profile
못하다 보면 잘하게 되는 거야 ・ᴗ・̥̥̥

0개의 댓글