Wanna be Brilliant Full-Stack Developer
2/14 SpringBoot Image DB에 업로드하기 본문
목표
실제로 저장된 사진의 경로 및 캡션 내용(이미지 내용소개) 데이터 베이스에 저장해보자
ImageUploadDto에 있는 내용을 가지고 이미지 객체로 변환하는것이 필요하다
그래야지 세이브를 할 수 있기 떄문!
그 로직을 하기위해 서비스를 수정!
package com.cos.photogramstart.service;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.cos.photogramstart.config.auth.PrincipalDetails;
import com.cos.photogramstart.domain.image.Image;
import com.cos.photogramstart.domain.image.ImageRepository;
import com.cos.photogramstart.web.dto.image.ImageUploadDto;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class ImageService {
private final ImageRepository imageRepository;
@Value("${file.path}")
private String uploadFolder;
public void 사진업로드(ImageUploadDto imageUploadDto, PrincipalDetails principalDetails) {
UUID uuid = UUID.randomUUID(); // uuid
String imageFileName = uuid+ "_" + imageUploadDto.getFile().getOriginalFilename(); // 1.jpg
System.out.println("이미지 파일이름: "+ imageFileName);
Path imageFilePath = Paths.get(uploadFolder+imageFileName);
//통신, I/O -> 예외
try {
Files.write(imageFilePath, imageUploadDto.getFile().getBytes());
} catch (Exception e) {
e.printStackTrace();
}
// image 테이블에 저장
Image image = imageUploadDto;
Image imageEntity = imageRepository.save(image);
}
}
또한 imageUploadDto 를 image에 집어넣기 위한 로직이 필요한데 dto에서 해줘야한다.
user정보를 또 왜넣어하냐면 이미지 객체는 유저정보가 필요하기 떄문에
어떤 유저가 사진을 저장하고 업로드했는지 알아야하기 떄문에
package com.cos.photogramstart.web.dto.image;
import org.springframework.web.multipart.MultipartFile;
import com.cos.photogramstart.domain.image.Image;
import com.cos.photogramstart.domain.user.User;
import lombok.Data;
@Data
public class ImageUploadDto {
private MultipartFile file;
private String caption;
public Image toEntity(String postImageUrl, User user) {
return Image.builder()
.caption(caption)
.postImageUrl(postImageUrl)
.user(user)
.build();
}
}
유저가 들고 있는 사진을 출력해야하는것과 이미지과 업로드 되거나 실패를 하게 되면 익셉션처리를 해줘야한다!
또한 캡션 (사진 내용) 이 프론트단에서는 required로 막았지만 Postman으로 강제적으로 집어넣으면 맘대로 넣을수 있기때문에 Validation 체크를 해야한다!
이미지 유효성 Validation 체크하기
캡션 은 필수적으로 작성 안해도 되도록 설정할것이지만
<input type="file" name="file" onchange="imageChoose(this)"/>
이미지 파일은 유효성 체크할 필요가 있다.
이미지를 유효성을 체크 하기 위해서는 그러면 이미지업로드DTO에서 @NotBlank같은것을 걸어서
처리할수있을것이라 생각했지만 MultipartFile에는 @NotBlank가 지원이 되지 않는다고 한다.
하지만 방법은 있다 우리가 이미지 컨트롤러에서 강제적으로 만들면되는데!
package com.cos.photogramstart.web;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import com.cos.photogramstart.config.auth.PrincipalDetails;
import com.cos.photogramstart.handler.ex.CustomValidationException;
import com.cos.photogramstart.service.ImageService;
import com.cos.photogramstart.web.dto.image.ImageUploadDto;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Controller
public class ImageController {
private final ImageService imageService;
@GetMapping({"/", "/image/story"})
public String story() {
return "image/story";
}
@GetMapping("/image/popular")
public String popular() {
return "image/popular";
}
@GetMapping("/image/upload")
public String upload() {
return "image/upload";
}
@PostMapping("/image")
public String imageUpload(ImageUploadDto imageUploadDto, @AuthenticationPrincipal PrincipalDetails principalDetails) {
// 서비스 호출
if(imageUploadDto.getFile().isEmpty()) {
throw new CustomValidationException("이미지가 첨부되지 않았습니다.",null);
}
imageService.사진업로드(imageUploadDto, principalDetails);
return "redirect:/user/"+principalDetails.getUser().getId();
}
}
package com.cos.photogramstart.handler;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import com.cos.photogramstart.handler.ex.CustomApiException;
import com.cos.photogramstart.handler.ex.CustomValidationApiException;
import com.cos.photogramstart.handler.ex.CustomValidationException;
import com.cos.photogramstart.util.Script;
import com.cos.photogramstart.web.dto.CMRespDto;
@RestController
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(CustomValidationException.class) // RuntimeExcpetion -> CustomValidaionException 으로 변경
public String validationException(CustomValidationException e) {
// CMRespDto, Script 비교
// 1. 클라이언트에게 응답할 때는 Script 좋음.
// 2. Ajax통신 - CMRespDto
// 3. Android 통신 -CMRespDto
if(e.getErrorMap() == null) {
return Script.back(e.getMessage());
}else {
return Script.back(e.getErrorMap().toString());
}
}
@ExceptionHandler(CustomValidationApiException.class)
public ResponseEntity<?> validationApiException(CustomValidationApiException e) {
return new ResponseEntity<>(new CMRespDto<>(-1, e.getMessage(), e.getErrorMap()), HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(CustomApiException.class)
public ResponseEntity<?> apiException(CustomApiException e) {
return new ResponseEntity<>(new CMRespDto<>(-1, e.getMessage(),null), HttpStatus.BAD_REQUEST);
}
}
VIEW 화면에 렌더링하는것을 해보려고한다!
localhost:8080/user/1페이지에 데이터를 들고와야하는데
UserConctroller에서
@GetMapping("/user/{id}")
public String profile(@PathVariable int id) {
return "user/profile";
}
지금 해당 메서드를 찾아보면 user/profile페이지로 그냥 가고 있기 떄문에 데이터를 함꼐 보내기 위해서 모델이라는것을 추가해보려고한다.
public String profile(@PathVariable int id, Model model) {
model.addAttribute("images", null);
return "user/profile";
이렇게 하면 이미지를 가져와야만 여기에다가 출력할수있다.
여기서 필요한것은 UserService가 필요하다!
public void 회원프로필(int userId) {
}
유저 서비스안에 int userId는 무엇을 받냐면 유저컨트롤러 안에 GetMapping에 user/{id}이것을 받아서 처리하기위해 (해당페이지 주인 아이디)
package com.cos.photogramstart.web;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import com.cos.photogramstart.config.auth.PrincipalDetails;
import com.cos.photogramstart.service.UserService;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Controller
public class UserController {
private final UserService userService;
@GetMapping("/user/{id}")
public String profile(@PathVariable int id, Model model) {
userService.회원프로필(id);
model.addAttribute("images", null);
return "user/profile";
}
@GetMapping("/user/{id}/update") //user에 업데이트가 아니라 어떤 번호를 업데이트 할건지 해야하기때문 {id}를 건다!
public String update(@PathVariable int id,@AuthenticationPrincipal PrincipalDetails principalDetails) {
// 1. 추천
System.out.println("세션 정보:" + principalDetails.getUser());
// 2.비추천
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
PrincipalDetails mPrincipalDetails = (PrincipalDetails) auth.getPrincipal();
System.out.println("직접 찾은 세션 정보 : " + mPrincipalDetails.getUser());
return "user/update";
}
}
여기서 문제는 내가 Sssar로 로그인했으니까 로그인 한 사람의 사진이 나오는게 아니라 path 에따라 달라지는것이니
또다른 익셉션을 만들어야하는데 이것은 유효성 처리가 아니기떄문에 CustomException라는 이름으로 만든다.
package com.cos.photogramstart.handler.ex;
import java.util.Map;
public class CustomException extends RuntimeException{
//시리얼 번호는 객체를 구분할때!!
private static final long serialVersionUID = 1L;
public CustomException(String message) {
super(message);
}
}
이렇게 하면 해당아이디가 있으면 있는거고 없으면 밑에 있는 exception을 타는거다
없는 유저를 치면 이렇게 결과가 나온다
이제 우리가 하게될건 userEntity를 바로 리턴해줄것이다.
이건 유저의 정보인데! 그걸 유저 컨트롤러에서 받을것이다.
@GetMapping("/user/{id}")
public String profile(@PathVariable int id, Model model) {
User userEntity = userService.회원프로필(id);
model.addAttribute("user", userEntity);
return "user/profile";
}
내가 회원프로필 페이지로 이동할떄 그떄 이 사진을 가져오고싶다. 이 페이지로 갈때는 사진만 들고오는것이 아니라 회원정보들도 있고 이미지 정보들도 있고 게시물이 몇개인지 구독정보가 몇개인지 와 같이 이 페이지를 갈떄는 이미지만 가져갈 수 있는것이 아니라 이미지도 들고가고 유저도 들고가고 많은 데이터를 가져가야만한다.
근데 유저 안에는 유저 정보만 있지 이미지 정보가 없다.
우리가 해야할것은 이미지를 셀렉트해서 이페이지로 가게되면 유저정보를 못가져간다.
자세히 설명해보자면
우리가 유저프로필 페이지를 갈떄 이미지만 가져갈수없다. 이렇게 가져가면 사진만 가져갈수 있다.
우리는 유저정보도 가져가야 유저 사진도 뿌리고 유저 정보도 보여줄수 있기때문
그리고 또 해당 유저를 구독하고있는지 아닌지 확인하기 위해 Subscribe도 가져가야하기때문에
이 많은 정보를 한꺼번에 가져가야하는데
우리는 모든 정보를 한꺼번에 가져가기 위해서!
데이터베이스 (DB)에서 영속성 컨텍스트가 있는데 이상태에서 받아서 DB안에 유저정보가 있으면
사용자는 1번 유저정보가 아직 영속성 컨텍스트에 없으면 DB에서 유저가 가져오게 되고 영속화가 된다.
유저에는 이미지 정보가 없기 떄문에
유저를 셀렉트 할떄 유저에 관련된 이미지들을 같이 뽑아 오는 로직을 만들어보려고한다
양방형 매핑을 하기위해서는?! 유저에
// 나는 연관관계의 주인이 아니다. 그러므로 테이블에 칼럼을 만들지마
// User를 Select할 떄 해당 User id로 등록된 image들을 다 가져와
// Lazy = User를 셀렉트 할때 해당 User id로 등록된 image들을 가져오지마 - 대신 getImages() 함수의 이미지들이 호출될 떄 가져와
// Eager = User를 select 할때 해당 User id로 등록된 image 들을 전부 Join 해서 가져와!!
@OneToMany(mappedBy ="user", fetch = FetchType.LAZY)
private List<Image> images; // 양방형 매핑!
'Back-End > Spring Boot' 카테고리의 다른 글
2/15 SpringBoot 구독정보 완성하기 #1 (0) | 2022.02.15 |
---|---|
2/14 SpringBoot Image 뷰 렌더링하기 (0) | 2022.02.14 |
SpringBoot 구독, 구독취소 API 만들기 (0) | 2022.02.14 |
SprignBoot 구독 API 모델 만들기 (0) | 2022.02.14 |
SPringBoot 구독하기 API 구현하기 연관관계 개념! (0) | 2022.02.14 |