본문 바로가기
Spring/JPA + Security

[Spring Data JPA] JPA를 사용한 이미지 업로드

by pyogowoon 2023. 1. 8.


@AllArgsConstructor
@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){
    //서비스 호출
        imageService.사진업로드(imageUploadDto,principalDetails);
            return "redirect:/user/" +principalDetails.getUser().getId();
    }
}

 

이미지 컨트롤러에 PostMapping 으로 업로드 관련 컨트롤러를 추가한다. 

return 은 return 하고 /user/(유저id값) 으로 돌아갈 수 있도록 구현

 

 

 

그 다음은 Image Entity를 만든다.

 

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
@Entity
public class Image {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String caption;
    private String postImageUrl;  // 사진을 전송받아서 그 사진을 서버에
    // 폴더에 저장하게될것 - DB에는 경로를 인서트

    @JoinColumn(name="userId") //foreign 키 이름 지정
    @ManyToOne // 한명이 많은 이미지를 올릴 수 있고 이미지는 하나의 이미지가 여럿일수없으니
    private User user; // db에 오브젝트 자체를 저장할 순 없고 이대로면 foreign key로 저장됨

    //이미지 좋아요

    //이미지 댓글

    private LocalDateTime createDate;

    @PrePersist
    public void createDate() {
        this.createDate = LocalDateTime.now();
    }
}

 

foreign key 이름 지정 해주고 createDate도 넣는다 postImageUrl은 추후 서버 폴더에 저장될 경로가 될것

 

그 후 ImageRepository 생성해서 jpa로 연결 

//코드블럭은 생략

 

그리고 실질적으로 사진을 첨부할 수 있게 할수있는 DTO를 생성

 

package com.pyo.pyostagram.web.dto.image;


import lombok.Data;
import org.springframework.web.multipart.MultipartFile;

@Data
public class ImageUploadDto {

    private MultipartFile file;
    private String caption;
}

 

file 은 꼭 MultipartFile  형식으로 지정

 

이제 ImageService를 구현

 

@RequiredArgsConstructor
@Service
public class ImageService {

    private final ImageRepository imageRepository;

    @Value("${file.path}") // yml에 적힌값 가져오는거
    private String uploadFolder;

    public void 사진업로드(ImageUploadDto imageUploadDto, PrincipalDetails principalDetails){
       UUID uuid = UUID.randomUUID(); //uuid 하는 이유 = 만약 같은이름의 파일이 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());
                        //첫번째로 경로,2. 실제 파일을 byte화 해서 넣어야함, 3.옵션값인데 생략가능
        }catch(Exception e){
                e.printStackTrace();
        }
    }
}

@Value 를 통해서 yml에 적힌 경로값을 가져오고

업로드시 UUID 처리를 꼭 해줘야 한다 그 이유는 같은 이름의 파일이 uuid 없이 올라오게 된다면 폴더에서 덮어씌워질것이기 때문

 

이제 JSP 가서

 


<!--사진업로드 Form-->
<form class="upload-form" action="/image" method="post" enctype="multipart/form-data">
       <!-- enctype이 multipart~ 인 이유는 input type="file"은 byte화 해서 전송하고 text는 key/value 여서 서로 방식이라 그렇다. -->

 

여기서 중요한것은 enctype 이다. enctype을 적는 이유는 Post되는 형식이 input type="file" 의 경우는 byte화 되서 들어가고

input type="text"의 경우 key/value 값으로 들어가기 때문에 두가지 동시에 보낼수 있는 multipart/form-data 형식을 사용하는것

 

이제 DB에 이미지를 적용해야하는데,

 

@Data
public class ImageUploadDto {


    private MultipartFile file;  //멀티파츠는 @NotBlank 지원하지않음
    private String caption;

    public Image toEntity(String postImageUrl, User user){
        return Image.builder()
                .caption(caption)
                .postImageUrl(postImageUrl)
                .user(user)
                .build();

    }
}

 

 ImageUploadDto 에 toEntitiy 메서드를 만든후 Dto에서 가공해서 domain으로 가져갈 데이터들을 빌드해준다.

 여기서 user정보랑 DB에 들어갈 url --> 우리가 DB에서 postImageUrl , 즉 UUID가 포함된 image의 url을 넣을것이기 때문에 service에서 받아오면 된다.

 

@Value("${file.path}") // yml에 적힌값 가져오는거
    private String uploadFolder;

    public void 사진업로드(ImageUploadDto imageUploadDto, PrincipalDetails principalDetails){
       UUID uuid = UUID.randomUUID(); //uuid 하는 이유 = 만약 같은이름의 파일이 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());
                        //첫번째로 경로,2. 실제 파일을 byte화 해서 넣어야함, 3.옵션값인데 생략가능
        }catch(Exception e){
                e.printStackTrace();
        }
        Image image = imageUploadDto.toEntity(imageFileName,principalDetails.getUser());
        Image imageEntity = imageRepository.save(image);

     
    }
}

 

그 후 Image 타입의 image를 선언하고 Dto의 toEntity 메서드에 파일이름과 유저를 담아서 보낸 후 인서트 하면 된다.

 

그리고

 

@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();
}

 

이 과정에서 우리는 ImageService에 이미지가 첨부되지 않은 경우를 위해 예외처리를 해줘야한다.

이유는 무조건 사진 업로드를 하기위한 환경을 만들기 위함이고

 

 저번처럼 Dto 에서 @NotBlank 해줘도 되지않나요 하실수 있지만 MultiPartFile 형식은 @NotBlank가 안됀다.

그래서 아래처럼 로직을 구현할것이다.

 그리고 throw new CustomValidationException 값의 errorMap이 null인 이유는 우리가 @Valid를 사용해서 BindingResult를 사용한게 아니기 때문에 null로 보낸다.

 

@ExceptionHandler(CustomValidationException.class)
public String validationException(CustomValidationException e) {
    //CMRespDto, Script 비교
    // 1. 클라이언트한테 대답하면 Script가 좋음
    // 2. Ajax통신 - CMRespDto가 좋음
    // 3. android 통신 - CMRRespDto

    if (e.getErrorMap() == null) {
        return Script.back(e.getMessage());
    } else {
        return Script.back(e.getErrorMap().toString());
    }

}

 

파일 첨부에선 getErrorMap이 null 이기 때문에 validationException 에 ErrorMap이 null일 경우를 대비하여 if문을 넣어주자.

 

if 분기에따라 errorMap이 null 이면서 이미지가 첨부되지 않았을 때의 alert 창. 잘뜬다.

댓글