주문을 하는 기능과 주문 목록을 저장하는 기능을 만들어보자
주문 id, 주문한 상품의 이름, 가격, 갯수와 주문한 사람의 정보를 저장했다
정보는 @ManyToOne, @JoinColumn 해서 가져왔다
@Entity
@Getter
@Setter
@ToString
public class Sales {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column
private String itemName;
@Column
private Integer price;
@Column
private Integer count;
@ManyToOne
@JoinColumn(name = "member_id",
foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)
)
private Member member;
@CreationTimestamp
private LocalDateTime created;
}
주문 컨트롤러를 만들고 주문 기능과 주문 목록 기능을 테스트해 봤다
@PreAuthorize("isAuthenticated()")
@PostMapping("/order")
String postSales(@RequestParam Integer count, @RequestParam Integer price,
@RequestParam String itemName, Model model , Authentication auth){
CustomUser user = (CustomUser) auth.getPrincipal(); //로그인 된 사용자 정보 가져오기!
Sales sales = new Sales();
sales.setItemName(itemName);
sales.setPrice(price);
Member member = new Member();
member.setId(user.userId);
sales.setMember(member);
sales.setCount(count);
salesRepository.save(sales);
/* MemberDto memberInfo = memberService.getMemberInfo(member.get().getId());
model.addAttribute("memberInfo", memberInfo);*/
return "redirect:/salesList";
}
@GetMapping("/salesList")
String salesList(Model model){
List<Sales> salesList = salesRepository.findAll();
System.out.println(salesList);
return "salesList.html";
}
• N+1 문제
주문은 잘 저장됬는데 문제가 있다
전체 조회 쿼리, 멤버 데이터 조회 쿼리(멤버 숫자만큼 발생) select를 하기에 상당히 비효율적이다
이게 N+1 문제라고 한다
해결하기위해 JPQL을 사용한 쿼리문을 작성한다
native sql로 조인을 사용해서 select * from Sales s , Member m where s.member_id = m.id; 이런식으로 작성해도
jpa가 번역하는 과정에서 N+1 문제가 발생하게 된다..
그래서 JPQL을 사용해서 리포지토리에 아래처럼 코드를 작성하면 n+1문제를 해결할 수 있다!
@Repository
public interface SalesRepository extends JpaRepository<Sales,Long> {
@Query(value = "SELECT s FROM Sales s JOIN FETCH s.member")
List<Sales> customFindAll();
}
• dto 사용
그리고 이대로 member를 보냈다간 restapi로 보내면 유저 비밀 번호까지 보내버리는 대참사가 일어날 수 있다
dto를 사용해서 보내고 싶은 데이터만 걸러서 보내자
보내고 싶은 정보인 구매한 아이템 이름, 가격, 구매자 id, 구매시간, 구매수량, 총 가격을 보낼 dto를 만들고
@Getter
@Setter
@ToString
@NoArgsConstructor
public class SalesDto {
private String itemName;
private Integer price;
private String userName;
private LocalDateTime created;
private Integer count;
private Integer sum;
}
컨트롤러에서는 sales를 그대로 보내지 않게 가져온걸 salesDto에 담아서 보내준다
@GetMapping("/salesList")
String salesList(Model model) {
List<Sales> salesList = salesRepository.customFindAll();
List<SalesDto> salesDtoList = new ArrayList<>();
for (Sales sales : salesList) {
SalesDto salesDto = new SalesDto();
salesDto.setItemName(sales.getItemName());
salesDto.setPrice(sales.getPrice());
salesDto.setCreated(sales.getCreated());
salesDto.setCount(sales.getCount());
salesDto.setSum(sales.getCount() * salesDto.getPrice());
salesDto.setUserName(sales.getMember().getUserName());
salesDtoList.add(salesDto);
}
model.addAttribute("salesList", salesDtoList);
return "salesList";
}
salesList.html에는 기존에 list에 한것처럼 th:each로 각각 dto의 데이터를 뽑아서 보여준다
<div class="card" th:each = "i : ${salesList}">
<div>
<h4 th:text="'구매자 이름 : ' + ${i.userName}"></h4>
<h4 th:text="'주문 상품 이름 : ' + ${i.itemName}"></h4>
<h4 th:text="'주문 상품 가격 : ' + ${i.price}"></h4>
<h4 th:text="'주문 수량 : ' + ${i.count}"></h4>
<h4 th:text="'총 가격 : ' + ${i.sum}"></h4>
<h4 th:text="'주문 일시 : ' +${i.created}"></h4>
</div>
</div>
원했던 대로 잘 나온다
• @OneToMany
지금은 sales에 있는 member_id로 해당하는 유저 member를 가져온다
특정 유저가 한 주문을 보려면?
Member 엔티티에 아래와 같은 컬럼을 추가한다, mappedBy는 Sales에 @ManyToOne을 한 컬럼을 넣어준다
@OneToMany(mappedBy = "member")
List<Sales> salesList = new ArrayList<>();
• @OneToMany, @ToString stackOverFlow 에러
아래 코드로 이대로 조회해보려고 하니 stackOverFlow 에러가 떳다
이유는 @ToString과 @OneToMany를 같이 사용할 시 상호참조로 무한루프가 뜰 수 있다고 한다
@OneToMany 부분 위에 ToString을 빼는 어노테이션을 달면 해결 가능하다
@ToString.Exclude
@OneToMany(mappedBy = "member")
List<Sales> salesList = new ArrayList<>();
• @OneToMany 조회
특정 멤버 가져온 것 에서 위의 salesList를 get 하면 해당 멤버 id의 주문 내역이 쭉 나온다
@GetMapping("/order/all")
String getMember(){
var member = memberRepository.findById(3L);
System.out.println(member.get().getSalesList());
return "index.html";
}
• @ManyToOne @OneToMany 뭘써야되나
지금 db 구조에서는 member 하나가 여러개의 sales를 가질 수 있음
- member 에 oneToMany로 sales에서 가져다 쓰는 컬럼 이름을 쓰면 됨
근데 뭐 사실 특정 멤버 주문 내역을 보고 싶으면
salesRepository에 findbyMemberId 해서 가져오면 되기도 해서 아직은 언제 써야 되는지 잘 모르겟지만 하다보면 알게되고 지금 이렇게 갈기는 것도 나중에 내가 블로그 리팩토링 하면서 지우겟죠? 그러길 바랍니다
• 요약
2 정규화로 sales에 member 정보 다 있는 걸 meberId만 들고 있게 했고
manyToOne으로 해당 컬럼(memberId)가 가르키는 컬럼 출력 , manyToOne 성능 문제는 JPQL join fetch로 해결
@OneTomany로 반대로도 가능
꼭 manyToOne 할때 상대방에 OneToMany 할 필요는 없지만 붙이면 테이블 구조 파악할때 도움이 된다고 하고 관련된 행을 삭제할때도 도움이 된다고 합니다
'스프링 쇼핑몰 만들어보기' 카테고리의 다른 글
검색 기능 - index (0) | 2024.05.08 |
---|---|
댓글 기능 만들기 (0) | 2024.05.06 |
S3 버킷 스프링 어플리케이션에서 사용하기 (0) | 2024.05.05 |
aws S3 이미지 업로드 (0) | 2024.05.05 |
페이지 나누기 (pagination) (0) | 2024.05.05 |