[6] 상품 등록하기

Veloger·2023년 1월 3일
0

1. 상품 등록하기

✨ Item의 Image를 저장하는 상품 이미지 엔티티를 만들어보자.

  • Item과 Item_img는 일대다 관계이다.
_ItemImg_
@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;
    }
}

✨ 상품 등록 및 수정시 사용할 데이터 전달용 DTO

  • 상품 등록 시, 화면으로 받은 DTOEntity로 변환 작업 필요
  • 상품 조회 시, EntityDTO로 변환 작업 필요

이를 해결하기 위해 modelmapper를 이용한다.

modelmapper

서로 다른 클래스의 값을 (필드의 이름과 자료형이 같으면) getter, setter를 통해 값을 복사해서 그 객체를 반환함.

Gradle에서 ModelMapper 사용하기


상품 저장 후 상품 이미지에 대한 데이터를 전달하는 DTO

_ItemImgDto_
@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(); // 멤버변수로 ModelMapper 객체를 추가

    public static ItemImgDto of(ItemImg itemImg){ // itemImg와 멤버변수 이름이 같으면 ItemImgDto로 값을 복사해서 반환
        return modelMapper.map(itemImg, ItemImgDto.class);
    }
}
  • of()#map() : 파라미터인 ItemImg의 자료형과 멤버변수 이름이 같으면, ItemImgDto로 값을 복사하고 반환.

상품 데이터 정보를 전달하는 DTO

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

    // 엔티티 <--> DTO 데이터를 복사하여 반환
    public Item createItem(){
        return modelMapper.map(this, Item.class);
    }

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

✨ 상품 등록 페이지 접근하는 ItemController를 수정한다.

상품 등록같은 관리자 페이지는 데이터의 무결성을 보장해야한다.
한 상품의 내용이 수정되면 연관된 다른 데이터도 수정되어야 한다.

✨ 상품 등록 페이지 itemForm.html을 작성한다.


✨ 실행 전, 프로퍼티 파일 설정

상품 등록은 ADMIN만 가능하기 때문에 서버 재실행 시, 회원 정보가 사라지는 것을 막기위해 프로퍼티 파일을 수정한다.

application.properties

spring.jpa.hibernate.ddl-auto=validate

application-test.properties

spring.jpa.hibernate.ddl-auto=create

validate : 서버 재실행 해도 테이블이 재생성되지 않음

이미지 파일 등록 시, 파일의 크기 및 다운 요청할 수 있는 파일의 크기 등이 설정 가능하다.

# File Max Size
spring.servlet.multipart.maxFileSize=20MB

# Request File Max Size
spring.servlet.multipart.maxRequestSize=100MB

# Upload Image Location
itemImgLocation=C:/shop/item

# Resource Upload Location
uploadPath=file:///C:/shop/

✨ 업로드한 파일을 읽어올 경로 설정

WebMvcConfig
public class WebMvcConfig implements WebMvcConfigurer {

    @Value("${uploadPath}") // 프로퍼티에 설정한 uploadPath 프로퍼티 값 불러옴
    String uploadPath;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/images/**") // url에 /images로 시작하는 경우, uploadPath에 설정한 폴더를 기준으로 파일 읽어옴
                .addResourceLocations(uploadPath); // 로컬 컴퓨터에 저장된 파일을 읽어올 root 경로 설정
    }
}

✨ 파일을 처리하는 FileService 클래스

FileService
@Service
@Log
public class FileService {

    public String uploadFile(String uploadPath, String originalFileName,
                             byte[] fileData) throws Exception {
        UUID uuid = UUID.randomUUID(); // 1. UUID : 객체 구별을 위해 유일한 이름 부여
        String extension = originalFileName.substring(originalFileName.lastIndexOf("."));
        String savedFileName = uuid.toString() + extension; // 2. UUID와 조합해서 저장할 파일 이름 생성
        String fileUploadFullUrl = uploadPath + "/" +savedFileName;
        FileOutputStream fos = new FileOutputStream(fileUploadFullUrl); // 3. FileOutputStream : 바이트 단위의 출력을 내보냄. 생성자에 저장될 위치와 파일 이름 넘겨서 출력 스트림 만듬
        fos.write(fileData); // 4. fuleData를 파일 출력 스트림에 입력
        fos.close();
        return savedFileName; // 5. 업로드된 파일의 이름을 반환
    }

    public void deleteFile(String filePath) throws Exception{
        File deleteFile = new File(filePath); // 6. 파일이 저장된 경로를 이용해서 파일 객체 생성

        if(deleteFile.exists()){ // 7. 해당 파일이 존재하면 파일을 삭제
            deleteFile.delete();
            log.info("파일을 삭제하였습니다.");
        }
        else{
            log.info("파일이 존재하지 않습니다.");
        }
    }
}

상품 이미지 정보를 저장하는 ItemImgRepository 인터페이스를 생성.
상품이미지 업로드 및 정보를 저장하는 ItemImgService 생성

ItemImgService
@Service
@RequiredArgsConstructor
@Transactional
public class ItemImgService {

    @Value("${itemImgLocation}") // 1. 프로퍼티에 등록한 itemImgLocation을 String 객체에 저장
    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)){
            // 2. 상품의 이미지를 등록했다면, uploadFile을 통해 로컬에 저장된 파일의 이름을 받아옴
            imgName = fileService.uploadFile(itemImgLocation, oriImgName, itemImgFile.getBytes());
            // 3. 저장한 상품 이미지를 불러올 경로 / 프로퍼티의 uploadPath(C:/shop) 아래의 /images/item/imgName을 불러옴
            imgUrl = "/images/item/"+imgName;
        }

        // 상품 이미지 정보 저장
        // 4.5.
        // imgName : 실제 로컬에 저자오딘 이미지 파일 이름
        // oriImgName : 업로드했던 이미지 파일의 원래 이름
        // imgUrl : 업로드 결과 로컬에 저장된 이미지 파일을 불러오는 경로
        itemImg.updateItemImg(oriImgName, imgName, imgUrl);
        itemImgRepository.save(itemImg);
    }
}

✨ 상품을 등록하는 클래스

ItemService
@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();  // 1. 상품 등록 폼의 데이터로 item 생성
        itemRepository.save(item);  // 2. 상품 데이터 저장

        // 이미지 등록
        for(int i=0;i<itemImgFileList.size()> ; i++) {
            ItemImg itemImg = new ItemImg();
            itemImg.setItem(item);
            if(i==0){ // 3. 첫번째 이미지면 대표 이미지 설정
                itemImg.setRepImgYn("Y");
            }else{
                itemImg.setRepImgYn("N");
            }
            itemImgService.saveItemImg(itemImg, itemImgFileList.get(i)); // 4. 상품의 이미지 정보 저장
        }
        return item.getId();
    }
}

✨ 상품을 등록하는 URL을 컨트롤러에 추가하면 끝

이제 테스트 코드를 작성해서 정상적으로 이미지가 등록되는지 확인한다.


2. 상품 수정하기

등록한 상품의 내용을 수정하는 페이지를 만들어보자.
페이지는 기존 등록 페이지를 재활용해서 사용한다.

✨ 등록된 상품을 불러오는 메소드를 추가

ItemService
    @Transactional(readOnly = true) // 읽기전용으로 하면 JPA가 더티체킹(변경 감지) 안해서 성능향상
    public ItemFormDto getItemDtl(Long itemId){

        List<ItemImg> itemImgList =
                itemImgRepository.findByItemIdOrderByIdAsc(itemId); // 해당 상품 이미지 조회 / 등록순으로 가져옴
        List<ItemImgDto> itemImgDtoList = new ArrayList<>();
        for(ItemImg itemImg : itemImgList){ // 조회한 ItemImg 엔티티를 ItemImgDto객체로 만들어서 List에 추가
            ItemImgDto itemImgDto = ItemImgDto.of(itemImg);
            itemImgDtoList.add(itemImgDto);
        }

        Item item = itemRepository.findById(itemId) // 상품 아이디로 상품 엔티티 조회 / 없으면 익센션
                .orElseThrow(EntityNotFoundException::new);
        ItemFormDto itemFormDto = ItemFormDto.of(item);
        itemFormDto.setItemImgDtoList(itemImgDtoList);
        return itemFormDto;
    }
  • 실무에선 데이터가 많아 등록용 페이지와 수정용 페이지를 나눠서 개발

수정 페이지로 이동하도록 컨트롤러에 매핑을 한다.

✨ 상품 이미지 수정을 위한 서비스 클래스 수정

ItemImgService
	public Long updateItem(ItemFormDto itemFormDto, List<MultipartFile> itemImgFileList) throws Exception {

        // 상품 수정
        Item item = itemRepository.findById(itemFormDto.getId()) // 멤버 폼으로 상품 불러옴
                .orElseThrow(EntityNotFoundException::new);
        item.updateItem(itemFormDto); // 상품을 상품 폼 데이터로 변경

        List<Long> itemImgIds = itemFormDto.getItemImgIds(); // 폼에서 이미지정보들을 받아옴

        // 이미지 등록
        for(int i=0;i<itemImgFileList.size()>;i++){
            itemImgService.updateItemImg(itemImgIds.get(i), itemImgFileList.get(i)); // 이미지를 업데이트하기 위해 (이미지 id, 이미지 파일 정보)를 파라미터로 전달
        }
        return item.getId();
    }
  • 이미지 세팅시, itemImgRepository.save()를 하지 않는다!
  • 그 이유는 savedItemImg는 현재 영속 상태이기 때문에 변경감지시 자동으로 DB에 반영되기 때문에 할 필요가 없다!

✨ 상품 업데이트하는 로직 구현

Item 엔티티에 상품 정보를 업데이트하는 로직을 만든다.
그 이유는 비즈니스 로직에 추가를 해야 좀 더 객체지향적으로 코딩 가능하고, 재활용도 가능하기 때문

Item
    public void updateItem(ItemFormDto itemFormDto){
        this.itemNm = itemFormDto.getItemNm();
        this.price = itemFormDto.getPrice();
        this.stockNumber = itemFormDto.getStockNumber();
        this.itemDetail = itemFormDto.getItemDetail();
        this.itemSellStatus = itemFormDto.getItemSellStatus();
    }
ItemService
public Long updateItem(ItemFormDto itemFormDto, List<MultipartFile> itemImgFileList) throws Exception {

        // 상품 수정
        Item item = itemRepository.findById(itemFormDto.getId()) // 멤버 폼으로 상품 불러옴
                .orElseThrow(EntityNotFoundException::new);
        item.updateItem(itemFormDto); // 상품을 상품 폼 데이터로 변경

        List<Long> itemImgIds = itemFormDto.getItemImgIds(); // 폼에서 이미지정보들을 받아옴

        // 이미지 등록
        for(int i=0;i<itemImgFileList.size()>;i++){
            itemImgService.updateItemImg(itemImgIds.get(i), itemImgFileList.get(i)); // 이미지를 업데이트하기 위해 (이미지 id, 이미지 파일 정보)를 파라미터로 전달
        }
        return item.getId();
    }

상품 수정하는 페이지를 컨트롤러에 매핑하자


0개의 댓글

관련 채용 정보