📝 Outline
- 이번에는 상품 상세 페이지를 구현하였다.
- Vue를 제대로 학습하고 사용한 것은 아니지만 확실히 익숙해지니까 작업 속도도 빨라지는 듯 하다.
- 구현 스타일은 다음과 같다.
- 페이지를 간단히 설명하자면, 기본적으로 상품의 이름과 이미지, 상품 가격, 상품 설명을 확인할 수 있다.
- 또한 주문을 위한 Select Box 및 Text Field가 존재하며, 아래의 버튼을 통해 해당 상품을 장바구니에 담거나 찜할 수 있도록 구현하였다. (물론 실제 기능은 아직 미구현 상태)
- 참고로 Select Box의 경우 상품 사이즈 재고에 따라 선택할 수 있는 품목을 제한하도록 하였으며, 그 결과는 아래와 같다.
- 즉, 현재 DB에 해당 상품의 L 사이즈 재고는 '0'으로 저장되어 있음을 의미한다.
📝 Review
- 해당 페이지를 구현하는데에는 꽤 생각할 것이 많았다.
- 새로운 엔티티를 생성하고 맵핑해주는 작업이 필요했으며, Vue Router를 통해 페이지간 데이터를 전달하기 위한 작업도 해주어야 했다. 또한 이러한 과정에서 발생한 오류도 해결해주어야 했다.
- 여튼 글이 좀 길어질 수도 있을 것 같다.
💬 @OneToMany, @ManyToOne
- 이번에 새로운 엔티티를 추가하면서 Product 엔티티가 좀 복잡해졌다.
- 추가된 친구들은 다음과 같은데
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Product {
...
@OneToMany(mappedBy = "product")
private List<Size> sizes = new ArrayList<>();
@OneToMany(mappedBy = "product")
private List<Image> images = new ArrayList<>();
@OneToMany(mappedBy = "product")
private List<Feature> features = new ArrayList<>();
...
}
- 각각 상품 사이즈, 상품 이미지, 상품 특징을 저장하기 위한 엔티티이다.
- 이때 @OneToMany를 사용하면서 한 가지 문제가 생겼다.
- 바로, OneToMany 단방향 맵핑이 좋지 않은 맵핑 방법이라는 것이다. 이는 구글링만 조금 해봐도 알 수 있다.
- OneToMany 단방향 맵핑의 문제점은 꽤 심플했다. 바로 연관관계 관리를 위해, 객체 저장/삭제 시에 UPDATE 쿼리가 추가로 발생하기 때문이었다. (자세한 내용은 여기에서)
- 이를 해결하기 위해 단방향 맵핑을 양방향 맵핑(@OneToMany + @ManyToOne)으로 수정해주었다.
- 아래 코드는 그 예시 중 하나이다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Size {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String size;
private int stock;
@ManyToOne
@JoinColumn(name = "product_id")
private Product product;
}
- 해당 이슈를 정리하자면 다음과 같다.
- OneToMany 단방향 매핑은 FK 위치 문제 때문에, UPDATE 쿼리가 또 한번 나가게 된다.
- 성능차이는 미비하나 쿼리를 보았을때 개발자가 헷갈릴 여부가 있고, soft delete 시 히스토리 관리가 잘 안되기 때문에, 단방향 관계의 경우에도 매핑자체는 양방향 매핑관계를 사용한뒤 Many 쪽에 로직을 두지 않는식으로 개발을 진행하자.
💬 Cannot call sendError() after the response has been committed
- 신나게 엔티티를 추가하고 맵핑하고 나서 프로젝트를 실행했더니 뜬금없이 오류가 발생하였다.
Cannot call sendError() after the response has been committed
- 찾아보니 해당 오류는 JSON 타입으로 변환하는 과정에 오류가 발생한 것이라고 하며, 이는 엔티티간 연관관계와 관련이 있다고 한다.
- 생각해보니, 이전에 프로젝트를 진행할 때에도 비슷한 상황이 발생했다는 것을 깨달았고, 무엇이 문제인지 단박에 알 수 있었다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Product {
...
@OneToMany(mappedBy = "product")
private List<Size> sizes = new ArrayList<>();
@OneToMany(mappedBy = "product")
private List<Image> images = new ArrayList<>();
@OneToMany(mappedBy = "product")
private List<Feature> features = new ArrayList<>();
...
}
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Size {
...
@ManyToOne
@JoinColumn(name = "product_id")
private Product product;
}
- 코드를 보면 알겠지만 각 엔티티끼리 서로를 참조하고 있다.
- 그래... 틀림없이 무한 참조 때문에 발생한 오류일 거야... 아니나 다를까 단박에 해결할 수 있었다.
- 해결방법으로는 여러가지가 있겠지만 필자의 경우 엔티티를 직접 넘겨주는 방식에서 DTO를 넘겨주는 방식으로 수정함으로써 해결하였다.
@GetMapping("/test5/{product_id}")
public ProductDto test5(@PathVariable Long product_id) throws Exception {
Product product = productRepository.findById(product_id).orElseThrow(() -> {
throw new IllegalStateException("상품 정보가 없습니다.");
});
ProductDto productDto = modelMapper.map(product, ProductDto.class);
return productDto;
}
💬 Vue Router 데이터 전달하기
- 상품 상세 페이지를 구현하면서, 기존에 구현했던 상품 리스트에서 상품을 클릭할 시 해당 상품에 해당하는 상세 페이지로 이동하도록 구현해주어야 했다.
- 이를 위해서는 Router로 페이지를 이동하는 시점에 해당 상품을 식별할 수 있는 고유값을 함께 넘겨주어야 했으며, 그 방법은 다음과 같다.
const router = new VueRouter({
mode: "history",
routes: [
...
{path: "/detail", component: Detail, name: "detail"},
],
});
- 우선 위와 같이 router.js에 각 경로에 해당하는 name 속성을 설정해준다.
showProductDetail(id) {
this.$router.push({
name: "detail",
query: { name: "productId", productId: id },
});
}
- 이후 데이터를 전달할 페이지에서는 위와 같이 코드를 작성해준다.
- Query를 사용하는 방식과 Params를 사용하는 방식이 있었는데, 별 차이는 없는 것 같다.
this.productId = this.$route.query.productId;
- 이후 데이터를 받는 쪽에서는 위와 데이터를 전달 받을 수 있다.
- 마지막으로 결과를 살펴보면?
📌 Reference
'🚗 Backend Toy Project > Baeg-won Clothing Gallery' 카테고리의 다른 글
[Baeg-won Clothing Gallery] 8. WISH 페이지 구현 (0) | 2023.07.28 |
---|---|
[Baeg-won Clothing Gallery] 7. CART 페이지 구현 (0) | 2023.07.24 |
[Baeg-won Clothing Gallery] 5. SALE, CONTACT 페이지 구현 (0) | 2023.07.15 |
[Baeg-won Clothing Gallery] 4. CLOTHING, FOOTWEAR, ACCESSORIES 페이지 구현 (0) | 2023.07.14 |
[Baeg-won Clothing Gallery] 3. BRANDS 페이지 구현 (0) | 2023.07.08 |