이 내용은 스프링 부트 쇼핑몰 프로젝트 with JPA 책을 학습한 내용입니다.
- Item (상품) Entity 와 다대일 단방향 관계를 갖는 ItemImg Entity 생성
| package kr.spring.item.entity; |
| |
| import jakarta.persistence.*; |
| import kr.spring.utils.entity.BaseEntity; |
| import lombok.*; |
| |
| @Entity |
| @Getter |
| @Setter |
| @ToString |
| @NoArgsConstructor |
| public class ItemImg extends BaseEntity { |
| |
| @Id |
| @GeneratedValue(strategy = GenerationType.IDENTITY) |
| @Column(name = "item_img_id") |
| 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; |
| } |
| } |
| |
| implementation 'org.modelmapper:modelmapper:3.1.1' |
- DTO 객체와 Entity 객체의 변환을 도와줌
- 서로 다른 클래스의 값을 필드의 이름과 자료형이 같으면 getter, setter를 통해 값을 복사해서 객체를 반환
- 상품을 등록 및 조회할 때 지정된 필드뿐 아니라 추가적인 데이터들의 이동이 많으므로 여러 DTO 이용
- 상품 이미지에 대한 DTO
- ItemImg 엔티티 객체를 ItemImgDto 객체로 변환
| package kr.spring.item.dto; |
| |
| import kr.spring.item.entity.ItemImg; |
| import lombok.Getter; |
| import lombok.Setter; |
| import lombok.ToString; |
| import org.modelmapper.ModelMapper; |
| |
| @Getter |
| @Setter |
| @ToString |
| 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); |
| } |
| } |
- 화면으로부터 입력 받은 상품 데이터 정보 DTO
- Item 엔티티 객체와 DTO 객체 간의 변환
| package kr.spring.item.dto; |
| |
| import jakarta.validation.constraints.NotBlank; |
| import jakarta.validation.constraints.NotNull; |
| import kr.spring.item.entity.Item; |
| import lombok.Getter; |
| import lombok.Setter; |
| import org.modelmapper.ModelMapper; |
| |
| import java.time.LocalDateTime; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| @Getter |
| @Setter |
| public class ItemFormDto { |
| |
| private Long id; |
| |
| @NotBlank(message = "상품명은 필수 항목입니다.") |
| private String itemNm; |
| |
| @NotNull(message = "가격은 필수 항목입니다.") |
| private int price; |
| |
| @NotNull(message = "재고는 필수 항목입니다.") |
| private int stockNumber; |
| |
| private String itemSellStatus; |
| |
| @NotBlank(message = "상품 설명은 필수 항목입니다.") |
| private String itemDetail; |
| |
| private List<ItemImgDto> itemImgDtoList = new ArrayList<>(); |
| |
| private List<Long> itemImgList = 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); |
| } |
| } |
- ItemFormDto 객체를 model 객체에 담아서 뷰로 전달
| @Controller |
| @RequiredArgsConstructor |
| public class ItemController { |
| |
| |
| @GetMapping("/admin/item/new") |
| public String itemForm(Model model) { |
| model.addAttribute("itemFormDto", new ItemFormDto()); |
| |
| return "item/itemForm"; |
| } |
- 상품 판매 상태를 “판매중” 또는 “품절”로 선택할 수 있게 <select> 태그 이용
| |
| <div class="form-group"> |
| <select th:field="*{itemSellStatus}" class="custom-select"> |
| <option value="SELL">판매중</option> |
| <option value="SOLD_OUT">품절</option> |
| </select> |
| </div> |
- 상품을 처음 등록할 경우 Controller를 통해 전달 받은 itemFormDtoList가 존재하지 않음
- Thymeleaf builtin 메소드 #numbers.sequence() 를 통해서 1 부터 5 까지 상품이미지명 출력
| |
| <div class="form-group" th:each="num: ${#numbers.sequence(1,5)}"> |
| <div class="custom-file img-div"> |
| |
| <input type="file" class="custom-file-input" name="itemImgFile"> |
| |
| <label class="custom-file-label" th:text="상품이미지 + ${num}"></label> |
| </div> |
| </div> |
- 상품을 수정할 경우는 이미 존재하던 상품 이미지가 있으므로 itemFormDtoList가 존재함
- 이미지가 존재하면 해당 이미지의 이름을 출력하고 빈칸이면 상품이미지+index를 출력
| |
| <div th:if="${not #lists.isEmpty(itemFormDto.itemImgDtoList)}"> |
| <div class="form-group" |
| th:each="itemImgDto, status: ${itemFormDto.itemImgDtoList}"> |
| <div class="custom-file img-div"> |
| |
| <input type="file" class="custom-file-input" name="itemImgFile"> |
| |
| <input type="hidden" name="itemImgIds" th:value="${itemImgDto.id}"> |
| <label class="custom-file-label" |
| th:text="${not #strings.isEmpty(itemImgDto.oriImgName)} ? ${itemImgDto.oriImgName} : '상품이미지' + ${status.index+1}"></label> |
| </div> |
| </div> |
| </div> |
- 상품을 처음 등록할 때는 “저장”버튼을 출력
- 상품을 수정할 때는 “수정”버튼을 출력
| !--상품아이디가 없는 경우 저장 로직을 호출하는 버튼 보여줌. 원래 form 태그에서 지정해줘야하지만 아이디를 체크해서 없는 경우 fromaction을 이용하여 new로 준다--> |
| <div th:if="${#strings.isEmpty(itemFormDto.id)}" |
| style="text-align: center"> |
| <button th:formaction="@{'/admin/item/new'}" type="submit" |
| class="btn btn-primary">저장</button> |
| </div> |
| |
| <div th:unless="${#strings.isEmpty(itemFormDto.id)}" |
| style="text-align: center"> |
| |
| <button th:formaction="@{'/admin/item/' + ${itemFormDto.id} }" |
| type="submit" class="btn btn-primary">수정</button> |
| </div> |