이 내용은 스프링 부트 쇼핑몰 프로젝트 with JPA 책을 학습한 내용입니다.
1. application.properties 설정 추가
- 파일 크기 및 경로 지정 (경로는 프로젝트 외부 디렉토리에 저장함)
##########################
# 파일 업/다운로드 설정
##########################
# 파일 한 개당 최대 사이즈
spring.servlet.multipart.max-file-size=20MB
# 요청 당 최대 파일 크기
spring.servlet.multipart.max-request-size=100MB
# 상품 이미지 업로드 경로
itemImgLocation=D:/shop/item
# 리소스 업로드 경로
uploadPath=file:///D:/shop/
2. WebMvcConfigurer 인터페이스
- addResourceHandlers 메소드를 오버라이딩하여 파일 업로드 경로 지정
- "${uploadPath}" - application.properties 에 설정한 "uploadPath" 프로퍼티 값
- " /images/** " 패턴의 URL 은 uploadPath 폴더를 기준으로 탐색
package kr.spring.config;
// 이미지 업로드 파일 경로
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//파일 올리고 이미지 올리고 이럴 때 웹이 로컬을 막 건들면 안되니까 설정 파일을 하나 만들어줌
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
// 이미지/파일을 업로드하는 진짜 경로를 application properties에 uploadPath로 저장해놨는데 그걸 가져오게 하기 위함
@Value(value = "${uploadPath}")
private String uploadPath; // 해당 경로를 사용할 수 있음
@Override
// registry를 등록해서 쓸 수 있게 함
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/images/**") // 접근을 이 주소로 하게 함
.addResourceLocations(uploadPath); // 실제로는 여기인데 (위에 처럼함)
}
}
3. 상품 이미지 파일 (File)Service
- FileService.java
- 이미지 파일 저장 로직을 담당할 Service 객체
- 파일 저장은 DB 에 저장되는 것이 아니기 때문에 Repository 필요 없음 (FileOutputStream 가 대신함)
- 이미지 파일 업로드
- UUID(Universally Unique IDentifier) - 서로 다른 개체들을 구별하기 위한 클래스
- FileOutputStream 클래스를 이용하여 파일을 저장
package kr.spring.item.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.UUID;
@Service
// 파일을 만들고 지우는 일을 함
@Slf4j
public class FileService {
// 특정 폴더를 잡고 올리고 싶지만, 파일 명칭이 충돌이 날 수 있기 때문에 파일 명칭이 충돌이 나지 않게 하는 메소드
public String uploadFile(String uploadPath, String oriFileName, byte[] fileData) throws IOException {
UUID uuid = UUID.randomUUID(); // 임의의 UUID 자동으로 생성
// file의 확장자를 떼기 위함
// 요즘 파일은 중간중간에도 .을 찍기 때문에 맨 마지막에 있는 확장자만 지운다.
String extension = oriFileName.substring(oriFileName.lastIndexOf(".")); // 마지막 .을 기준으로 문자열 가져옴
// uuid는 문자열이 아니니까 toString 꼭 해줘야 함 거기에 확장자만 붙여주면 됨
String savedFileName = uuid.toString() + extension;
// 실제 파일 경로 -> D://어쩌고~ / uuid로 만든 파일 이름.extension으로 저장됨
String fileUploadUrl = uploadPath + "/" + savedFileName;
FileOutputStream fos = new FileOutputStream(fileUploadUrl); // 출력
// 위에서 받아온 fileData를 써줌
fos.write(fileData);
fos.close();
return savedFileName;
}
// 파일을 지우는 동작
public void deleteFile(String filePath) {
// 파일을 지우기 위해서는 file 객체 필요
File deleteFile = new File(filePath);
// 지우려는 파일 존재하면
if(deleteFile.exists()) {
deleteFile.delete();
log.info("파일을 삭제했습니다.");
} else {
log.info("파일이 존재하지 않습니다.");
}
}
}
4. 상품 이미지 정보 Repository
package kr.spring.item.repository;
import kr.spring.item.entity.ItemImg;
import org.springframework.data.jpa.repository.JpaRepository
public interface ItemImgRepository extends JpaRepository<ItemImg, Long> {
}
5. 상품 이미지 Service
- 상품 이미지 업로드, 상품 이미지 정보 저장 Service
- 클래스 생성 및 DI
- application.properties 에 설정한 itemImgLocation 값을 가져옴
- 상품 이미지 업로드 - FileService
- 상품 이미지 정보 저장 - itemImgRepository
@Service
@RequiredArgsConstructor // 이걸 사용하면 autowired를 안해도 됨
@Transactional //그림을 세 개를 올리다가 네 개까지 갔는데 하나만 오류가 날 경우 다 취소하고 다시 해야함. 이게 transaction
public class ItemImgService {
// application properties에 설정해놓은 itemImgLocation를 가져와서 service에서 uploadfile 할 때 사용
@Value(value = "${itemImgLocation}")
private String itemImgLocation;
private final ItemImgRepository itemImgRepository;
private final FileService fileService;
}
- 상품 이미지 저장 (내부에 사진 업로드 포함)
- if (!StringUtils.isEmpty(oriImgName))상품 이미지가 존재하지 않는다면 건너 뜀존재한다면 fileService.uploadFile() 메소드로 파일 업로드 후 UUID 를 통해 변경된 파일명 return
- updateItemImg 메소드와 itemImgRepository.save() 메소드를 이용해서 상품 이미지 정보 저장
// 이미지 저장
public void saveItemImg(ItemImg itemImg, MultipartFile itemImgFile) throws IOException {
// 파일을 받아온 시점에 보면 multiopart 안에 오리지날 이름 등 파일 정보가 다 들어있는데 일단 저건 원래 이름을 가지고 오는 것
// 왜냐면 DB에 넣을 때는 원래 이름을 알아야 하기 때문
String oriImgName = itemImgFile.getOriginalFilename();
// fileService에서 만든 imgName임
String imgName = "";
// 이미지 경로
String imgUrl = "";
// 원래 경로가 값이 비어있는지 타임리프 유틸을 이용해서 확인
if(!StringUtils.isEmpty(oriImgName)) {
// 진짜 이미지 이름 받아옴
// 파일의 정보는 itemImgFile에 다 있으니까 이걸 byte배열로 가져옴
imgName = fileService.uploadFile(itemImgLocation, oriImgName, itemImgFile.getBytes());
imgUrl = "/images/item/" + imgName;
}
// 실제 상품 이미지 저장
itemImg.updateItemImg(oriImgName, imgName, imgUrl);
// 이미지만 저장 그러므로 아이템도 저장해줘야함
itemImgRepository.save(itemImg);
}
6. 상품 Service
- 클래스 생성 및 DI
- 상품 저장 - itemRepository
- 상품 이미지 저장 - itemImgService
@Service
@RequiredArgsConstructor
@Transactional // 서비스 등록하다가 깨지면 지금 했던 일을 다시 해야하기 때문에
@Slf4j
public class ItemService {
private final ItemRepository itemRepository;
private final ItemImgRepository itemImgRepository;
}
- 상품 저장
- ItemFormDto 객체를 DB 에 저장하기 위해 Item 객체로 변환
- 상품의 첫 번째 이미지는 대표 사진으로 설정
// 아이템 저장
// 아이템을 등록했으니까 아이템 아이디가 넘어왔을 것
// itemDto 값을 넘겨 받고, multipart 형식으로 되어있는 리스트를 받아옴
public Long saveItem(ItemFormDto itemFormDto, List<MultipartFile> itemImgeFileList) throws IOException {
// DTO를 entity로 바꾼다. createItem에서 mapper로 바꿨으니까
Item item = itemFormDto.createItem();
itemRepository.save(item);
// 그림 저장하기
// 내가 저장한 이미지의 갯수만큼 돌림
for(int i=0; i<itemImgeFileList.size(); i++) {
ItemImg itemImg = new ItemImg();
// 기존에 있던 아이템의 id값을 세팅해야한다. 영속성 영역에 떠있기 때문에 지금 값이 채워져있는 상태가 돼서 그냥 값을 가져오기만 하면 됨
itemImg.setItem(item);
// 첫 번째 이미지면 대표 이미지로 씀
if(i == 0) {
itemImg.setRepimgYn("Y");
} else {
itemImg.setRepimgYn("N");
}
// 실제로 DB에 집어넣어야한다 파일 리스트에 있는 애들 중 i번째에 있는 애들을 꺼내서 등록을 해줌
itemImgService.saveItemImg(itemImg, itemImgeFileList.get(i));
}
// 아이디를 반환함
return item.getId();
}
7. 상품 등록 Controller
- 상품 등록 페이지 접근 (GetMapping)
@Controller
@RequiredArgsConstructor // @Autowired로도 쓸 수 있음
public class ItemController {
// @Autowired -> @RequiredArgsConstructor를 써도 됨 (final 붙여야함)
private final ItemService itemService;
// 웹 페이지로 이동
@GetMapping("/admin/item/new")
public String itemForm(Model model) {
model.addAttribute("itemFormDto", new ItemFormDto());
return "item/itemForm";
}
}
- 상품 등록 (PostMapping)
- 입력값 검증 및 에러 메시지 표시 참고
- POST 입력으로 들어온 이미지 파일 (name = "itemImgFile")을 MultipartFile 객체로 받음
- 입력값이 비정상이거나, 첫 번째 상품 이미지를 지정하지 않았으면 다시 상품 등록 페이지로 돌아감
- 입력값이 정상이면 itemService.saveItem(itemFormDto, itemImgFileList) 수행
// 웹 페이지에서 정보를 가져와 저장
@PostMapping("/admin/item/new")
// itemFormDto에 에러 발생 시 메시지 주려고 처리해놨으므로 @Valid 해줌
public String itemNew(@Valid ItemFormDto itemFormDto, BindingResult bindingResult,
Model model, @RequestParam("itemImgFile") List<MultipartFile> itemImgFileList) {
// 에러가 있는 경우 상품등록으로 다시 가기
if(bindingResult.hasErrors()) {
return "item/itemForm";
}
// 0번째 이미지 리스트가 값이 비었고 itemFormDto의 아이디가 없다면
if(itemImgFileList.get(0).isEmpty() && itemFormDto.getId() == null) {
model.addAttribute("errorMessage", "첫 번째 상품 이미지는 필수입니다.");
return "item/itemForm";
}
try {
itemService.saveItem(itemFormDto, itemImgFileList);
} catch (IOException e) {
e.printStackTrace();
model.addAttribute("errorMessage", "상품 등록 중에 오류 발생");
return "item/itemForm";
}
return "redirect:/";
}
8. 상품 등록 동작 과정
① "ADMIN" 권한을 가진 아이디로 상품 등록 페이지 Get 요청
② Item Controller 에서 상품 등록 페이지를 반환하면서 itemFormDto 객체도 넘김
③ 상품 등록 페이지에서 상품 정보 및 이미지를 입력하고 "저장" (POST 요청)
④ Item Controller 에서 입력값을 검증하고 itemService.saveItem() 메소드를 수행
- 이 때, 파라미터는 입력받은 itemFormDto 객체와 이미지 정보를 담고있는 itemImgFileList 를 넘김
⑤ itemService 에서 itemFormDto 객체를 item 엔티티로 변환하고 itemRepository.save() 메소드 수행
⑥ itemService 에서 itemImg 객체를 생성하고 itemImgService.saveItemImg() 메소드 수행
- 이 때, 파라미터는 itemImg 객체와 이미지 정보를 담고있는 itemImgFileList.get(i) 객체 하나를 지정
⑦ ItemImgService 에서 상품 이미지가 존재한다면 fileService.uploadFile() 메소드 수행
- 이 때, 파라미터는 "저장위치", "원래 파일명", "이미지 Byte 파일"
⑧ FileService 에서 UUID 객체를 이용해 파일명을 새로 만들고 FileOutputStream 을 이용하여 저장
⑨ ItemImgService 에서 itemImgRepository.save() 메소드 수행
'Web & Android > 스프링 부트 쇼핑몰 프로젝트 with JPA' 카테고리의 다른 글
[스프링 부트 쇼핑몰 프로젝트 with JPA] 8-1. [상품 관리] 상품 목록 조회 Querydsl (0) | 2023.10.16 |
---|---|
[스프링 부트 쇼핑몰 프로젝트 with JPA] 7. 상품 수정 (0) | 2023.10.16 |
[스프링 부트 쇼핑몰 프로젝트 with JPA] 6-1. [상품 등록] Entity, DTO, View (0) | 2023.10.15 |
[스프링 부트 쇼핑몰 프로젝트 with JPA] 5. Entity 공통 속성 공통화(Auditing) (0) | 2023.10.15 |
[스프링 부트 쇼핑몰 프로젝트 with JPA] 4. 페이지 권한 설정 (0) | 2023.10.15 |