본문 바로가기
PROGRAMING📚/Spring📑

Spring[EX06.코드] - 데이터 , 이미지 등록(통합구현)

Ta이니 2024. 9. 25.
728x90
반응형

페이지 안에 영화 이미지, 영화 정보에 대해서 나오도록 작업 할 예정

src/main/java/com/example/ex6/service/MovieService.java 에 추가
PageResultDTO<MovieDTO, Object[]> getList(PageRequestDTO pageRequestDTO);

default MovieDTO entityToDto(Movie movie, List<MovieImage> movieImageList
      , Double avg, int reviewCnt){
    MovieDTO movieDTO = MovieDTO.builder()
        .mno(movie.getMno())
        .title(movie.getTitle())
        .regDate(movie.getRegDate())
        .modDate(movie.getModDate())
        .build();
    List<MovieImageDTO> movieImageDTOList = movieImageList.stream().map(
        new Function<MovieImage, MovieImageDTO>() {
          @Override
          public MovieImageDTO apply(MovieImage movieImage) {
            MovieImageDTO movieImageDTO = MovieImageDTO.builder()
                .imgName(movieImage.getImgName())
                .path(movieImage.getPath())
                .uuid(movieImage.getUuid())
                .build();
            return movieImageDTO;
          }
        }
    ).collect(Collectors.toList());
    movieDTO.setImageDTOList(movieImageDTOList); //추가
    movieDTO.setAvg(avg);
    movieDTO.setReviewCnt(reviewCnt);
    return movieDTO;
  };
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MovieDTO {
  private Long mno;
  private String title;
  @Builder.Default // @AllArgsConstructor가 없으면 에러,기본값초기화
  private List<MovieImageDTO> imageDTOList = new ArrayList<>();
  private double avg;
  private int reviewCnt;
  private LocalDateTime regDate;
  private LocalDateTime modDate;
}
  @Override
  public PageResultDTO<MovieDTO, Object[]> getList(PageRequestDTO pageRequestDTO) {
    Pageable pageable = pageRequestDTO.getPageable(Sort.by("mno").descending());
//  Page<Movie> result = movieRepository.findAll(pageable);
    Page<Object[]> result = movieRepository.getListPageImg(pageable);
    return null;
  }

findAll 메소드는 주로 데이터베이스에서 모든 레코드를 가져오는 데 사용됩니다.

  // 아래와 같은 경우 mi를 찾기 위해서 review 카운트 만큼 반복횟수도 늘어나는 문제점
  // mi의 inum이 가장 낮은 이미지 번호가 출력된다.
  // 영화와 영화이미지,리뷰의 평점과 댓글 갯수 출력
  @Query("select m, mi, avg(coalesce(r.grade, 0)), count(distinct r) from Movie m " +
      "left outer join MovieImage mi on mi.movie = m " +
      "left outer join Review     r  on r.movie  = m group by m ")
  Page<Object[]> getListPageImg(Pageable pageable);

Spring 컨테이너는 스프링 애플리케이션에서 객체의 생성, 초기화, 구성, 및 라이프사이클을 관리하는 중앙 구성 요소입니다

Spring 컨테이너는 설정 파일(XML, JavaConfig, Annotation)을 기반으로 애플리케이션에서 사용되는 빈(Bean)을 정의하고 관리합니다. 이 빈들은 애플리케이션 실행 중에 필요한 객체들입니다.

스프링 컨테이너( ⇒ 어플리케이션 컨텍스트) 에서 사용되는 객체(javabean)의 생명주기 까지 관리해주는 것을 이라고 함

JavaBean의 주요 특징

JavaBean은 Java 언어에서 재사용 가능한 컴포넌트를 만드는 데 사용되는 클래스의 규약을 의미합니다. 특히, JavaBean은 Java 기반 애플리케이션에서 데이터 객체로 자주 사용됩니다. Spring 프레임워크에서도 다양한 곳에서 JavaBean을 사용합니다.

프록시(Proxy)

프록시는 대리자 역할을 하는 디자인 패턴으로, 실제 객체에 대한 접근을 제어하거나, 추가적인 기능을 제공하기 위해 사용됩니다. Spring에서는 주로 동적 프록시(dynamic proxy)를 사용하여 AOP와 트랜잭션 관리 등의 기능을 구현합니다.

  @Override
  public PageResultDTO<MovieDTO, Object[]> getList(PageRequestDTO pageRequestDTO) {
    Pageable pageable = pageRequestDTO.getPageable(Sort.by("mno").descending());
//  Page<Movie> result = movieRepository.findAll(pageable);
    Page<Object[]> result = movieRepository.getListPageImg(pageable);
    Function<Object[], MovieDTO> fn = new Function<Object[], MovieDTO>() {
      @Override
      public MovieDTO apply(Object[] objects) {
        return entityToDto((Movie) objects[0],
            (List<MovieImage>)(Arrays.asList((MovieImage)objects[1])),
            (Double) objects[2],
            (int) objects[3]);
      }
    };
    return new PageResultDTO<>(result, fn);
  }

컨트롤러 생성하기

@GetMapping("/list")
  public void list(PageRequestDTO pageRequestDTO , Model model){
    model.addAttribute("result", movieService.getList(pageRequestDTO));
}
<tbody>
      <tr th:each="movieDTO : ${pageResultDTO.dtoList}" style="cursor:pointer;"
          th:onclick="goRead([[${movieDTO.bno}]],[[${pageResultDTO.page}]],[[${pageRequestDTO.type}]], [[${pageRequestDTO.keyword}]])"
          onmouseover="this.style.background='#d6e6ff'"
          onmouseout="this.style.background='white'">
        <th scope="row">[[${movieDTO.mno}]]</th>
        <td>
          <img th:src="|@{/display(fileName=${movieDTO.imageDTOList[0].getThumbnailURL()})}|">
          [[${movieDTO.title}]]
        </td>
        <td><b th:text="${movieDTO.reviewCnt}"></b></td>
        <td><b th:text="${movieDTO.avg}"></b></td>
        <td>[[${#temporals.format(movieDTO.regDate, 'yyyy/MM/dd hh:mm')}]]</td>
      </tr>
      </tbody>

Thymeleaf 구문

  1. th:src:
    • 이 속성은 Thymeleaf에서 HTML 태그의 속성 값을 동적으로 설정하는 데 사용됩니다. 여기서 th:srcimg 태그의 src 속성 값을 설정합니다.
    • |@{/display(fileName=${movieDTO.imageDTOList[0].getThumbnailURL()})}|: 이 부분은 URL을 동적으로 생성합니다. |...| 구문은 Thymeleaf에서 URL을 표현하는 방식입니다.
      • @{/display(...)}: 여기서 @{} 구문은 컨텍스트 경로를 포함한 URL을 생성하는 데 사용됩니다.
      • ${movieDTO.imageDTOList[0].getThumbnailURL()}: movieDTO 객체의 imageDTOList의 첫 번째 요소의 getThumbnailURL() 메서드를 호출하여 해당 이미지의 썸네일 URL을 가져옵니다.
      • 최종적으로 fileName이라는 파라미터로 URL의 쿼리 스트링에 추가됩니다. 예를 들어, /display?fileName=someThumbnailUrl과 같은 URL이 생성됩니다.
  2. [[${movieDTO.title}]]:
    • 이 부분은 Thymeleaf에서 표현식을 사용하여 텍스트를 출력하는 방식입니다. [[...]] 구문은 텍스트를 안전하게 HTML 인코딩하여 출력합니다.
    • ${movieDTO.title}movieDTO 객체의 title 속성을 참조합니다. 즉, 해당 영화의 제목을 HTML로 출력합니다.
  @Override
  public MovieDTO getMovie(Long mno) {
    List<Object[]> result = movieRepository.getMovieWithAll(mno);
    Movie movie = (Movie)result.get(0)[0];
    List<MovieImage> movieImages = new ArrayList<>();
    result.forEach(objects -> movieImages.add((MovieImage) objects[1]));
    Double avg = (Double) result.get(0)[2];
    Long reviewCnt = (Long) result.get(0)[3];
    return entityToDto(movie,movieImages,avg,reviewCnt);
  }
@Query("select m, mi, avg(coalesce(r.grade, 0)), count(r) " +
      "from Movie m left outer join MovieImage mi on mi.movie=m " +
      "left outer join Review r on r.movie = m " +
      "where m.mno = :mno group by mi ")
  List<Object[]> getMovieWithAll(Long mno); //특정 영화 조회

ReviewDTO 생성하기

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ReviewDTO {
  private Long reviewnum;
  private Long mno; //Moive
  private Long mid; //Member
  private String email;
  private int grade; //별점
  private String text; //한줄평
  private LocalDateTime regDate, modDate;
}
@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString(exclude = {"movie", "member"})
public class Review extends BasicEntity {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long reviewnum;

  @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;} //추가
}

ReviewService Interface 생성하기

public interface ReviewService {
  List<ReviewDTO> getListofMoive(Long mno);
  Long register(ReviewDTO reviewDTO);
  void modify(ReviewDTO reviewDTO);
  void remove(Long reviewnum);
  public default Review dtoToEntity(ReviewDTO reviewDTO) {
    Review review = Review.builder()
        .reviewnum(reviewDTO.getReviewnum())
        .movie(Movie.builder().mno(reviewDTO.getMno()).build())
        .member(Member.builder().mid(reviewDTO.getMid()).build())
        .grade(reviewDTO.getGrade())
        .text(reviewDTO.getText())
        .build();
    return review;
  }

  public default ReviewDTO entityToDto(Review review) {
    ReviewDTO reviewDTO = ReviewDTO.builder()
        .reviewnum(review.getReviewnum())
        .text(review.getText())
        .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;
  }
}

ReviewServiceImpl 에 ReviewService implemets 해주기

ctrl+ o 해서 override 생성하기


@Service
@Log4j2
@RequiredArgsConstructor
public class ReviewServiceImpl implements ReviewService {
  private final ReviewRepository reviewRepository;

  @Override
  public List<ReviewDTO> getListofMoive(Long mno) {
    List<Review> result =reviewRepository.findByMovie(Movie.builder().mno(mno).build());
    return result.stream().map(review -> entityToDto(review)).collect(Collectors.toList());
  }

  @Override
  public Long register(ReviewDTO reviewDTO) {
    Review review = dtoToEntity(reviewDTO);
    reviewRepository.save(review);
    return review.getReviewnum();
  }

  @Override
  public void modify(ReviewDTO reviewDTO) {
    Optional<Review> result = reviewRepository.findById(reviewDTO.getReviewnum());
    if(result.isPresent()){
      Review review = result.get();
      review.changeGrade(reviewDTO.getGrade());
      review.changeText(reviewDTO.getText());
      reviewRepository.save(review);
    }
  }
  @Override
  public void remove(Long reviewnum) {
    Optional<Review> result = reviewRepository.findById(reviewnum);
    if(result.isPresent()){
      reviewRepository.deleteById(reviewnum);
    }
  }
}

ReviewController 생성하기 : 리뷰를 비동기적으로 리뷰를 생성하고 동작할 수있게함

@RestController는 RESTful 웹 서비스 개발을 쉽게 하고, 코드를 간결하게 유지하며, JSON 등의 데이터를 자동으로 직렬화하는 데 도움을 줍니다. Spring MVC의 RESTful 지원 기능을 최대한 활용하려면 @RestController를 사용하는 것이 좋습니다.

@RequestBody@RequestParam은 Spring MVC에서 클라이언트의 HTTP 요청을 처리할 때 요청 데이터의 전달 방식을 정의하는 어노테이션입니다. 두 어노테이션은 각각 다른 방식으로 요청 데이터를 추출합니다.

@RequestBody는 HTTP 요청의 본문(body)에 담긴 데이터를 Java 객체로 변환하여 메서드의 매개변수로 전달하는 데 사용됩니다. 주로 JSON이나 XML 형식의 데이터를 처리할 때 사용됩니다.

@RequestParam은 HTTP 요청의 쿼리 파라미터를 메서드의 매개변수로 전달하는 데 사용됩니다. URL의 쿼리 스트링 또는 폼 데이터를 처리할 때 유용합니다.

주요 차이점

  • 데이터 위치:
    • @RequestBody는 요청 본문에서 데이터를 추출합니다. 주로 JSON, XML 등의 데이터 포맷을 처리할 때 사용됩니다.
    • @RequestParam은 쿼리 파라미터(또는 URL의 파라미터)에서 데이터를 추출합니다. 주로 URL 쿼리 스트링 또는 폼 데이터에서 값을 추출할 때 사용됩니다.
  • 데이터 형식:
    • @RequestBody는 복잡한 데이터 구조를 처리할 수 있으며, JSON이나 XML과 같은 포맷을 Java 객체로 변환합니다.
    • @RequestParam은 단순한 key-value 쌍의 데이터를 처리합니다.
  • 용도:
    • @RequestBody는 주로 RESTful API에서 클라이언트가 보내는 복잡한 데이터를 처리할 때 사용됩니다.
    • @RequestParam은 URL의 쿼리 파라미터나 폼 데이터를 간단히 처리할 때 사용됩니다.

이 두 어노테이션은 요청 데이터를 처리하는 데 있어 각기 다른 역할을 하며, 사용하려는 데이터의 형태와 요구 사항에 따라 적절한 어노테이션을 선택하여 사용하는 것이 중요합니다.

@RestController //repregentation 대의적인 상태를 전송하는 것
@Log4j2
@RequiredArgsConstructor
@RequestMapping("/reviews")
public class ReviewController {
  private final ReviewService reviewService;

  @GetMapping(value = "/{mno}/all", produces = MediaType.APPLICATION_JSON_VALUE)
  public ResponseEntity<List<ReviewDTO>> getList(@PathVariable("mno") Long mno ) {
    log.info("mno: "+mno);
    List<ReviewDTO> reviewDTOList = reviewService.getListofMoive(mno);
    return new ResponseEntity<>(reviewDTOList, HttpStatus.OK);
  }

  @PostMapping("/{mno}")
  //@RequestBody : form이나, json 데이터를 전송 받을 때
  //@RequestParam : 변수로 데이터를 전송 받을 때
  public ResponseEntity<Long> register(@RequestBody ReviewDTO reviewDTO ) {
    log.info(reviewDTO);
    Long reviewnum = reviewService.register(reviewDTO);
    return new ResponseEntity<>(reviewnum, HttpStatus.OK);
  }

  @PutMapping({"/{mno}/{reviewnum}"})
  public ResponseEntity<Long> modify(@PathVariable Long reviewnum, 
                                     @RequestBody ReviewDTO reviewDTO ) {
    log.info(reviewDTO);
    reviewService.modify(reviewDTO);
    return new ResponseEntity<>(reviewnum, HttpStatus.OK);
  }

  @DeleteMapping({"/{mno}/{reviewnum}"})
  public ResponseEntity<Long> delete(@PathVariable Long reviewnum ) {
    log.info(">>"+reviewnum);
    reviewService.remove(reviewnum);
    return new ResponseEntity<>(reviewnum, HttpStatus.OK);
  }
}

Modify 버튼을 눌렀을 때 기존에있던 데이터는 그대로 두고 추가 및 수정이 되어야함

      fileInput.onchange = function() {
        let formData = new FormData();
        const fileName = fileInput.value.split("\\").pop();
        let label = document.querySelector("#custom-label")
        label.innerHTML =
          (fileInput.files.length-1) == 0 ? "" :
                `${fileName} 외 ${fileInput.files.length-1}개`
        let appended = false; // 파일이 잘 추가되는지 확인
        for(let i=0;i<fileInput.files.length;i++) {
          if(!checkExtension(fileInput.files[i].name, fileInput.files.size)) {
            label.innerHTML = ''; fileInput.value = '';
            appended = false;break;
          }
          formData.append("uploadFiles", fileInput.files[i])
          appended = true;
        }
        if(!appended) return;
        for(const value of formData.values()) console.log(value)
        const url = /*[[@{/uploadAjax}]]*/'url'
        fetch(url, {
          method: 'POST', body: formData, dataType: 'json'
        })
        .then(res => res.json())
        .then(json => {console.log(json);showResult(json);})
        .catch(err => console.log('Error: ', err))
      }
function showResult(arr) {
        const uploadUL = document.querySelector(".uploadResult ul");
        let str = ""
        const url = /*[[@{/display}]]*/'url'
        for(let i=0;i<arr.length;i++){
          str += `<li data-name='${arr[i].fileName}' data-path='${arr[i].folderPath}'
          data-uuid='${arr[i].uuid}' data-file='${arr[i].imageURL}'><div>
          <button class="removeBtn" type="button">X</button>
          <img src="${url}?fileName=${arr[i].thumbnailURL}">
          </div></li>`
        }
        uploadUL.innerHTML = str;
        const removeBtns = document.querySelectorAll(".removeBtn");
        for(let i=0;i<removeBtns.length;i++){
          removeBtns[i].onclick = function() {
            const removeUrl = /*[[@{/removeFile?fileName=}]]*/'removeUrl'
            const targetLi = this.closest('li')
            const fileName = targetLi.dataset.file;
            fetch(removeUrl+fileName, {
              method: 'POST', dataType: 'json', fileName: fileName
            })
            .then(response => response.json())
            .then(json => {
              console.log(json)
              if(json === true) targetLi.remove()
              document.querySelector("#custom-label").innerHTML = ''
              document.querySelector("#fileInput").value = '';
            })
            .catch(err => console.log("Error occurred: ", err))
          }
        }
      }

uploadUL.innerHTML += str; 를 해주면 기존에 데이터에 추가 된다

해당 부분을 수정하기 위해서 btnModi.onclick 버튼의 내용을 수정 할 예정

   btnModi.onclick = e => {
        e.preventDefault() // 유효성검사 넣어줄 것!
        const title = document.querySelector("input[name='title']")
        if(title.value == "") {title.focus();return false;}

        let str = "";
        const liArr = document.querySelectorAll(".uploadResult ul li");
        for(let i=0;i<liArr.length;i++){
          str += `
            <input type="hidden" name="imageDTOList[${i}].imgName" value="${liArr[i].dataset.name}">
            <input type="hidden" name="imageDTOList[${i}].path" value="${liArr[i].dataset.path}">
            <input type="hidden" name="imageDTOList[${i}].uuid" value="${liArr[i].dataset.uuid}">
          `
        }
        document.querySelector(".box").innerHTML = str;
        /*if(fileInput.files.length == 0) {
          document.querySelector("#custom-label").innerHTML =
            "파일을 선택하세요!"
          fileInput.focus(); return false;
        }*/
        frmSend.submit();
      }

등록되지 않은 값이 있는 경우 스케줄러를 이용해서 자동으로 삭제하는 기능도 존재함

 function showResult(arr) {
        const uploadUL = document.querySelector(".uploadResult ul");
        let str = ""
        const url = /*[[@{/display}]]*/'url'
        for(let i=0;i<arr.length;i++){
          str += `<li class="new" data-name='${arr[i].fileName}' data-path='${arr[i].folderPath}'
          data-uuid='${arr[i].uuid}' data-file='${arr[i].imageURL}'><div>
          <button class="removeBtn" type="button">X new</button>
          <img src="${url}?fileName=${arr[i].thumbnailURL}">
          </div></li>`
        }

class에 new를 넣어주면서 새로 추가된 것인이 아닌지를 구분함

 let str = "";
        const liArr = document.querySelectorAll(".uploadResult ul li .new");
        for(let i=0;i<liArr.length;i++){
          str += `
            <input type="hidden" name="imageDTOList[${i}].imgName" value="${liArr[i].dataset.name}">
            <input type="hidden" name="imageDTOList[${i}].path" value="${liArr[i].dataset.path}">
            <input type="hidden" name="imageDTOList[${i}].uuid" value="${liArr[i].dataset.uuid}">
          `
        }

const liArr = document.querySelectorAll(".uploadResult ul li .new");

.new 클래스를 넣어줌

728x90
반응형

'PROGRAMING📚 > Spring📑' 카테고리의 다른 글

서버 사이드 랜더링 방식  (0) 2024.09.29
Spring / Spring boot  (1) 2024.09.26
CSRF - API 서버  (0) 2024.09.25
JSP(JavaServer Pages)  (1) 2024.09.25
Spring : 구글 로그인 세션 만들기  (1) 2024.09.25

댓글