상품 등록 구현

심규환·2022년 2월 6일
0

Shop

목록 보기
1/10

모든 내용은 백견의불여일타 서적을 통해 배운 것을 정리하기 위해 작성했습니다. 기본적인 쇼핑몰 구현에서 V2로 만들어 나갈 예정입니다.

먼저 Entity를 생성합니다. Entity는 Item, ItemImg로 상품과 상품 이미지를 저장할 수 있도록 생성하겠습니다. 또 item 등록시 정보를 전달 받을 ItemFormDto도 별도로 생성해 놓습니다.

Entity

Item.Java

기본적으로 아이템 이름, 가격, 재고, 상품 정보, (품절, 판매) 상태를 가지도록 합니다.

public class Item extends BaseEntity {
    @Id @Column(name = "item_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 50)
    private String itemNm;

    @Column(name="price",nullable = false)
    private int price;

    @Column(nullable = false)
    private int stockNumber;

    @Lob
    @Column(nullable = false)
    private String itemDetail;

    @Enumerated(EnumType.STRING)
    private ItemSellStatus itemSellStatus;

    @Builder
    public Item(String itemNm, int price, int stockNumber, String itemDetail, ItemSellStatus itemSellStatus) {
        this.itemNm = itemNm;
        this.price = price;
        this.stockNumber = stockNumber;
        this.itemDetail = itemDetail;
        this.itemSellStatus = itemSellStatus;
    }

ItemImg.Java

하나의 아이템은 여러 개의 아이템 이미지를 저장할 수 있도록 다대일 방식으로 item을 매핑합니다. 그래서 item_id를 통해서 포함되는 이미지를 모두 가져올 수 있도록 설계합니다.
repImgYn은 대표 이미지 여부를 나타냅니다. imgName은 로컬 저장소에 저장할 이름을 나타내며 oriImgName은 원본 이름을 저장합니다. imgUrl은 url 정보를 나타냅니다.

public class ItemImg extends BaseEntity{

    @Id
    @Column(name ="item_img_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    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;

    @Builder
    public ItemImg(String imgName, String oriImgName, String imgUrl, String repImgYn, Item item) {
        this.imgName = imgName;
        this.oriImgName = oriImgName;
        this.imgUrl = imgUrl;
        this.repImgYn = repImgYn;
        this.item = item;
    }

    public void updateItemImg(String oriImgName, String imgName, String imgUrl){
        this.oriImgName = oriImgName;
        this.imgName = imgName;
        this.imgUrl = imgUrl;
    }
}

ItemFormDto.Java

@Valid로 데이터의 유효성을 1차적으로 검증할 수 있도록 어노테이션 처리를 해놓습니다. itemImgDtoList와 itemImgIds는 나중에 수정할 때 사용하기 위해서 넣어둡니다.
ItemFormDto -> Item 으로 생성할 때, modelMapper를 사용해서 간단하게 받을 수 있지만 그러면 item에 @Setter를 추가해야 하기 때문에 Builder 생성자로 반환했습니다.
Dto -> Item, Item -> Dto의 변환은 Dto에서 관리할 수 있도록 설계했습니다.

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(){
        Item item = Item.builder()
                .itemDetail(this.itemDetail)
                .itemSellStatus(this.itemSellStatus)
                .itemNm(itemNm)
                .stockNumber(stockNumber)
                .price(price)
                .build();
        return item;
     //   return modelMapper.map(this, Item.class);
    }

    public static ItemFormDto of(Item item){
        return modelMapper.map(item,ItemFormDto.class);
    }
}

Controller

ItemController.Java

먼저 model에 Dto 객체를 담아서 itemForm.html에 같이 전달해 줍니다.

@GetMapping("/admin/item/new")
    public String itemForm(Model model){
        model.addAttribute("itemFormDto", new ItemFormDto());
        return "item/itemForm";
    }

Post 방식으로 데이터 변경을 요청받을 수 있도록 매핑합니다. BindingResult 에는 @Valid 결과가 담겨져 있습니다.

<유효성 검사>

  1. 먼저 기본 입력 값에서 에러가 있는지 확인 후 있으면 다시 itemForm으로 반환합니다.
    if(bindingResult.hasErrors())
  2. 이미지 파일이 있는지 여부와 아이템이 담겨져 있는지 확인
  3. itemService.saveItem 안에 예외처리를 해놨기 때문에 try,catch문을 생성하여 Dto와 이미지 리스트를 전달한다.
@PostMapping("/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:/";
    }

Service

ItemService.Java

먼저 itemFormDto의 객체를 Item 객체로 변환한 뒤, itemRepository에 저장한다. 저장과 동시에 insert문이 생성되고 item_id 값이 할당된다.
이미지 개수만큼 itemImg 객체를 생성한 뒤, item과 첫 번째 이미지는 RepImgYn("Y")로 설정해 놓는다.
생성한 itemImg 객체와 MultipartFile객체를 하나씩 itemImgService.saveItemImg에 전달한다.
itemImg의 저장 및 관리는 itemImgService가 관리하도록 하는게 좋다.

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

ItemImgService.Java

application.properties에 itemImgLocation의 경로를 지정해 놓고 필드값으로 받아온다.
전달받은 itemImgFile에서 getOriginalFilename()을 사용해서 원본 이미지 이름을 가져옵니다.
또 전달받은 itemImg 변수에는 item과 repImgYn 값 말고는 정보가 없습니다. 저장하기 위해서는 나머지 정보가 필요하니 값으로 넣을 imgName, imgUrl을 선언해 둡니다.

먼저 oriImgName에 제대로 된 이미지 이름이 들어있는지 확인하고 만약 있다면 이미지를 저장할 경로와, 원본 이미지 이름, byte[] 값을 FileService에 전달합니다.
이미지를 저장할 경로를 FileService가 아닌 ItemImgService에 선언한 이유는 FileService는 단순히 경로만 전달오면 처리할 수 있도록 하여 하나의 책임을 가지도록 하고 경로의 지정은 이미지를 저장시킬 ItemImgService가 하게 합니다.

FileService에서 저장된 이미지 정보를 받고 url 값도 추가로 지정합니다. 이제 itemImg에 나머지 값들을 추가하고 itemImgRepository에 저장합니다.

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

FileService.Java

이미지 이름을 확장자를 빼고 걸러준 뒤, UUID 값을 붙여서 saveFileName에 저장합니다.
UUID는 겹칠 확률이 매우 적기 때문에 확연한 구분을 위해 UUID 값으로 더해줍니다. FileOutputStream을 사용해서 저장할 경로와 파일명을 더해서 Stream을 열고 fos.write(byte[]) 이미지 정보를 넣어서 저장합니다.
그리고 저장한 이미지 이름을 반환합니다.

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("파일이 존재하지 않습니다.");
        }
    }
profile
장생농씬가?

0개의 댓글