파일 업로드로 영화를 등록하고, 사용자들이 영화 리뷰를 기록하는 것을 예제로 작성해봅니다.
@Controller
@Log4j2
@RequestMapping("/movie")
public class MovieController {
@GetMapping("/register")
public void register(){
}
}
<!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="Entier 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-breose="Browse"></label>
</div>
</div>
<div class="box">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<script>
$(document).ready(function(e){
}); //document ready
</script>
</th:block>
</th:block>
</html>
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MovieDTO {
private Long mno;
private String title;
@Builder.Default
private List<MovieImageDTO> imageDTOList = new ArrayList<>();
//화면에 영화 이미지들도 같이 수집해서 전달해야 하므로 내부적으로 리스트를 이용하여 수집합니다.
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class MovieImageDTO {
private String uuid;
private String imgName;
private String path;
public String getImageURL(){
try{
return URLEncoder.encode(path + File.separator + 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 "";
}
}
public interface MovieService {
Long register(MovieDTO movieDTO);
default Map<String,Object> dtoToEntity(MovieDTO movieDTO){ //Map타입으로 반환
Map<String,Object> entityMap = new HashMap<>();
//key값을 String, Value값을 Object형으로
Movie movie = Movie.builder()
.mno(movieDTO.getMno())
.title(movieDTO.getTitle())
.build();
entityMap.put("movie",movie);
//Map에 키,값 저장
List<MovieImageDTO> imageDTOList = movieDTO.getImageDTOList();
//MovieImageDTO 처리
if(imageDTOList != null && imageDTOList.size() > 0){
//리스트는 stream()으로 해결하는것이 편하다.
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());//List로 변환
entityMap.put("imageList",movieImageList);
//Map에 키,값 저장
}
return entityMap;
}
}
HashMap의 중요한 특성
-HashMap은 또한 '복제 가능'및 '직렬화 가능'인터페이스를 구현합니다.
-HashMap은 중복 값을 허용하지만 중복 키는 허용하지 않습니다.
-HashMap은 또한 여러 널값을 허용하지만 널 키는 하나만 될 수 있습니다.
-HashMap은 동기화되지 않으며 요소의 순서도 보장하지 않습니다.
MovieServiceImpl은 MovieRepository와 MovieImageRepository를 주입받도록 구성하고, dtoToEntity()에서 반환한 객체들을 이용해서 save()를 처리해줍니다.
@Serviceva
@Log4j2
@RequiredArgsConstructor
public class MovieServiceImpl implements MovieService{
private final MovieRepository movieRepository;
private final MovieImageRepository movieImageRepository;
@Override
@Transactional
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");
//Map에서 꺼내온 값들을 Movie,List<MovieImage>로 형변환
movieRepository.save(movie);
movieImageList.forEach(movieImage -> {
movieImageRepository.save(movieImage);
});
return movie.getMno();
}
}
@Controller
@Log4j2
@RequestMapping("/movie")
@RequiredArgsConstructor
public class MovieController {
private final MovieService movieService;
@GetMapping("/register")
public void register(){
}
@PostMapping("/register")
public String register(MovieDTO movieDTO, RedirectAttributes redirectAttributes){
log.info("movieDTO: "+movieDTO);
Long mno = movieService.register(movieDTO);
redirectAttributes.addFlashAttribute("msg",mno);
return "redirect:/movie/list";
}
}
<!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>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<script>
$(document).ready(function(e){
var regex =new RegExp("(.*?)\.(exe|sh|zip|alz|tiff)$");
// .*? : 줄 바꿈 문자를 제외한 모든 문자를 0개 이상 찾되, 최소 크기로 찾는다.
var maxSize = 10485760; //10MB
function checkExtension(fileName, fileSize){
if(fileSize>maxSize){
alert("파일 사이즈 초과")
return false;
}
if(regex.test(fileName)){ //RegExp.test() => 패턴이 있는지 없는지를 테스트하는데 목적
alert("해당 종류의 파일은 업로드 할 수 없습니다.")
return false;
}
return true;
}
$(".custom-file-input").on("change",function(){
var fileName = $(this).val().split("\\").pop();
//pop() : 배열에서 마지막 요소를 제거하고 그 요소를 반환합니다.
$(this).siblings(".custom-file-label").addClass("selected").html(fileName);
//siblings() : 자신을 제외한 형제 요소를 찾습니다.
//addClass() : 새로운 클래스를 추가(이미 가지고 있는 클래스는 추가되지 않습니다.)
//html() : 요소안의 내용을 지우고 새로운 내용을 넣습니다.
var formData = new FormData();
var inputFile = $(this);
var files = inputFile[0].files;
var append = false;
console.log(formData);
console.log(inputFile);
console.log(files);
console.log(append);
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(!append){
return;
}
for(var value of formData.values()){
console.log(value);
}
//실제 업로드 부분
$.ajax({
url: '/uploadAjax',
processData: false,
contentType : false,
data : formData,
type : 'POST',
dataType : 'json',
success : function(result){
console.log(result);
},
error : function(jqXHR,textStatus,errorThrown){
conlose.log(textStatus);
}
});//$.ajax
});//end change event
}); //document ready
</script>
</th:block>
</th:block>
<form>
태그아래 쪽에 다음과 같은 스타일과 <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>
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);
}
//실제 업로드 부분
$.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){
conlose.log(textStatus);
}
<li>
태그로 구성되는데 이때 ImageDTO에 필요한 속성들을 구성하게 됩니다.