SpringBoot + MariaDB 5-1 프로필페이지 모델만들고 서버에 업로드하기 (외부에 uploadFile 저장하는 이유)
모델을 만들기위해 domain패키지에 image라는 패키지 생성후 image클래스 만들어주기
package com.cos.photogramstart.domain.image;
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
public class Image {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String caption; // 사진글
private String postImageUrl;
// 사진을 전송받아서 그 사진을 서버에 특정 폴더에 저장 - DB에는 그저장된 경로만 insert할것
// 객채 자체를 DB에 저장할 경우 포린키로 저장된다.
@JoinColumn(name="userId")
@ManyToOne
private User user; // 한명의 유저는 여러이미지를 생성가능
// 이미지는 하나의 유저만 가질 수 있다.
// 이미지 좋아요
// 이미지 댓글
private LocalDateTime createDate;
// 항상 데이터베이스에는 시간이 중요하니 넣어줍니다.
@PrePersist
public void createDate() {
this.createDate = LocalDateTime.now();
}
}
여기서 중요하게 볼점은 왜 @ManyToOne 인지 알아야한다
한명의 유저는 여러개의 image를 가질 수 있다 하지만
한개의 이미지는 하나의 유저만 소유 가능하기 때문에 user의 기준으로 봤을땐 ManyToOne이 되게된다.
모델이 완성되었다면 저장해줄 Repository를 만들기
package com.cos.photogramstart.domain.image;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ImageRepository extends JpaRepository<Image, Integer>{
}
이미지와 같은 image패키지 안에 만들어 주면 됩니다.
후에 이제 ImageController에서
@PostMapping("/image")
public String imageUpload(){
}
이렇게 코드를 작성하고 받아야할 것을 생각해보니 우리는 파일을 받아야 하는대 파일을 받는 DTO가 없다 요청을 처리하기 위한 DTO를 만들러가자.
dto패키지에 image란 패키지를 만든뒤 ImageUploadDto를 생성해주자
그전에 우리가 무었을 받아오는지 보면 지금 file과 caption을 받아오고있다 그렇담 Dto에는 저 두개를 받아오면 될거 같다
package com.cos.photogramstart.web.dto.image;
import org.springframework.web.multipart.MultipartFile;
import lombok.Data;
@Data
public class ImageUploadDto {
private MultipartFile file;
private String caption;
}
컨트롤러에 매개변수에 우리가만든 ImageUploadDto와 principalDetails받도록 수정
@PostMapping("/image")
public String imageUpload(ImageUploadDto imageUploadDto, @AuthenticationPrincipal PrincipalDetails principalDetails){
return "redirect:/user/"+principalDetails.getUser().getId();
}
업로드가 완료되면 프로필 페이지로 이동시킬것이라 return을 저렇게 설정하였습니다.
이제 컨트롤에서 서비스로 넘겨야하니 서비스를 만들러 갑니다.
service패키지에 ImageService 클래스 생성
package com.cos.photogramstart.service;
@RequiredArgsConstructor
@Service
public class ImageService {
private final ImageRepository imageRepository;
public void 사진업로드(ImageUploadDto imageUploadDto, PrincipalDetails principalDetails) {
}
}
서비스를 다 생성하였다면 컨트롤러에 추가해주자
@RequiredArgsConstructor
@Controller
public class ImageController {
private final ImageService imageService;
@PostMapping("/image")
public String imageUpload(ImageUploadDto imageUploadDto, @AuthenticationPrincipal PrincipalDetails principalDetails){
imageService.사진업로드(imageUploadDto, principalDetails);
return "redirect:/user/"+principalDetails.getUser().getId();
}
}
서비스를 불러와 적용 시킨 모습이다.
이제 사진 업로드란 함수를 만들어보자
package com.cos.photogramstart.service;
@RequiredArgsConstructor
@Service
public class ImageService {
private final ImageRepository imageRepository;
@Value("${file.path}")
private String uploadForder;
public void 사진업로드(ImageUploadDto imageUploadDto, PrincipalDetails principalDetails) {
UUID uuid = UUID.randomUUID(); // uuid
String imageFileName= uuid +"_"+imageUploadDto.getFile().getOriginalFilename();
Path imageFilePath = Paths.get(uploadForder+imageFileName);
// 통신, I/O -> 예외가 발생할 수 있다.
// runtime시에만 발견된다 그래서 무조건 위와같은 것을 실행할땐 예외처리를 무조건 해주어야 한다.
try {
Files.write(imageFilePath, imageUploadDto.getFile().getBytes());
}catch(Exception e) {
e.printStackTrace();
}
}
}
imageFilename에는 실제 파일 이름이 담기게 된다.
위와같이 받으면 생길 문제점은 파일을 저장할때 똑같은 파일명이 들어오게 된다면
똑같은 이름으로 또 들어와 원래있던 사진에 덮어 씌우게 된다.
이를 방지하기 위해 UUID를 사용하자
하지만 UUID라도 랜덤으로 값을 뽑기때문에 같은 값을 뽑을 확률이 몇십억분의 일이지만
그것마저도 방지하기위해서 uuid뒤에 원래 이미지의 이름까지 더해주게된다.
그렇다면 99.9999999%는 위와 같은일이 없을것이다.
uploadForder에는 @Value에 경로를 적었는데 이경로는 yml파일에보이는 path경로를 가져오게 된다.
enabled는 true어야하고
file에는 path경로를 지정할 수 있다 경로가 여러개여도 된다 path2,path3가 있어도 괜찮다
경로를 적을때 백슬러시가 아닌 그냥 /를 사용하여 경로를 표기한다!
그리고 경로를 불러올땐 위와같이 @Value("${file.path}")라고 가져오면 위에 명시한 경로를 가져오게 된다
path2일경우 file.path2라고 el태그안에 넣어주면 됨
자 이제 모든게 완료되었으니 jsp를 수정하고 테스트하러 가봅시다.
<!--사진업로드 Form-->
<form class="upload-form" action="/image" method="POST" enctype="multipart/form-data">
<input type="file" name="file" onchange="imageChoose(this)"/>
<div class="upload-img">
<img src="/images/person.jpeg" alt="" id="imageUploadPreview" />
</div>
<!--사진설명 + 업로드버튼-->
<div class="upload-form-detail">
<input type="text" placeholder="사진설명" name="caption">
<button class="cta blue">업로드</button>
</div>
<!--사진설명end-->
</form>
<!--사진업로드 Form-->
action과 method="post" 적어주고 enctype="multipart/form-data를 적어주게 되는데 원래 enctype은 application/x-www-form-urlencoded 가 들어가게 된다 하지만 우리가 사진이란 파일을 보내고 싶을땐 Byte형태로 보내기 때문에 Byte와 application/x-www-form-urlencoded 형태를 보내고 싶다면 multipart/form-data형태로 보내면 된다
즉 다른 데이터 를 묶어서 보내고싶을땐 multipart/form-data로 enctype을 잡아서 보내주자
로그인후 홈페이지에서 사진 업로드 해보기
외부에 파일을 두는 이유는
1. 서버가 실행됨
2. 컴파일을 진행되고
3. target폴더에 .class가 저장이된다
4. target폴더에 .class를 읽어 실행된다.
사진파일도 target폴더에 컴파일은 안되지만 집어넣고 실행이될것이다
이런 정적인 파일을 넣은 것을 deploy라고 칭함
파일같은경우는 용량이 1mb정도 되고 그에 비하면 java파일은 용량이적다
내부에 upload폴더가있고 실행을한다 가정을하면
java파일이 컴파일하는데 훨씬 빠르니 java이 실행되고 뒤늦게 upload폴더에 사진을 불러오게되면 사진이 있어야 할 곳에 엑박이뜰 수 있다.
다른 예시로 내가 업로드를 하게되면 사진이 deploy가 되는대 1초가 걸리고 업로드작업후 프로필 페이지로 돌아가는데 0.5초가 걸린다고 가정을하면 아직 사진이 저장되기 전에 프로필 페이지로 와버리면 당연히 엑박이 뜨게된다
하지만 외부에 업로드 파일을 저장할 경우 deploy과정을 거치지 않고 바로 불러오기때문에 위와같은 불상사는 일어나지 않게되어 외부에 저장하는것이다.
위와같은 개념을 항상 인지하여 엑박떠서 맨탈나가는 일은 없도록 하자