반응형
영화 목록 처리
- 목록 처리와 함께 평균 평점 화면에 출력
영화 목록 처리
- 페이징을 위한 DTO 추가
: PageRequestDTO, PageResultDTO - 서비스계층에서 getList() 메서드를 생성하기 위해, 각 Object[]을 MovieDTO 하나의 객체로 처리하기 위해
: MovieDTO에 Double타입의 평점 평균과 리뷰의 개수 처리하는 파라미터와 날짜 관련 부분 추가 - MovieRepository에 저장된 영화와 평점 엔티티의 처리를 위해
: Object[]로 반환하는 getListPage() 메서드 이용해, MovieService, MovieServiceImpl 클래스 수정- Movie, MovieImage 객체
- double값으로 나오는 평균 평점
- Long타입의 리뷰 개수
- MovieController에 list() 메서드 추가하고, 목록 화면 구현
1. 페이징을 위한 DTO 추가
PageRequesDTO
package com.movie.boot4.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
//화면에 전달되는 게시물 목록 처리
@Builder
@AllArgsConstructor
@Data
public class PageRequestDTO {
private int page;
private int size;
//화면에 전달되는 목록을 위한 생성자 : 1페이지부터 시작하고, 10페이지 단위로 목록 처리를 의미
public PageRequestDTO(){
this.page=1;
this.size=10;
}
public Pageable getPageable(Sort sort){
return PageRequest.of(page -1, size, sort);
//정적으로 생성하기 위해 of를 사용하고, JPA에서 페이지는 0부터 시작하므로 page-1로 전달;
}
}
PageResultDTO
package com.movie.boot4.dto;
import lombok.Data;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
//화면에 필요한 결과 게시물 목록
@Data
//DTO -> Entity로 변환하기 때문에
public class PageResultDTO<DTO, EN> {
//DTO리스트
private List<DTO> dtoList;
//총 페이지 번호
private int totalPage;
//현재 페이지 번호
private int page;
//목록 사이즈
private int size;
//시작 페이지 번호, 끝 페이지 번호
private int start, end;
//이전, 다음
private boolean prev, next;
//페이지 번호 목록
private List<Integer> pageList;
//function 패키지에 있는 람다식으로 사용할 수 있는 편리한 함수형 메서드 :
//매개변수와 반환값 존재 여부에 따라 구분 / 조건식 표현해 참 거짓 반환
// Runnable -> run(), Supplier -> get(), Consumer -> accept(), Function -> apply() , Predicate -> test()
//Page<Entity>의 객체들을 DTO객체로 변환해서 담는 메서드
public PageResultDTO(Page<EN> result, Function<EN, DTO> fn){
//페이징 결과를 스트림에 담아서, fn을 이용해 람다식을 통해 변환하고, 최종연산으로 리스트로 반환
dtoList = result.stream().map(fn).collect(Collectors.toList());
totalPage = result.getTotalPages();
makePageList(result.getPageable());
}//Function 함수형 인터페이스를 사용하면, 제네릭으로 정의되어 있기 때문에 어떤 엔티티를 전달해도 재사용 가능
private void makePageList(Pageable pageable){
this.page = pageable.getPageNumber() + 1; // JPA가 전달한 페이지는 0부터 시작하므로 1을 추가
this.size = pageable.getPageSize();
//임시 끝 번호
int tempEnd = (int)(Math.ceil(page/10.0)) * 10;
start = tempEnd - 9;
prev = start > 1;
//끝 번호 처리 : 임시 끝 번호와 크기 비교해 실제 마지막 번호와 임시 끝번호 중 선택
end = totalPage > tempEnd ? tempEnd: totalPage;
next = totalPage > tempEnd;
//페이지 리스트
//기본형 int스트림을 스트림으로 변환하는 메서드 : mapToObj()와 boxed()
//int스트림을 이용해 범위 제한해 데이터를 추출하고, 다시 Stream<Integer>로 변환 후, 최종연산으로 담아서 리스트로 변환
pageList = IntStream.rangeClosed(start, end).boxed().collect(Collectors.toList());
//pageList는 List<Integer>
}
}
2. MovieDTO에 Double타입의 평점 평균과 리뷰의 개수 처리하는 파라미터와 날짜 관련 부분 추가
변경 전의 MovieDTO
package com.movie.boot4.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MovieDTO {
private Long mno;
private String title;
//특정 필드를 특정 값으로 초기화하기위한 어노테이션
@Builder.Default
private List<MovieImageDTO> imageDTOList=new ArrayList<>();
//영화 이미지도 같이 수집해 전달하므로, 내부에 리스트를 이용해 수집한다.
}
변경 후의, MovieDTO
package com.movie.boot4.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MovieDTO {
private Long mno;
private String title;
//특정 필드를 특정 값으로 초기화하기위한 어노테이션
@Builder.Default
private List<MovieImageDTO> imageDTOList=new ArrayList<>();
//영화 이미지도 같이 수집해 전달하므로, 내부에 리스트를 이용해 수집한다.
//영화의 평균 평점
private double avg;
//리뷰 수 jpa의 count()
private int reviewCnt;
//BaseEntity에 속한 속성들
private LocalDateTime regDate;
private LocalDateTime modDate;
}
3. Object[]로 반환하는 getListPage() 메서드 이용해,
MovieService, MovieServiceImpl 클래스 수정
(1) 엔티티 객체를 DTO로 변환하는 entitiesToDto()
- Movie 엔티티
- List<MovieImage> 엔티티
- 조회 화면에서 여러 개의 MovieImage를 처리하기 위해
- Double 타입의 평점 평균
- Long 타입의 리뷰 개수
(2) 컨트롤러 호출시 사용할 getList()과 getList() 구현
MovieService의 getList()
package com.movie.boot4.service;
import com.movie.boot4.dto.MovieDTO;
import com.movie.boot4.dto.MovieImageDTO;
import com.movie.boot4.dto.PageRequestDTO;
import com.movie.boot4.dto.PageResultDTO;
import com.movie.boot4.entity.Movie;
import com.movie.boot4.entity.MovieImage;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public interface MovieService {
//영화 등록을 위한 메서드
Long register(MovieDTO movieDTO);
//영화를 JPA로 처리하기위한 메서드
//: Movie와 함께 포함된 MovieImage를 함께 반환 -> Map 이용
default Map<String, Object> dtoToEntity(MovieDTO movieDTO){
//movie, movie 객체
//imgList, List<MovieImageDTO>
Map<String, Object> entityMap = new HashMap<>();
Movie movie = Movie.builder()
.mno(movieDTO.getMno())
.title(movieDTO.getTitle())
.build();
entityMap.put("movie", movie);
List<MovieImageDTO> imageDTOList = movieDTO.getImageDTOList();
//MovieImageDTO 처리
//: 이미지가 존재할 경우에
if(imageDTOList !=null && imageDTOList.size()>0){
List<MovieImage> movieImageList=imageDTOList.stream()
.map(movieImageDTO -> {
MovieImage movieImage=MovieImage.builder()
.path(movieImageDTO.getPath())
.imgName(movieImageDTO.getImgName())
.uuid(movieImageDTO.getUuid())
.movie(movie)
.build();
return movieImage;
}).collect(Collectors.toList());
entityMap.put("imgList",movieImageList);
}
return entityMap;
}
//getListPage()를 이용해 목록 처리하는 메서드
//: PageRequestDTO를 받아, PageResultDTO로 변환하는데, MovieDTO를 이용해 Object[]로 반환
PageResultDTO<MovieDTO, Object[]> getList(PageRequestDTO requestDTO);
//엔티티를 DTO로 변환하는 메서드
default MovieDTO entitiesToDTO(Movie movie, List<MovieImage> movieImages, Double avg, Long reviewCnt){
MovieDTO movieDTO = MovieDTO.builder()
.mno(movie.getMno())
.title(movie.getTitle())
.regDate(movie.getRegDate())
.modDate(movie.getModDate())
.build();
List<MovieImageDTO> movieImageDTOList = movieImages.stream()
.map(movieImage -> {
return MovieImageDTO.builder()
.imgName(movieImage.getImgName())
.path(movieImage.getPath())
.uuid(movieImage.getUuid())
.build();
}).collect(Collectors.toList());
movieDTO.setImageDTOList(movieImageDTOList);
movieDTO.setAvg(avg);
movieDTO.setReviewCnt(reviewCnt.intValue());
return movieDTO;
}
}
MovieServiceImple의 getList() 구현
package com.movie.boot4.service;
import com.movie.boot4.dto.MovieDTO;
import com.movie.boot4.dto.PageRequestDTO;
import com.movie.boot4.dto.PageResultDTO;
import com.movie.boot4.entity.Movie;
import com.movie.boot4.entity.MovieImage;
import com.movie.boot4.repository.MovieImageRepository;
import com.movie.boot4.repository.MovieRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@Service
@Log4j2
//final을 항상 의존성 주입하는 어노테이션
@RequiredArgsConstructor
public class MovieServiceImpl implements MovieService{
private final MovieRepository movieRepository;
private final MovieImageRepository imageRepository;
//영화 객체와 영화 이미지 객체 모두 함꼐 등록해야하기 때문에
@Transactional
@Override
public Long register(MovieDTO movieDTO){
Map<String, Object> entityMap= dtoToEntity(movieDTO);
//Map에 저장된 movie 객체
Movie movie = (Movie) entityMap.get("movie");
//Map에 저장된 imglist 리스트
List<MovieImage> movieImageList=(List<MovieImage>) entityMap.get("imgList");
//영화 엔티티 저장 후
movieRepository.save(movie);
//forEach문과 스트림을 이용해
//: 영화 이미지 엔티티 저장
movieImageList.forEach(movieImage -> {
imageRepository.save(movieImage);
});
return null;
}
//getList() 구현
@Override
public PageResultDTO<MovieDTO, Object[]> getList(PageRequestDTO requestDTO){
//requestDTO에서 페이징 메서드로 mno기준 정렬 수행해 페이징 객체에 저장
Pageable pageable = requestDTO.getPageable(Sort.by("mno")
.descending());
//페이징 객체를 전달해 영화 목록을 받아서 Page에 배열로 저장
Page<Object[]> result = movieRepository.getListPage(pageable);
//배열에 있는 값으로 엔티티를 가져와 DTO 만들기
Function<Object[], MovieDTO>fn =(arr ->
entitiesToDTO(
(Movie) arr[0],
(List<MovieImage>) (Arrays.asList((MovieImage)arr[1])),
(Double) arr[2],
(Long) arr[3])
);
return new PageResultDTO<>(result, fn);
}
}
4.MovieController에 list() 메서드 추가하고, 목록 화면 구현
(1) list() 메서드 작성
@GetMapping("/list")
public void list(PageRequestDTO pageRequestDTO, Model model){
log.info("pageReQuestDTO : "+pageRequestDTO);
model.addAttribute("result", movieService.getList(pageRequestDTO));
}
(2) list.html 목록 화면 구현
- 타임리프를 이용한 fragment 사용해 레이아웃 구성
- h1클래스를 이용해 제목 구성하고, 등록 버튼 만들기
- 목록을 위한 폼만들기
- 테이블로 원하는 thead에 목록 컬럼 만들고
- tbody에 반복문 이용해 목록 구성
- 반복문 범위 <th scope: "row"> </th>
- 타임리프의 if문을 이용해 이미지 있을 때만 출력 [리스트가 0보다 클때, 실제 이미지 존재할 때]
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content} )}">
<th:block th:fragment="content">
<h1 class="mt-4">Movie List Page
<span>
<a th:href="@{/movie/register}">
<button type="button" class="btn btn-outline-primary">REGISTER
</button>
</a>
</span>
</h1>
<form action="/movie/list" method="get" id="searchForm">
<input type="hidden" name="page" value="1">
</form>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Picture</th>
<th scope="col">Review Count</th>
<th scope="col">AVG Rating</th>
<th scope="col">Regdate</th>
</tr>
</thead>
<tbody>
<tr th:each="dto : ${result.dtoList}" >
<th scope="row">
<a th:href="@{/movie/read(mno = ${dto.mno}, page= ${result.page})}">
[[${dto.mno}]]
</a>
</th>
<td><img th:if="${dto.imageDTOList.size() > 0 && dto.imageDTOList[0].path != null }"
th:src="|/display?fileName=${dto.imageDTOList[0].getThumbnailURL()}|" >[[${dto.title}]]</td>
<td><b>[[${dto.reviewCnt}]]</b></td>
<td><b>[[${dto.avg}]]</b></td>
<td>[[${#temporals.format(dto.regDate, 'yyyy/MM/dd')}]]</td>
</tr>
</tbody>
</table>
<script th:inline="javascript">
</script>
</th:block>
</th:block>
페이징 만들기
<ul class="pagination h-100 justify-content-center align-items-center">
<li class="page-item " th:if="${result.prev}">
<a class="page-link" th:href="@{/movie/list(page= ${result.start -1})}" tabindex="-1">Previous</a>
</li>
<li th:class=" 'page-item ' + ${result.page == page?'active':''} " th:each="page: ${result.pageList}">
<a class="page-link" th:href="@{/movie/list(page = ${page})}">
[[${page}]]
</a>
</li>
<li class="page-item" th:if="${result.next}">
<a class="page-link" th:href="@{/movie/list(page= ${result.end + 1} )}">Next</a>
</li>
</ul>
+) 모달 창을 이용해 등록 후에 보여주는 작업 수행
반응형
'Server Programming > Spring Boot Backend Programming' 카테고리의 다른 글
[Spring 부트 - 영화 리뷰 프로젝트] 6. Ajax로 영화 리뷰 처리 (1) 리뷰 구성 (0) | 2022.10.18 |
---|---|
[Spring 부트 - 영화 리뷰 프로젝트] 5. 영화 조회 처리 (0) | 2022.10.18 |
[Spring 부트 - 영화 리뷰 프로젝트] 3. 영화 등록 처리 (0) | 2022.10.18 |
[Spring 부트 - 영화 리뷰 프로젝트] 2. 파일 업로드 처리 (2) 섬네일 이미지를 통한 화면 처리와 파일 삭제 (0) | 2022.10.18 |
[Spring 부트 - 영화 리뷰 프로젝트] 2. 파일 업로드 처리 (1) Ajax를 통한 JSON으로 이미지 업로드 (0) | 2022.10.18 |