상품등록을 위한 엔티티 설계 및 등록하기
들어가며
이번포스팅에서는 상품을 등록하는 기능을 개발해 보겠습니다. (도서 판매 페이지를 기준으로 개발)
엔티티 설계
사용자가 상품을 등록할때에는 여러 개의 상품을 등록할 수 있으며, 상품은 사용자의 한 명으로만 매핑되도록 설계했으며 아이템엔티티는 상속을 통해서 Book을 설계하도록 했습니다.
엔티티 작성
Member
@OneToMany(mappedBy = "member")
private List<Item> items = new ArrayList<>();
일대다 관계 매핑을 위해서 Member 에다가 위에 코드를 추가한다.
Item
@Entity
@Getter@Setter
public abstract class Item {
@Id@GeneratedValue
private Long id;
private String name;
private int price;
private int stockQuantity;
private String itemDetail;
@Enumerated(EnumType.STRING)
private ItemSellStatus itemSellStatus;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
public void saveItem(String name, int price, int stockQuantity, String itemDetail, ItemSellStatus itemSellStatus){
this.name = name;
this.price = price;
this.stockQuantity = stockQuantity;
this.itemDetail = itemDetail;
this.itemSellStatus = itemSellStatus;
}
}
- 추상 메소드로 Item을 설계하여 사용하지 못하도록 했다.
- private Member member : 아이템은 member와 다대일 관계로 매핑
- void saveItem(....) : 아이템을 저장하기 위한 메서드
ItemSellStauts는 enum타입으로 만들어 "SELL, SOLD_OUT"로 만든다.
Book
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Book extends Item{
private String author;
private String isbn;
@Builder
public Book(String name, int price, int stockQuantity, String itemDetail, ItemSellStatus itemSellStatus,String author, String isbn){
super.saveItem(name,price,stockQuantity,itemDetail,itemSellStatus);
this.author = author;
this.isbn = isbn;
}
}
- public Book(...) : 상품을 등록하기 위해서 super를 사용해 상품을 저장하기 위한 생성자(NoArgsConstructor을 통해서 자동으로 생성하게 해 준다.)
BookDto
@Getter@Setter
@NoArgsConstructor
@Data
public class BookDto {
private Long id;
@NotBlank(message = "이름을 필수로 입력하세요")
private String name; //아이템 이름
@NotNull(message = "가격을 입력하세요")
private int price; //아이템 가격
@NotNull(message = "수량을 입력하세요")
private int stockQuantity; //아이템 수량
@NotBlank(message = "작가명을 필수로 입력하세요")
private String author; //작가
@NotBlank(message = "ISBN을 입력하세요")
private String isbn; //isbn
@NotBlank(message = "상세설명을 필수로 입력하세요")
private String itemDetail; //상세목록
private ItemSellStatus itemSellStatus;
@Builder
public BookDto(String name, int price, int stockQuantity, String author, String isbn,
String itemDetail, ItemSellStatus itemSellStatus) {
this.name = name;
this.price = price;
this.stockQuantity = stockQuantity;
this.author = author;
this.isbn = isbn;
this.itemDetail = itemDetail;
this.itemSellStatus = itemSellStatus;
}
public Item toEntity(BookDto dto){
Book entity = Book.builder()
.name(dto.name)
.price(dto.price)
.stockQuantity(dto.stockQuantity)
.author(dto.author)
.isbn(dto.isbn)
.itemDetail(dto.itemDetail)
.itemSellStatus(dto.itemSellStatus)
.build();
return entity;
}
}
변경사항이있을 때 유연한 유지보수를 위해서 Dto를 사용
Repository
전 포스팅 까지는 직접 EntityManager를 통해서 메서드를 직접 구현했지만 이번에는 JpaRepository를 통해서 해보도록 하겠습니다.
ItemRepository
@Repository
public interface ItemRepository extends JpaRepository<Item, Long> {
}
JpaRepository를 확장하여 간편한 CRUD 등을 사용할 수 있게 해 준다.
Service
ItemService
@Service
@Transactional
@RequiredArgsConstructor
public class ItemService {
private final ItemRepository itemRepository;
public void save(Item item){
itemRepository.save(item);
}
public List<Item> findItems(){
return itemRepository.findAll();
}
}
- void save(Item item) : 아이템을 저장하기 위한 메서드
- List<Item> findItems() : 모든 아이템을 가져와 List에 저장해 값을 찾는 메서드
Controller
ItemController
@Controller
@RequiredArgsConstructor
public class ItemController {
private final ItemService itemService;
@GetMapping("/admin/items/new")
public String createForm(HttpServletRequest request, Model model){
HttpSession session = request.getSession(false);
Member loginMember = (Member) session.getAttribute(SessionConst.LOGIN_MEMBER);
model.addAttribute("bookDto",new BookDto());
model.addAttribute("member",loginMember);
return "item/createItem";
}
@PostMapping("/admin/items/new")
public String create(@Valid @ModelAttribute("bookDto") BookDto bookDto, BindingResult result,
HttpServletRequest request, Model model){
HttpSession session = request.getSession(false);
Member loginMember = (Member) session.getAttribute(SessionConst.LOGIN_MEMBER);
model.addAttribute("member",loginMember);
if(result.hasErrors()){
return "item/createItem";
}
Item entity = bookDto.toEntity(bookDto);
entity.setMember(loginMember);
itemService.save(entity);
return "redirect:/admin";
}
}
아이템을 등록할 때도 로그인을 한 상태에서 등록하기 때문에 세션을 생성하지 않고 만들어진 세션을 사용해 사용자값을 전달받아 어떤 사용자가등록을 하는지 알수있다. 그리고 저장을 할때에는 dto가 아닌 엔티티를저장해야하기 때문에 dto를 toEntity 메소드를 통해서 엔티티로 반환하고 세션에서 넘어온 loginMember 값을 변환한 entitiy에 loginMeber값을 넘겨주어서 상품을 등록할때 어떤 사용자가 등록하는지 알게 해 주었다.
HomeController부분에
List<Item> items = itemService.findItems();
model.addAttribute("items",items);
위에 코드를 defaultHome 부분과 loginHome 부분에 추가해주어야한다. 그래야 상품등록을 하면 findItems()메소드가 실행되어 저장되어있는 상품을 보여줄수있다.
HTML
createItem을 작성전에 상품을 등록하면 홈페이지 별로 상품을 등록한 상품이 보여야 하기때문에 home, loginHome, userHome.html에 <div layout:fragment="content">안에 코드를 추가해준다
<div layout:fragment="content">
<form role="form" th:object="${items}" enctype="multipart/form-data">
<section class="py-5">
<div class="container px-4 px-lg-5 mt-5">
<div class="row gx-4 gx-lg-5 row-cols-2 row-cols-md-3 row-cols-xl-4 justify-content-center">
<!-- 반복문을 사용하여 상품 목록 표시 -->
<div th:each="product : ${items}" class="col mb-5">
<div class="card h-100">
<!-- Product image -->
<!-- <img class="card-img-top" th:src="" alt="no image" />-->
<!-- Product details -->
<div class="card-body p-4">
<div class="text-center">
<!-- Product name -->
<h5 class="fw-bolder" th:text="${product.name}">Fancy Product</h5>
<!-- Product price -->
<div th:text="${#numbers.formatInteger(product.price, 0, 'COMMA')} + '원'"></div>
<div th:text="'판매자: '+${product.member.username}"></div>
<div th:text="${product.itemSellStatus}"style="color: #dc3545; font-weight: bold"></div>
</div>
</div>
<!-- Product actions -->
<div class="card-footer p-4 pt-0 border-top-0 bg-transparent">
<div class="text-center"><a class="btn btn-outline-dark mt-auto" th:href="@{/items/{itemId}/info(itemId=${product.id})}">View options</a></div>
</div>
</div>
</div>
</div>
</div>
</section>
</form>
</div>>
createItem
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{/fragements/layout/loginlay}">
<th:block layout:fragment="script">
</th:block>
<th:block layout:fragment="css">
<style>
.fieldError{
color: #dc3545;
}
</style>
</th:block>
<div layout:fragment="content" class="content w-75 py-5 m-auto">
<form role="form" action="/admin/items/new" th:object="${bookDto}" method="post" enctype="multipart/form-data">
<p class="h2">
상품 등록
</p>
<input type="hidden" th:field="*{id}">
<div class="form-group">
<label th:for="name">상품명</label>
<input type="text" th:field="*{name}"class="form-control">
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="fieldError">에러 메시지</p>
</div>
<div class="form-group">
<label th:for="price">가격</label>
<input type="number" th:field="*{price}"class="form-control">
<p th:if="${#fields.hasErrors('price')}" th:errors="*{price}" class="fieldError">에러 메시지</p>
</div>
<div class="form-group">
<label th:for="stockQuantity">수량</label>
<input type="number" th:field="*{stockQuantity}"class="form-control">
<p th:if="${#fields.hasErrors('stockQuantity')}" th:errors="*{stockQuantity}" class="fieldError">에러 메시지</p>
</div>
<div class="form-group">
<label th:for="author">저자</label>
<input type="text" th:field="*{author}"class="form-control">
<p th:if="${#fields.hasErrors('author')}" th:errors="*{author}" class="fieldError">에러 메시지</p>
</div>
<div class="form-group">
<label th:for="isbn">ISBN</label>
<input type="text" th:field="*{isbn}"class="form-control">
<p th:if="${#fields.hasErrors('isbn')}" th:errors="*{isbn}" class="fieldError">에러 메시지</p>
</div>
<div class="form-group">
<label th:for="itemDetail" class="form-label">상세설명</label>
<input type="text" class="form-control" th:field="*{itemDetail}" >
<p th:if="${#fields.hasErrors('itemDetail')}" th:errors="*{itemDetail}" class="fieldError">에러 메시지</p>
</div>
<div class="form-group">
<select th:field="*{itemSellStatus}" class="custom-select">
<option value="SELL">판매중</option>
<option value="SOLD_OUT">품절</option>
</select>
</div>
<p th:if="${#fields.hasErrors('itemDetail')}" th:errors="*{itemDetail}" class="fieldError">Incorrect data</p>
<div style="text-align: center">
<button th:formaction="@{/admin/items/new}" type="submit" class="btn btn-primary">저장</button>
</div>
</form>
</div>
</html>
- th:formaction : 속성은 "저장" 버튼을 누를때 /admin/items/new URL로 폼을 제출하기 위해서 사용합니다.
실행
실행 장면에서 상품을등록하고 로그아웃을 해도 등록한 상품이 올바르게 보여지는것을 볼수있다. DB에서 확인을 해보면
올바르게 저장되어있으며 매핑한 MEMBER와도 ID값이 올바르게 저장되어 있는것을 확인해볼수있다.
다음으로
다음에는 상품을 등록할때 이미지도 함께 등록하여 상품의 이미지도 함께 등록하도록 하겠습니다! 감사합니다!
'ToyProject쇼핑몰' 카테고리의 다른 글
[스프링 부트] JPA 쇼핑몰 # 10.아이디찾기(본인인증), 비밀번호 찾기 (1) | 2023.10.31 |
---|---|
[스프링 부트] JPA 쇼핑몰 # 9.세션을 이용한 로그인 및 로그아웃 (9) | 2023.10.28 |
[스프링 부트] JPA 쇼핑몰 # 8.로그인 레이아웃 (0) | 2023.10.27 |
[스프링 부트] JPA 쇼핑몰 # 7.회원가입 실행해보기 (0) | 2023.10.27 |
[스프링 부트] JPA 쇼핑몰 # 6.회원가입 주소 (0) | 2023.10.27 |