일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 멋사 면접
- django
- 멋사 서류평가
- 멋쟁이사자처럼대학
- 멋쟁이사자처럼
- 멋사
- 멋사10기
- 코딩동아리
- 멋쟁이사자처럼 서류
- 파이썬
- 멋사 합격
- 멋사 서류
- API
- 멋사11기
- 멋쟁이사자처럼10기
- 깃허브
- ㅏㄴ
- 알림봇
- 디스코드봇
- 멋사12
- 멋쟁이 사자처럼
- discord
- 백엔드
- 크롤링
- 멋쟁이사자처럼11기
- IT동아리
- 멋사 10기
- 파이썬 크롤링
- 웹동아리
- 기사 제목 크롤링
- Today
- Total
ACHO.pk devlog
[Springboot] 상품 등록하기 본문
1. 상품 등록하기
상품의 이미지를 저장하는 상품 이미지 엔티티를 만들어보자
상품 이미지 엔티티는 이미지 파일명, 원본 이미지 파일명, 이미지 조회 경로, 대표 이미지 여부를 갖도록 설계한다.
item_img 엔티티에는 item_img_id, item_id, img_name, ori_img_name, img_url, rep_img_yn으로 설정해준다.
entity > ItemImg
package com.shop.shop.entity;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Table(name="item_img")
@Getter
@Setter
public class ItemImg extends BaseEntity{
@Id
@Column(name="item_img_id")
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String imgName; //이미지 파일명
private String oriImgName; //원본 이미지 파일명
private String imgUrl; //이미지 조회 경로
private String repimgYn; //대표 이미지 여부
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "item_id")
private Item item;
public void updateItemImg(String oriImgName, String imgName, String imgUrl){
this.oriImgName = oriImgName;
this.imgName = imgName;
this.imgUrl = imgUrl;
}
}
▹상품 엔티티와 다대일 단방향 관계로 매핑한다. 지연 로딩을 설정하여 매핑된 상품 엔티티 정보가 필요할 경우 데이터를 조회하도록 하자.
@ManyToOne(fetch = FetchType.LAZY)
▹원본 이미지 파일명, 업데이트할 이미지 파일명, 이미지 경로를 파라미터로 입력 받아서 이미지 정보를 업데이트하는 메소드다.
public void updateItemImg(String oriImgName, String imgName, String imgUrl){
상품 등록 및 수정에 사용할 데이터 전달용 DTO 클래스를 만들어보자. 엔티티 자체를 화면으로 반환할 수 있지만 그럴 때마다 사용하는 값이 추가가 된다. 실제 쇼핑몰에서는 상품 등록 페이지에 많은 데이터를 입력해야할 수도 있다. 상품을 등록할 때는 화면으로부터 전달받은 DTO 객체를 엔티티 객체로 변환하는 작업을 해야 하고, 상품을 조회할 때는 엔티티 객체를 DTO 객체로 바꿔주는 작업을 해야한다. 멤버 변수가 많아지면 상당한 시간을 소모하게 된다.
이를 도와주는 라이브러리로 modelmapper가 있다. 서로 다른 클래스의 값을 필드의 이름과 자료형이 같으면 getter, setter를 통해 값을 복사해서 객체를 반환해준다. pom.xml에 의존성을 추가해주자.
pom.xml
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.9</version>
</dependency>
상품 저장 후 상품 이미지에 대한 데이터를 전달할 DTO 클래스를 만든다.
dto > ItemImgDto.java
package com.shop.shop.dto;
import com.shop.shop.entity.ItemImg;
import lombok.Getter;
import lombok.Setter;
import org.modelmapper.ModelMapper;
@Getter
@Setter
public class ItemImgDto {
private Long id;
private String imgName;
private String oriImgName;
private String imgUrl;
private String repImgYn;
private static ModelMapper modelMapper = new ModelMapper();
public static ItemImgDto of(ItemImg itemImg) {
return modelMapper.map(itemImg,ItemImgDto.class);
}
}
▹멤버 변수로 ModelMapper 객체를 추가한다.
private static ModelMapper modelMapper = new ModelMapper();
▹ItemImg 엔티티 객체를 파라미터로 받아서 ItemImg 객체의 자료형과 멤버변수의 이름이 같을 때 ItemImgDto로 값을 복사해서 반환한다. static 메소드로 선언해 ItemImgDto 객체를 생성하지 않아도 호출할 수 있도록 한다.
return modelMapper.map(itemImg,ItemImgDto.class);
상품 데이터 정보를 전달하는 DTO를 만들어보자.
dto > ItemFormDto.java
package com.shop.shop.dto;
import com.shop.shop.constant.ItemSellStatus;
import com.shop.shop.entity.Item;
import lombok.Getter;
import lombok.Setter;
import org.modelmapper.ModelMapper;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
public class ItemFormDto {
private Long id;
@NotBlank(message = "상품명은 필수 입력 값입니다.")
private String itemNm;
@NotNull(message = "가격은 필수 입력 값입니다.")
private Integer price;
@NotBlank(message = "상품 상세는 필수 입력 값입니다.")
private String itemDetail;
@NotNull(message = "재고는 필수 입력 값입니다.")
private Integer stockNumber;
private ItemSellStatus itemSellStatus;
private List<ItemImgDto> itemImgDtoList = new ArrayList<>();
private List<Long> itemImgIds = new ArrayList<>();
private static ModelMapper modelMapper = new ModelMapper();
public Item createItem(){
return modelMapper.map(this, Item.class);
}
public static ItemFormDto of(Item item){
return modelMapper.map(item,ItemFormDto.class);
}
}
▹상품 저장 후 수정할 때 상품 이미지 정보를 저장하는 리스트이다.
private List<ItemImgDto> itemImgDtoList = new ArrayList<>();
▹상품의 이미지 아이디를 저장하는 리스트다. 상품 등록 시에는 아직 상품의 이미지를 저장하지 않았기 때문에 아무 값도 들어가 있지 않고 수정 시에 이미지 아이디를 담아둘 용도로 사용한다.
private List<Long> itemImgIds = new ArrayList<>();
▹modelMapper를 이용하여 엔티티 객체와 DTO 객체 간의 데이터를 복사하여 복사한 객체를 반환해주는 메소드이다.
return modelMapper.map(this, Item.class);
return modelMapper.map(item,ItemFormDto.class);
상품 등록 페이지로 접근할 수 있도록 기존에 만들어 두었던 ItemController 클래스도 수정하자. ItemFormDto를 model 객체에 담아서 뷰로 전달한다.
controller > ItemController.java
package com.shop.shop.controller;
import com.shop.shop.dto.ItemFormDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
@RequiredArgsConstructor
public class ItemController {
@GetMapping(value = "/admin/item/new")
public String itemForm(Model model){
model.addAttribute("itemFormDto", new ItemFormDto());
return "item/itemForm";
}
}
상품 등록 페이지 소스를 작성하자.
item > ItemForm.html
https://github.com/roadbook2/shop/blob/master/src/main/resources/templates/item/itemForm.html
여기서 소스 가져와서 쓰면 된다. 너무 길다.
▹상품 등록 시 실패 메시지를 받아서 상품 등록 페이지에 재진입 시 alert를 통해서 실패 사유를 보여준다.
var errorMessage = [[${errorMessage}]];
▹파일 첨부 시 이미지 파일인지 검사를 한다. 보통 데이터를 검증할 때는 스크립트에서 밸리데이션을 한 번하고, 스크립트는 사용자가 변경이 가능하므로 서버에서 한 번 더 밸리데이션을 한다. 스크립트에서 밸리데이션을 하는 이유는 서버 쪽으로 요청을 하면 네트워크를 통해 서버에 요청이 도착하고 다시 그 결과를 클라이언트에 반환하는 등 리소스를 소모하기 때문이다.
if(fileExt != "jpg" && fileExt != "jpeg" && fileExt != "gif" && fileExt != "png" && fileExt != "bmp"){
▹label 태그 안의 내용을 jquery의 .html() 을 이용하여 파일명을 입력해준다.
$(this).siblings(".custom-file-label").html(fileName);
▹상품 이미지 정보를 담고 있는 리스트가 비어 있다면 상품을 등록하는 경우다.
<div th:if="${#lists.isEmpty(itemFormDto.itemImgDtoList)}">
▹타임리프의 유틸리티 객체 #numbers.sequence(start, end)를 이용하면 start 부터 end까지 반복 처리를 할 수 있다. 상품 등록 시 이미지의 개수를 최대 5개로 한다. num에는 1부터 5까지 숫자가 할당된다.
<div class="form-group" th:each="num: ${#numbers.sequence(1,5)}">
▹label 태그에는 몇번째 상품 이미지인지 표시를 한다.
<label class="custom-file-label" th:text="상품이미지 + ${num}"></label>
▹상품 이미지 정보를 담고 있는 리스트가 비어 있지 않다면 상품을 수정하는 경우다.
<div th:if = "${not #lists.isEmpty(itemFormDto.itemImgDtoList)}">
▹상품 수정 시 어떤 이미지가 수정됐는지를 알기 위해서 상품 이미지의 아이디를 hidden 값으로 숨겨둔다.
<input type="hidden" name="itemImgIds" th:value="${itemImgDto.id}">
▹타임리프의 유틸리티 객체인 #string.isEmpty(string)을 이용하여 저장된 이미지 정보가 있다면 파일의 이름을 보여주고, 없다면 '상품 이미지 + 번호'를 출력한다.
<label class="custom-file-label" th:text="${not #strings.isEmpty(itemImgDto.oriImgName)} ? ${itemImgDto.oriImgName} : '상품이미지' + ${status.index+1}"></label>
▹상품 아이디가 없는 경우 저장 로직을 호출하는 버튼을 보여준다.
<div th:if="${#strings.isEmpty(itemFormDto.id)}" style="text-align: center">
▹상품의 아이디가 있는 경우 수정 로직을 호출하는 버튼을 보여준다.
<div th:unless="${#strings.isEmpty(itemFormDto.id)}" style="text-align: center">
이제 상품 등록 페이지에 접근해보자.
애플리케이션을 재실행하면 테이블을 삭제하고 다시 만들기 때문에 이전에 가입했던 회원 데이터도 삭제된다. 이런 과정을 생략하기 위해서 application.properties의 ddl-auto 속성을 vaildate로 변경하면 애플리케이션 실행 시점에 테이블을 삭제한 후 재생성하지 않으며 엔티티와 테이블이 매핑이 정상적으로 되어 있는지만 확인한다.
application.properties
spring.jpa.hibernate.ddl-auto=validate
설정이 validate라면 테스트 코드 실행 시 테이블이 자동으로 생성되지 않으므로 테스트 환경에서는 create로 바꿔줘야 한다.
상품 등록 메뉴를 클릭하면 방금 전에 만들었던 상품 등록 페이지가 보이는 것을 확인할 수 있다.
에러다.. 브라우저가 요청조차 하지 못하는 상황이다. 인터넷 연결도 잘 되고 있는데 왜...
crbug/1173575, non-JS module files deprecated.
문제는 바로 "상품 등록 페이지"가 ADMIN 유저만 들어갈 수 있는데, 내가 로그인을 안 해서 그렇다.
ADMIN 계정으로 로그인할 경우 아래 화면과 같이 잘 뜬다 !!!!
이제 상품을 등록하기 위한 로직을 구현해보자. 이미지 파일을 등록할 때 서버에서 각 파일의 최대 사이즈와 한 번에 다운 요청할 수 있는 파일의 크기를 지정할 수 있다.
또한 컴퓨터에서 어떤 경로에 저장할지를 관리하기 위해서 프로퍼티에 추가해줘야한다. 프로젝트 내부가 아닌 자신의 컴퓨터에서 파일을 찾는 경로로 uploadPath 프로퍼티를 추가한다. C 폴더 아래에 shop/item 폴더를 만든 후 이곳에 이미지를 저장한다.
#파일 한 개당 최대 사이즈
spring.servlet.multipart.maxFileSize=20MB
#요청당 최대 파일 크기
spring.servlet.multipart.maxRequestSize=100MB
#상품 이미지 업로드 경로
itemImgLocation=C:/shop/item
#리소스 업로드 경로
uploadPath=file:///C:/shop/
업로드한 파일을 읽어올 경로를 설정하자. WebMvcConfigurer 인터페이스를 구현하는 WebMvcConfig.java 파일을 작성한다. addResourceHandlers 메소드를 통해서 자신의 로컬 컴퓨터에 업로드한 파일을 찾을 위치를 설정한다.
config > WebMvcConfig.java
package com.shop.shop.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 {
@Value("${uploadPath}")
String uploadPath;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/images/**")
.addResourceLocations(uploadPath);
}
}
▹application.properties에 설정한 "uploadPath" 프로퍼티 값을 읽어온다.
@Value("${uploadPath}")
▹웹 브라우저에 입력하는 url에 /images로 시작하는 경우 uploadPath에 설정한 폴더를 기준으로 파일을 읽어오도록 설정한다.
registry.addResourceHandler("/images/**")
▹로컬 컴퓨터에 저장된 파일을 읽어올 root 경로를 설정한다.
.addResourceLocations(uploadPath);
다음으로 파일을 처리하는 FileService 클래스를 만들어보자. 파일을 업로드하는 메소드와 삭제하는 메소드를 작성한다.
service > FileService.java
package com.shop.shop.service;
import lombok.extern.java.Log;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileOutputStream;
import java.util.UUID;
@Service
@Log
public class FileService {
public String uploadFile(String uploadPath, String originalFileName, byte[] fileData) throws Exception{
UUID uuid = UUID.randomUUID();
String extension = originalFileName.substring(originalFileName.lastIndexOf("."));
String savedFileName = uuid.toString() + extension;
String fileUploadFullUrl = uploadPath + "/" + savedFileName;
FileOutputStream fos = new FileOutputStream(fileUploadFullUrl);
fos.write(fileData);
fos.close();
return savedFileName;
}
public void deleteFile(String filePath) throws Exception{
File deleteFile = new File(filePath);
if(deleteFile.exists()) {
deleteFile.delete();
log.info("파일을 삭제하였습니다.");
} else {
log.info("파일이 존재하지 않습니다.");
}
}
}
▹UUID는 서로 다른 개체들을 구별하기 위해서 이름을 부여할 때 사용한다. 실제 사용 시 중복될 가능성이 거의 없기 때문에 파일의 이름으로 사용하면 파일명 중복 문제를 해결할 수 있다.
UUID uuid = UUID.randomUUID();
▹UUID로 받은 값과 원래 파일의 이름이 확장자를 조합해서 저장될 파일 이름을 만든다.
String savedFileName = uuid.toString() + extension;
▹FileOutputStream 클래스는 바이트 단위의 출력을 내보내는 클래스이다. 생성자로 파일이 저장될 위치와 파일의 이름을 넘겨 파일에 쓸 파일 출력 스트림을 만든다.
FileOutputStream fos = new FileOutputStream(fileUploadFullUrl);
▹fileData를 파일 출력 스트림에 입력한다.
fos.write(fileData);
▹업로드된 파일의 이름을 반환한다.
return savedFileName;
▹파일이 저장된 경로를 이용하여 파일 객체를 생성한다.
File deleteFile = new File(filePath);
▹해당 파일이 존재하면 파일을 삭제한다.
if(deleteFile.exists()) {
상품의 이미지 정보를 저장하기 위해서 repository 패키지 아래에 JpaRepository를 상속받는 ItemImgRepository 인터페이스를 만들어야 한다.
repository > ItemImgRepository.java
package com.shop.shop.repository;
import com.shop.shop.entity.ItemImg;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ItemImgRepository extends JpaRepository<ItemImg, Long> {
}
상품 이미지를 업로드하고, 상품 이미지 정보를 저장하는 ItemImgService 클래스를 service 패키지 아래에 생성한다.
service > ItemImgService.java
package com.shop.shop.service;
import com.shop.shop.entity.ItemImg;
import com.shop.shop.repository.ItemImgRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.thymeleaf.util.StringUtils;
import javax.transaction.Transactional;
@Service
@RequiredArgsConstructor
@Transactional
public class ItemImgService {
@Value("${itemImgLocation}")
private String itemImgLocation;
private final ItemImgRepository itemImgRepository;
private final FileService fileService;
public void saveItemImg(ItemImg itemImg, MultipartFile itemImgFile) throws Exception{
String oriImgName = itemImgFile.getOriginalFilename();
String imgName = "";
String imgUrl = "";
//파일 업로드
if(!StringUtils.isEmpty(oriImgName)){
imgName = fileService.uploadFile(itemImgLocation, oriImgName,
itemImgFile.getBytes());
imgUrl = "/images/item/" + imgName;
}
//상품 이미지 정보 저장
itemImg.updateItemImg(oriImgName, imgName, imgUrl);
itemImgRepository.save(itemImg);
}
}
▹@Value 어노테이션을 통해 application.properties 파일에 등록한 itemImgLocation 값을 불러와서 itemImgLocation 변수에 넣어준다.
@Value("${itemImgLocation}")
▹사용자가 상품의 이미지를 등록했다면 저장할 경로와 파일의 이름, 파일을 파일의 바이트 배열을 파일 업로드 파라미터로 uploadFile 메소드를 호출한다. 호출 결과 로컬에 저장된 파일의 이름을 imgName 변수에 저장한다.
imgName = fileService.uploadFile(itemImgLocation, oriImgName,
itemImgFile.getBytes());
▹저장한 상품 이미지를 불러올 경로를 설정한다. 외부 리소스를 불러오는 urlPatterns로 WebMvxConfig 클래스에서 "/images/**"를 설정해줬다. 또한 application.properties에서 설정한 uploadPath 프로퍼티 경로인 "C:/shop//" 아래 item 폴더에 이미지를 저장하므로 상품 이미지를 불러오는 경로로 "/images/item/"를 붙여준다.
imgUrl = "/images/item/" + imgName;
▹입력받은 상품 이미지 정보를 저장한다.
itemImg.updateItemImg(oriImgName, imgName, imgUrl);
itemImgRepository.save(itemImg);
imgName : 실제 로컬에 저장된 상품 이미지 파일의 이름
orilmgName : 업로드했던 상품 이미지 파일의 원래 이름
imgUrl : 업로드 결과 로컬에 저장된 상품 이미지 파일을 불러오는 경로
상품을 등록하는 ItemService 클래스를 만들어보자.
item > ItemService.java
package com.shop.shop.service;
import com.shop.shop.dto.ItemFormDto;
import com.shop.shop.entity.Item;
import com.shop.shop.entity.ItemImg;
import com.shop.shop.repository.ItemImgRepository;
import com.shop.shop.repository.ItemRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.transaction.Transactional;
import java.util.List;
@Service
@Transactional
@RequiredArgsConstructor
public class ItemService {
private final ItemRepository itemRepository;
private final ItemImgService itemImgService;
private final ItemImgRepository itemImgRepository;
public Long saveItem(ItemFormDto itemFormDto, List<MultipartFile> itemImgFileList) throws Exception{
//상품 등록
Item item = itemFormDto.createItem();
itemRepository.save(item);
//이미지 등록
for(int i=0;i<itemImgFileList.size();i++){
ItemImg itemImg = new ItemImg();
itemImg.setItem(item);
if(i == 0)
itemImg.setRepimgYn("Y");
else
itemImg.setRepimgYn("N");
itemImgService.saveItemImg(itemImg, itemImgFileList.get(i));
}
return item.getId();
}
}
▹상품 등록 폼으로부터 입력 받은 데이터를 이용하여 item 객체를 생성한다.
Item item = itemFormDto.createItem();
▹상품 데이터를 저장한다.
itemRepository.save(item);
▹첫번째 이미지일 경우 대표 상품 이미지 여부 값을 "Y"로 세팅한다. 나머지 상품 이미지는 "N"으로 설정한다.
if(i == 0)
▹상품의 이미지 정보를 저장한다.
itemImgService.saveItemImg(itemImg, itemImgFileList.get(i));
상품을 등록하는 url을 ItemController 클래스에 추가하자.
controller > ItemController.java
package com.shop.shop.controller;
import com.shop.shop.dto.ItemFormDto;
import com.shop.shop.service.ItemService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.Valid;
import java.util.List;
@Controller
@RequiredArgsConstructor
public class ItemController {
private final ItemService itemService;
@GetMapping(value = "/admin/item/new")
public String itemForm(Model model){
model.addAttribute("itemFormDto", new ItemFormDto());
return "item/itemForm";
}
@PostMapping(value = "/admin/item/new")
public String itemNew(@Valid ItemFormDto itemFormDto, BindingResult bindingResult,
Model model, @RequestParam("itemImgFile") List<MultipartFile> itemImgFileList){
if(bindingResult.hasErrors()){
return "item/itemForm";
}
if(itemImgFileList.get(0).isEmpty() && itemFormDto.getId() == null){
model.addAttribute("errorMessage", "첫번째 상품 이미지는 필수 입력 값 입니다.");
return "item/itemForm";
}
try {
itemService.saveItem(itemFormDto, itemImgFileList);
} catch (Exception e){
model.addAttribute("errorMessage", "상품 등록 중 에러가 발생하였습니다.");
return "item/itemForm";
}
return "redirect:/";
}
}
▹상품 등록 시 필수 값이 없다면 다시 상품 등록 페이지로 전환한다.
if(bindingResult.hasErrors()){
▹상품 등록 시 첫 번째 이미지가 없다면 에러 메시지와 함께 상품 등록 페이지로 전환한다. 상품의 첫 번째 이미지는 메인 페이지에서 보여줄 상품 이미지로 사용하기 위해서 필수 값으로 지정한다.
if(itemImgFileList.get(0).isEmpty() && itemFormDto.getId() == null){
▹상품 저장 로직을 호출한다. 매개 변수로 상품 정보와 상품 이미지 정보를 담고 있는 itemImgFileList를 넘겨준다.
try {
itemService.saveItem(itemFormDto, itemImgFileList);
▹상품이 정상적으로 등록되었다면 메인 페이지로 이동한다.
return "redirect:/";
2-1. 테스트 코드 작성
상품 저장 로직 테스트 코드를 작성해보자. ItemImgRepository 인터페이스에 findByItemIdOrderByIdAsc 메소드를 추가하면 된다. 매개변수로 넘겨준 상품 아이디를 가지며, 상품 이미지 아이디의 오름차순으로 가져오는 쿼리 메소드이다.
ItemImgRepository
package com.shop.shop.repository;
import com.shop.shop.entity.ItemImg;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ItemImgRepository extends JpaRepository<ItemImg, Long> {
List<ItemImg> findByItemIdOrderByIdAsc(Long iemId);
}
ItemServiceTest.java
package com.shop.shop.service;
import com.shop.shop.constant.ItemSellStatus;
import com.shop.shop.dto.ItemFormDto;
import com.shop.shop.entity.Item;
import com.shop.shop.entity.ItemImg;
import com.shop.shop.repository.ItemImgRepository;
import com.shop.shop.repository.ItemRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.TestPropertySource;
import org.springframework.web.multipart.MultipartFile;
import javax.persistence.EntityNotFoundException;
import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
@TestPropertySource(locations="classpath:application-test.properties")
class ItemServiceTest {
@Autowired
ItemService itemService;
@Autowired
ItemRepository itemRepository;
@Autowired
ItemImgRepository itemImgRepository;
List<MultipartFile> createMultipartFiles() throws Exception{
List<MultipartFile> multipartFileList = new ArrayList<>();
for(int i=0;i<5;i++){
String path = "C:/shop/item/";
String imageName = "image" + i + ".jpg";
MockMultipartFile multipartFile =
new MockMultipartFile(path, imageName, "image/jpg", new byte[]{1,2,3,4});
multipartFileList.add(multipartFile);
}
return multipartFileList;
}
@Test
@DisplayName("상품 등록 테스트")
@WithMockUser(username = "admin", roles = "ADMIN")
void saveItem() throws Exception {
ItemFormDto itemFormDto = new ItemFormDto();
itemFormDto.setItemNm("테스트상품");
itemFormDto.setItemSellStatus(ItemSellStatus.SELL);
itemFormDto.setItemDetail("테스트 상품 입니다.");
itemFormDto.setPrice(1000);
itemFormDto.setStockNumber(100);
List<MultipartFile> multipartFileList = createMultipartFiles();
Long itemId = itemService.saveItem(itemFormDto, multipartFileList);
List<ItemImg> itemImgList = itemImgRepository.findByItemIdOrderByIdAsc(itemId);
Item item = itemRepository.findById(itemId)
.orElseThrow(EntityNotFoundException::new);
assertEquals(itemFormDto.getItemNm(), item.getItemNm());
assertEquals(itemFormDto.getItemSellStatus(), item.getItemSellStatus());
assertEquals(itemFormDto.getItemDetail(), item.getItemDetail());
assertEquals(itemFormDto.getPrice(), item.getPrice());
assertEquals(itemFormDto.getStockNumber(), item.getStockNumber());
assertEquals(multipartFileList.get(0).getOriginalFilename(), itemImgList.get(0).getOriImgName());
}
}
▹MockMultipartFile 클래스를 이용하여 가짜 리스트를 만들어서 반환해주는 메소드이다.
List<MultipartFile> createMultipartFiles() throws Exception{
▹상품 등록 화면에서 입력 받는 상품 데이터를 세팅해준다.
ItemFormDto itemFormDto = new ItemFormDto();
▹상품 데이터와 이미지 정보를 파라미터로 넘겨서 저장 후 저장된 상품의 아이디 값을 반환 값으로 리턴해준다.
Long itemId = itemService.saveItem(itemFormDto, multipartFileList);
▹입력한 상품 데이터와 실제로 저장된 상품 데이터가 같은지 확인한다.
assertEquals(itemFormDto.getItemNm(), item.getItemNm());
▹상품 이미지는 첫 번째 파일의 원본 이미지 파일 이름만 같은지 확인한다.
assertEquals(multipartFileList.get(0).getOriginalFilename(), itemImgList.get(0).getOriImgName());
결과
로컬서버를 돌린 후에 상품 등록 페이지에서 상품을 등록해보자.
그러면 본인이 지정한 파일 업로드 경로에 들어가보면 상품 등록할 때 업로드한 사진이 있음을 확인할 수 있다.
'프레임워크 > Springboot' 카테고리의 다른 글
[Springboot] 상품 관리하기 (2) | 2023.03.14 |
---|---|
[Springboot] 상품 수정하기 (0) | 2023.03.13 |
[Springboot] 연관 관계 매핑 (0) | 2023.03.06 |
[Springboot] 스프링 시큐리티를 이용한 로그인/로그아웃 (0) | 2023.02.26 |
[Springboot] 스프링 시큐리티를 이용한 회원가입 (0) | 2023.02.23 |