프로젝트

[Spring Boot & JPA 프로젝트] 상품등록 / 파일업로드(다중X)

sian han 2022. 8. 17. 06:09

판매자는 상품등록 시 반드시 상품이미지를 첨부해야하며, 상품이미지는 1장만 첨부가능하다

 

Item 클래스에 파일컬럼으로 FileEntity 클래스 변수를 넣고

OneToOne 단방향 매핑으로 연관관계를 맺어줬다 

 

엔티티별로 requestDto와 responseDto 를 만들어서 Entity 에 직접접근을 막음

 

▶ 상품등록 (이미지 등록)

 

Item

package com.proj.KnitMarket.domain.Item;

import com.proj.KnitMarket.Constant.SellStatus;
import com.proj.KnitMarket.domain.BaseEntity;
import com.proj.KnitMarket.domain.Member.Seller;
import lombok.*;

import javax.persistence.*;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name="item")
@Entity
public class Item extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id; //상품코드

    private String itemName; //상품명

    private int price; //상품가격

    private String itemDesc; //상품 상세 설명

    @ManyToOne
    @JoinColumn(name="seller_id")
    private Seller seller; //상품 등록자

    @OneToOne(cascade = CascadeType.REMOVE)
    @JoinColumn(name="fileEntity_id")
    private FileEntity file;

    @Enumerated(EnumType.STRING)
   private SellStatus sellStatus; //상품 판매 상태

    @Builder
    public Item(String itemName, int price, String itemDesc,Seller seller,FileEntity file,SellStatus sellStatus) {
        this.itemName = itemName;
        this.price = price;
        this.itemDesc = itemDesc;
        this.seller = seller;
        this.file = file;
        this.sellStatus = sellStatus;
    }
}

 

 

 

FileEntity

package com.proj.KnitMarket.domain.Item;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class FileEntity{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String orginFileName;

    private String filePath;

    @Builder
    public FileEntity(Long id, String orginFileName, String filePath) {
        this.id = id;
        this.orginFileName = orginFileName;
        this.filePath = filePath;
    }
}

 

 

 

Controller

 

컨트롤러에서 핵심은

인자로 받은 ItemRequestDto 에서 FileEntity 객체의 값들을 꺼내서

FileRequestDto 객체로 빌드한 것이다. 

 

이미지 경로를 application.properties 에 설정했었다가,

특정부분들이 내가생각한대로 작동하지않아서

클래스를 따로 만들어서 선언해놨다 (ConstUtil.UPLOAD_IMG_PATH_TEST)

본인이 파일을 저장할 경로를 설정해주시면 됩니다

@PostMapping(value = "/register")
    public String item_register_post(@ModelAttribute("item") ItemRequestDto itemDto, HttpSession httpSession, Model model) throws IOException {
        log.info("ItemRequestDto={}", itemDto.toString());
        String email = (String) httpSession.getAttribute("email");

        String url = "";
        String msg = "";

        if (itemDto.getFile() != null) {
            log.info("이미지 有");
            MultipartFile file = itemDto.getFile();
            String filePath = uploadDir + file.getOriginalFilename();
            file.transferTo(new File(filePath));
            log.info("file.getOriginalFilenaem={}", file.getOriginalFilename());
            log.info("filePath={}", filePath);

            FileRequestDto fileDto = FileRequestDto.builder()
                    .orginFileName(file.getOriginalFilename())
                    .filePath(uploadDir + file.getOriginalFilename())
                    .build();

            Long itemId = itemService.save(itemDto, email, fileDto);

            log.info("상품번호 ={}", itemId);

            url = "/knitmarket/";
            msg = "상품등록이 완료되었습니다";
        } else {
            url = "/knitmarket/";
            msg = "상품등록에 실패했습니다 상품 정보를 확인해주세요 ! ";

        }
        model.addAttribute("url", url);
        model.addAttribute("msg", msg);
        return "/common/message";
    }

 

 

FileRequestDto

package com.proj.KnitMarket.dto;

import com.proj.KnitMarket.domain.Item.FileEntity;
import com.proj.KnitMarket.domain.Item.Item;
import lombok.*;

@Getter
@Setter
@ToString
@NoArgsConstructor
public class FileRequestDto {
    private String orginFileName;
    private String filePath;


    public FileEntity toEntity(){
        return FileEntity.builder()
                .orginFileName(orginFileName)
                .filePath(filePath)
                .build();
    }

    @Builder
    public FileRequestDto(String orginFileName, String filePath) {
        this.orginFileName = orginFileName;
        this.filePath = filePath;
    }
}

 

 

 

ItemRequestDto

package com.proj.KnitMarket.dto;

import com.proj.KnitMarket.Constant.SellStatus;
import com.proj.KnitMarket.domain.Item.FileEntity;
import com.proj.KnitMarket.domain.Item.Item;
import com.proj.KnitMarket.domain.Member.Seller;
import lombok.*;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Getter
@Setter
@ToString
@NoArgsConstructor
public class ItemRequestDto {

    @NotBlank(message = "상품명은 필수 입력 값입니다")
    private String itemName;

    @NotNull(message = "가격은 필수 입력 값입니다")
    private int price;

    private String itemDesc;
    private Seller seller;

    @NotNull(message = "상품이미지는 필수 입력 값입니다")
    private MultipartFile file;
    private FileEntity fileEntity;

    private SellStatus sellStatus;

//dto => entity
    public Item toEntity() {
        return Item.builder()
                .itemName(itemName)
                .price(price)
                .itemDesc(itemDesc)
                .seller(seller)
                .file(fileEntity)
                .sellStatus(sellStatus)
                .build();
    }

    @Builder
    public ItemRequestDto(String itemName, String itemDesc, int price, Seller seller, FileEntity file,SellStatus sellStatus) {
        this.price = price;
        this.itemDesc = itemDesc;
        this.itemName = itemName;
        this.seller = seller;
        this.fileEntity = file;
        this.sellStatus = sellStatus;
    }

}

 

 

 

FileService

save 메서드 반환타입을 평범하게 id 로 했었다가 ItemService 에서 절차를 줄이기 위해서

FileEntity 객체로 변경했다

 @Transactional
    public FileEntity save(FileRequestDto fileRequestDto){

        return fileRepository.save(fileRequestDto.toEntity());
    }

 

 

ItemService

File 테이블에 저장 후 Item 테이블에 저장

 @Transactional //아이템 등록
    public Long save(ItemRequestDto itemDto, String email, FileRequestDto fileRequestDto){
        Seller seller = sellerRepository.findByEmail(email);
        FileEntity file = fileService.save(fileRequestDto); // file 저장

        itemDto.setFileEntity(file);
        itemDto.setSeller(seller);
        Item item = itemDto.toEntity();

        return itemRepository.save(itemDto.toEntity()).getId();
    }

 

 


 

▶ 상품조회 (이미지 불러와서 조회)

 

Controller

    @GetMapping("/")
    public String index(Model model) {
        List<ItemResponseDto> itemDtoList = itemService.getItemList();
        model.addAttribute("itemList",itemDtoList);
        return "index";
    }

 

 

ItemService

 @Transactional
    public List<ItemResponseDto> getItemList(){
        List<Item> items = itemRepository.findAll();
        List<ItemResponseDto> itemDtoList = new ArrayList<>();

        for(Item item : items){
            ItemResponseDto responseDto = ItemResponseDto.builder()
                    .id(item.getId())
                    .itemName(item.getItemName())
                    .itemDesc(item.getItemDesc())
                    .orginFileName(item.getFile().getOrginFileName())
                    .price(item.getPrice())
                    .sellerName(item.getSeller().getName())
                    .build();

            itemDtoList.add(responseDto);
        }//for

        return itemDtoList;
    }

 

 

ItemResponseDto

package com.proj.KnitMarket.dto;

import com.proj.KnitMarket.domain.Item.Item;
import lombok.Builder;
import lombok.Getter;

@Getter
public class ItemResponseDto {
    private Long id;
    private String itemName;
    private int price;
    private String itemDesc;
    private String sellerName;
    private String orginFileName;

    //entity=>dto
    public ItemResponseDto(Item entity){
        this.id=entity.getId();
        this.itemName=entity.getItemName();
        this.itemDesc=entity.getItemDesc();
        this.price=entity.getPrice();
        this.sellerName = entity.getSeller().getName();
        this.orginFileName = entity.getFile().getOrginFileName();
    }

    @Builder
    public ItemResponseDto(Long id, String itemName, int price, String itemDesc, String sellerName, String orginFileName) {
        this.id = id;
        this.itemName = itemName;
        this.price = price;
        this.itemDesc = itemDesc;
        this.sellerName = sellerName;
        this.orginFileName = orginFileName;
    }
}

 

 

상품목록.html

<div class="card" style="width: 18rem;" th:each="item : ${itemList}">
                        <img th:src="@{/uploadImg/} + ${item.getOrginFileName()}" class="card-img-top" th:alt="${item.itemName}">
                        <div class="card-body">
                            <h5 class="card-title" th:text="${item.itemName}"></h5>
                            <p class="card-text" th:text="${item.price}"></p>
                            <a href="#" class="btn btn-primary">Go somewhere</a>
                        </div>
                    </div>

 


 

▶ 발생에러 : MaxUploadSizeExceededException

 

  ▷ 해결 : application.properties 에서 파일기본용량 설정

spring.servlet.multipart.maxFileSize=5MB
spring.servlet.multipart.maxRequestSize=5MB

 

 


 

만들고나니까 굳이 file 객체를 만들어서 저장할게아니라

이미지 경로만 Item 에 멤버변수로 넣을껄그랬나 싶기도 하다.

그렇게 해도 문제없이 프로그램이 돌아가겠지만 ( + 훨씬 편리하겠지만)

덕분에 requestDto, responseDto, Builder 로 머리좀싸매봤고 (이번에 처음 응용해서 사용해봤다)

코드가 좀 더 객체지향스럽게 보이는 것 같다 (그런것 "같다"고 행복회로 돌릴순있잖아요)

 

분리하는 걸 좋아해서 있는대로 쪼개서

DTO 와 Builder 를 남발하며 나아가고 있는데

잘가고있는건지 모르겠다.