SpringBoot with JPAプロジェクト(M:N)5.映画の登録、登録された文章のリストの処理
129834 ワード
📚 勉強した本:コード学習を用いたSpring Boot Webプロジェクト
▼githubアドレス:https://github.com/qkralswl689/LearnFromCode/tree/main/mreview2022
映画(Movie)の登録と修正は を含む.メンバーは既存のメンバーが存在すると仮定し、DBのメンバーは を使用する.会員は、特定の映画閲覧ページで採点と自分の鑑賞記録をコメント として記録することができる.クエリー画面では、メンバーは、自分が記録したコメント内容 を変更/削除することができる.
2.映画登録処理
注意点
->ムービーオブジェクトとムービーイメージオブジェクトを一緒に処理!一度に2種類のオブジェクトを返さなければならないので、Mapタイプを使用して返されます.
=>追加されたdtoEntity()は、MapタイプでMovieオブジェクトとMovieImageオブジェクトのリスト を処理する.
dtoEntity()を使用して返されるオブジェクト処理save()
MovieControllerは、MovieServiceタイプオブジェクトのregister()を呼び出すために、POST方式で渡されるパラメータをMovieDTOに収集する
*カタログページは後ほど記入
画面上のSubmitボタンをクリックしたときのタスクの処理順序
1)各画像liタグのdata-属性を読み出す
2)取得した属性値を使用してフォームタグ内にinputtype="hidden"タグを生成する
3)inputtype="hidden"の名前にインデックス番号を付けて処理運転画面
現在リスト画面が実現していませんが、Submitボタンをクリックするとエラー画面が表示されますが、DBを検索すると保存がわかります
-プロセス全体
1)ファイルアップロード後、liラベルが設定されます
2)Submitボタンをクリックしてフォームラベル内にラベルを作成する
3)MovieControllerでPOST方式で伝送されたデータはMovieImageDTOで収集される
4)MovieServiceでは、MovieImageDTOはMovieエンティティオブジェクト内のMovieImageとみなされる
5)JPAにてsave()処理を行いDBに保存する
3.リスト処理と平均採点
entitiesToDto()が受信したパラメータ.
->Movieエンティティ、Listエンティティ、Doubleタイプの平均スコア、Longタイプのコメント数 リストエンティティリスト: クエリー画面で複数のMovieImageを処理するために使用される理由
リストから出力されたデータがモデルに含まれているため、モデル結果を使用して出力されます.運転結果
▼githubアドレス:https://github.com/qkralswl689/LearnFromCode/tree/main/mreview2022
1.応用映画/評論項目
2.映画登録処理
2-1.DTOクラスの作成
MovieDTOはMovieクラスに基づいて作成され、MovieDTOはスクリーン上で同時に映画画像を収集し、伝達する必要があるため、内部でリストを使用して収集される.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<>();
}
2-2.MovieImageDTOクラスの作成
MovieImageDTOクラスも追加されます.MovieDTOクラスにはアップロードファイルの情報が含まれている必要があります.import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class MovieImageDTO {
private String uuid;
private String imgName;
private String path;
public String getImageURL(){
try {
return URLEncoder.encode(path + "/" + uuid + "_" + imgName,"UTF-8");
}catch (UnsupportedEncodingException e){
e.printStackTrace();
}
return "";
}
public String getThumbnailURL(){
try {
return URLEncoder.encode(path+"/s_" + uuid + "_" +imgName ,"UTF-8");
}catch (UnsupportedEncodingException e){
e.printStackTrace();
}
return "";
}
}
2-3.サービスインタフェースの作成
MovieDTOは、MovieをJPAとして扱うためにMovieオブジェクトに変換する必要があるため、MovieServiceにdtoEntity()を追加します.
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<>();
}
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class MovieImageDTO {
private String uuid;
private String imgName;
private String path;
public String getImageURL(){
try {
return URLEncoder.encode(path + "/" + uuid + "_" + imgName,"UTF-8");
}catch (UnsupportedEncodingException e){
e.printStackTrace();
}
return "";
}
public String getThumbnailURL(){
try {
return URLEncoder.encode(path+"/s_" + uuid + "_" +imgName ,"UTF-8");
}catch (UnsupportedEncodingException e){
e.printStackTrace();
}
return "";
}
}
->ムービーオブジェクトとムービーイメージオブジェクトを一緒に処理!一度に2種類のオブジェクトを返さなければならないので、Mapタイプを使用して返されます.
=>追加されたdtoEntity()は、MapタイプでMovieオブジェクトとMovieImageオブジェクトのリスト
package com.example.mreview2022.service;
import com.example.mreview2022.dto.MovieDTO;
import com.example.mreview2022.dto.MovieImageDTO;
import com.example.mreview2022.entity.Movie;
import com.example.mreview2022.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);
default Map<String, Object> dtoToEntity(MovieDTO movieDTO) { //Map 타입으로 변환
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;
}
}
2-4.ServiceImplクラスの作成
dtoEntity()を使用して返されるオブジェクト処理save()
package com.example.mreview2022.service;
import com.example.mreview2022.dto.MovieDTO;
import com.example.mreview2022.entity.Movie;
import com.example.mreview2022.entity.MovieImage;
import com.example.mreview2022.repository.MovieImageRepository;
import com.example.mreview2022.repository.MovieRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class MovieServiceImpl implements MovieService {
private final MovieRepository movieRepository; // final
private final MovieImageRepository imageRepository; // final
@Transactional
@Override
public Long register(MovieDTO movieDTO) {
Map<String,Object> entityMap = dtoToEntity(movieDTO);
Movie movie = (Movie) entityMap.get("movie");
List<MovieImage> movieImageList = (List<MovieImage>) entityMap.get("imgList");
movieRepository.save(movie);
movieImageList.forEach(movieImage -> {
imageRepository.save(movieImage);
});
return movie.getMno();
}
}
2-5.コントローラの作成
MovieControllerは、MovieServiceタイプオブジェクトのregister()を呼び出すために、POST方式で渡されるパラメータをMovieDTOに収集する
*カタログページは後ほど記入
package com.example.mreview2022.controller;
import com.example.mreview2022.dto.MovieDTO;
import com.example.mreview2022.service.MovieService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
@RequestMapping("/movie")
@RequiredArgsConstructor
public class MovieController {
private final MovieService movieService; //final
@GetMapping("/register")
public void register(){
}
@PostMapping("/register")
public String register(MovieDTO movieDTO, RedirectAttributes redirectAttributes){
Long mno = movieService.register(movieDTO);
redirectAttributes.addFlashAttribute("msg",mno);
return "redirect:/movie/list";
}
}
2-6.Htmlの作成
画面上のSubmitボタンをクリックしたときのタスクの処理順序
1)各画像liタグのdata-属性を読み出す
2)取得した属性値を使用してフォームタグ内にinputtype="hidden"タグを生成する
3)inputtype="hidden"の名前にインデックス番号を付けて処理
<!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 Register Page</h1>
<form th:action="@{/movie/register}" th:method="post" >
<div class="form-group">
<label >Title</label>
<input type="text" class="form-control" name="title" placeholder="Enter Title">
</div>
<div class="form-group fileForm">
<label >Image Files</label>
<div class="custom-file">
<input type="file" class="custom-file-input files" id="fileInput" multiple>
<label class="custom-file-label" data-browse="Browse"></label>
</div>
</div>
<div class="box">
</div>
<style>
.uploadResult {
width: 100%;
background-color: gray;
margin-top: 10px;
}
.uploadResult ul {
display: flex;
flex-flow: row;
justify-content: center;
align-items: center;
vertical-align: top;
overflow: auto;
}
.uploadResult ul li {
list-style: none;
padding: 10px;
margin-left: 2em;
}
.uploadResult ul li img {
width: 100px;
}
</style>
<div class="uploadResult">
<ul>
</ul>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<script>
$(document).ready(function(e) {
var regex = new RegExp("(.*?)\.(exe|sh|zip|alz|tiff)$");
var maxSize = 10485760; //10MB
function checkExtension(fileName, fileSize){
if(fileSize >= maxSize){
alert("파일 사이즈 초과");
return false;
}
if(regex.test(fileName)){
alert("해당 종류의 파일은 업로드할 수 없습니다.");
return false;
}
return true;
}
$(".custom-file-input").on("change", function() {
var fileName = $(this).val().split("\\").pop();
$(this).siblings(".custom-file-label").addClass("selected").html(fileName);
var formData = new FormData();
var inputFile = $(this);
var files = inputFile[0].files;
var appended = false;
for (var i = 0; i < files.length; i++) {
if(!checkExtension(files[i].name, files[i].size) ){
return false;
}
console.log(files[i]);
formData.append("uploadFiles", files[i]);
appended = true;
}
//upload를 하지 않는다.
if (!appended) {return;}
for (var value of formData.values()) {
console.log(value);
}
//실제 업로드 부분
//upload ajax
$.ajax({
url: '/uploadAjax',
processData: false,
contentType: false,
data: formData,
type: 'POST',
dataType:'json',
success: function(result){
console.log(result);
showResult(result);
},
error: function(jqXHR, textStatus, errorThrown){
console.log(textStatus);
}
}); //$.ajax
}); //end change event
function showResult(uploadResultArr){
var uploadUL = $(".uploadResult ul");
var str ="";
$(uploadResultArr).each(function(i, obj) {
str += "<li data-name='" + obj.fileName + "' data-path='"+obj.folderPath+"' data-uuid='"+obj.uuid+"'>";
str + " <div>";
str += "<button type='button' data-file=\'" + obj.imageURL + "\' "
str += "class='btn-warning btn-sm'>X</button><br>";
str += "<img src='/display?fileName=" + obj.thumbnailURL + "'>";
str += "</div>";
str + "</li>";
});
uploadUL.append(str);
}
$(".uploadResult ").on("click", "li button", function(e){
console.log("delete file");
var targetFile = $(this).data("file");
var targetLi = $(this).closest("li");
$.ajax({
url: '/removeFile',
data: {fileName: targetFile},
dataType:'text',
type: 'POST',
success: function(result){
alert(result);
targetLi.remove();
}
}); //$.ajax
});
//prevent submit
$(".btn-primary").on("click", function(e) {
e.preventDefault();
var str = "";
$(".uploadResult li").each(function(i,obj){
var target = $(obj);
str += "<input type='hidden' name='imageDTOList["+i+"].imgName' value='"+target.data('name') +"'>";
str += "<input type='hidden' name='imageDTOList["+i+"].path' value='"+target.data('path')+"'>";
str += "<input type='hidden' name='imageDTOList["+i+"].uuid' value='"+target.data('uuid')+"'>";
});
//태그들이 추가된 것을 확인한 후에 comment를 제거
$(".box").html(str);
$("form").submit();
});
}); //document ready
</script>
</th:block>
</th:block>
現在リスト画面が実現していませんが、Submitボタンをクリックするとエラー画面が表示されますが、DBを検索すると保存がわかります
-プロセス全体
1)ファイルアップロード後、liラベルが設定されます
2)Submitボタンをクリックしてフォームラベル内にラベルを作成する
3)MovieControllerでPOST方式で伝送されたデータはMovieImageDTOで収集される
4)MovieServiceでは、MovieImageDTOはMovieエンティティオブジェクト内のMovieImageとみなされる
5)JPAにてsave()処理を行いDBに保存する
3.リスト処理と平均採点
3-1.ページ処理
N:1プロジェクトで使用するPageRequestDTOとPageResultDTOを追加
3-2.ムービーDTOの変更
MovieServiceのgetList()は、Movie、MovieImage、Double、Longをオブジェクトとする[]をリストに並べる形式です
各オブジェクト[]をMovieDTOという名前のオブジェクトとして処理する必要があります.MovieDTOにはDoubleタイプのスコア平均とコメント処理個数のパラメータと日付に関連する部分も追加されています.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;
private LocalDateTime regDate;
private LocalDateTime modDate;
}
3-3.サービスの変更
JPAで生成されたエンティティオブジェクトと、Double、Long等値をMovieDTOに変換したエンティティsToDto()を追加し、コントローラ呼び出し時に使用するgetList()を追加します.
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;
private LocalDateTime regDate;
private LocalDateTime modDate;
}
->Movieエンティティ、List
import com.example.mreview2022.dto.MovieDTO;
import com.example.mreview2022.dto.MovieImageDTO;
import com.example.mreview2022.dto.PageRequestDTO;
import com.example.mreview2022.dto.PageResultDTO;
import com.example.mreview2022.entity.Movie;
import com.example.mreview2022.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);
PageResultDTO<MovieDTO,Object[]> getList(PageRequestDTO requestDTO); // 목록처리
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;
}
}
3-4.ServiceImplの変更
import com.example.mreview2022.dto.MovieDTO;
import com.example.mreview2022.dto.PageRequestDTO;
import com.example.mreview2022.dto.PageResultDTO;
import com.example.mreview2022.entity.Movie;
import com.example.mreview2022.entity.MovieImage;
import com.example.mreview2022.repository.MovieImageRepository;
import com.example.mreview2022.repository.MovieRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@Service
@RequiredArgsConstructor
public class MovieServiceImpl implements MovieService {
@Autowired
private final MovieRepository movieRepository; // final
@Autowired
private final MovieImageRepository imageRepository; // final
//... 생략
@Override
public PageResultDTO<MovieDTO, Object[]> getList(PageRequestDTO requestDTO) {
Pageable pageable = requestDTO.getPageable(Sort.by("mno").descending());
Page<Object[]> result = movieRepository.getListPage(pageable);
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);
}
}
3-6.コントローラの変更
import com.example.mreview2022.dto.MovieDTO;
import com.example.mreview2022.dto.PageRequestDTO;
import com.example.mreview2022.service.MovieService;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
@RequestMapping("/movie")
@RequiredArgsConstructor
public class MovieController {
@Autowired
private final MovieService movieService; //final
//... 생략
@GetMapping("/list")
public void list(PageRequestDTO pageRequestDTO, @NotNull Model model){
model.addAttribute("result",movieService.getList(pageRequestDTO));
}
}
3-6.htmlの変更
リストから出力されたデータがモデルに含まれているため、モデル結果を使用して出力されます.
<!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>
<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>
<script th:inline="javascript">
</script>
</th:block>
</th:block>
Reference
この問題について(SpringBoot with JPAプロジェクト(M:N)5.映画の登録、登録された文章のリストの処理), 我々は、より多くの情報をここで見つけました https://velog.io/@alswl689/SpringBoot-with-JPA-프로젝트MN-5テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol