JPQL(4)

9mond·2023년 10월 16일
0
post-thumbnail

1. 아이템 수정

1-1. 준영속 상태에서의 아이템 수정

  • 변경 감지 기능 사용
  • 병합(merge) 사용 -> 사용 금지
// ItemRepository.java
@Repository
@RequiredArgsConstructor
public class ItemRepository {

	private final EntityManager em;
	
	// 저장
	// void, save()
	public void save(Item item) {
		// 신규등록
//		if( item.getId() == null ) {
			em.persist(item);
//		}else {
			// update
//			em.merge(item);	// merge 사용 금지
//		}
	}
//ItemController
@PostMapping("/items/{itemId}/edit")
public String updateItem(@ModelAttribute("form")Item form) {
	Item item = new Item();
	item.setId(form.getId());
	item.setName(form.getName());
	item.setPrice(form.getPrice());
	item.setStockQuantity(form.getStockQuantity());
		
	// pk값을 알려주는 것 : form.getId()
	itemService.updateItem(form.getId(), item);
		
	// merge 사용 금지
//	itemService.saveItem(item);
		
		
	return "redirect:/items";
}

1-2. merge의 문제점

  • 변경을 원치 않는 값의 경우 내가 set을 해주지 않으면 해당 값이 null로 update가 된다.
  • 필요한 값만 update를 원한다면 변경감지를 해주어야 한다.
  • 즉, merge는 실무에서 절대 쓰지 않는 사용법

1-3. 준영속과 비영속의 차이점

  • 영속상태가 되어본적이 있는 엔티티의 경험
// ItemService.java
// update는 이렇게 해주면 된다.
	@Transactional
	public Item updateItem(Long itemId, Item item) {
		// itemId를 기반으로 실제 영속상태를 확실시 해야한다.
		// 	-> 그 후 변경감지(Dirty Checking)를 통해서 해당 변경 내용을 set만 해주면 된다.
		Item findItem = itemRepository.findOne(itemId);	// 이렇게 select구문을 날려주면 확실하게 영속상태로 넘어간다.
		findItem.setPrice(item.getPrice());
		findItem.setName(item.getName());
		findItem.setStockQuantity(item.getStockQuantity());
		/*
		 * 영속성 컨텍스트 영역으로 들어온 뒤, 영속성 컨텍스트에 변화가 일어나게 되고,
		 * 변화가 일어나는 것을 JPA가 감지한다.
		 * flush는 변화가 생긴 부분을 감지 후, update 요청
		 *  => 이 일련의 과정이 변경감지에 의한 데이터를 update하는 방법이다.
		 */
		
		return findItem;
	}

💻 update 유지보수

@PostMapping("/items/{itemId}/edit")
public String updateItem(@PathVariable Long itemId,
		@ModelAttribute("form")Item form) {
	// 유지보수에 더 좋은 코드
	itemService.updateItem(itemId,
			form.getName(), form.getPrice(), form.getStockQuantity());
		
	return "redirect:/items";
}
	public void updateItem(Long itemId, String name, int price, int stockQuantity) {
		Item findItem = itemRepository.findOne(itemId);
		findItem.setName(name);
		findItem.setPrice(price);
		findItem.setStockQuantity(stockQuantity);
		// update되는 데이터가 많다면 서비스 계층에서 업데이트만을 위한 DTO를 따로 만들어도 된다.
	}

1-4. 엔티티 변경시에는 항상 변경감지를 이용

  • 컨트롤러 단에서 엔티티를 생성하기 보단, 트랜잭션이 있는 서비스 계층에 식별자(id)와 변경할 데이터(파라미터 or DTO)를 명확하게 전달.
  • 트랜잭션이 있는 서비스 계층에서 영속 상태의 엔티티를 조회하고, 엔티티의 데이터를 직접 변경
  • 트랜잭션 커밋시점에 변경감지가 실행된다.

💻 상품주문 페이지

// OrderController
@Controller
@RequiredArgsConstructor
public class OrderController {

	private final OrderService orderService;
	private final MemberService memberService;
	private final ItemService itemService;
	
	// url : order
	// createForm()
	// return order/orderForm
	@GetMapping("/order")
	public String createForm(Model model) {
		// members의 list
		List<Member> members = memberService.findMembers();
		model.addAttribute("members", members);
		
		// items의 list
		List<Item> items = itemService.findItems();
		model.addAttribute("items", items);
		
		
		return "order/orderForm";
	}

  @PostMapping("/orders")
  public String order(@RequestParam("memberId") Long memberId,
                      @RequestParam("itemId") Long itemId,
                      @RequestParam("count") int count) {

      orderService.order(memberId, itemId, count);


      // 추후 상세페이지로 수정
      return "redirect:/order";
  }
// OrderService.java
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OrderService {
	
	private final OrderRepository orderRepository;
	private final MemberRepository memberRepository;
	private final ItemRepository itemRepository;
	
	
	@Transactional
	public Long order(Long memberId, Long itemId, int count) {
		// jpa 영속성 컨텍스트 영역 들어오기
		Member member = memberRepository.findOne(memberId);
		Item item = itemRepository.findOne(itemId);
		
		// 주문상품
		OrderItem orderItem = 
				OrderItem.createOrderItem(item,item.getPrice(), count);
				
		// 주문생성
		Order order = Order.createOrder(member, orderItem);
		
		// 주문저장
		orderRepository.save(order);
		return order.getId();
		
	}
// OrderRepository.java
@Repository
@RequiredArgsConstructor
public class OrderRepository {
	
	private final EntityManager em;

	public void save(Order order) {
		em.persist(order);
	}

	// findAll(), List<>
	
}
// Order.java
@Entity
@Getter @Setter
@Table(name = "orders")
public class Order {

	@Id @GeneratedValue
	@Column(name = "order_id")
	private Long id;
	
	private LocalDateTime orderDate;
	
	// 주문상태(ORDER, CANCEL)
	@Enumerated(EnumType.STRING)
	private OrderStatus status;
	
	@OneToMany(mappedBy = "order")
	private List<OrderItem> orderItems = new ArrayList<>();
	
	@ManyToOne
	@JoinColumn(name = "member_id")
	private Member member;
	
	// 연관관계 메서드
	// setMember
	public void setMember(Member member) {
		this.member = member;
		member.getOrders().add(this);
	}
	
	// addOrderItem
	public void addOrderItem(OrderItem orderItem) {
		orderItems.add(orderItem);
		orderItem.setOrder(this);
	}

	// ====== 비즈니스 로직 ======
	// 주문생성에 관련된 로직
	public static Order createOrder(Member member, OrderItem... orderItems) {
		Order order = new Order();
		order.setMember(member);
		for( OrderItem orderItem : orderItems ) {
			order.addOrderItem(orderItem);
		}
		
		order.setStatus(OrderStatus.ORDER);
		order.setOrderDate(LocalDateTime.now());
		
		return order;
	}
}
// OrderItem.java
@Entity
@Getter @Setter
public class OrderItem {

	@Id @GeneratedValue
	@Column(name = "order_item_id")
	private Long id;
	
	private int orderPrice;
	private int count;

	@ManyToOne
	@JoinColumn(name = "order_id")
	private Order order;
	
	@ManyToOne
	@JoinColumn(name = "item_id")
	private Item item;
	
	// ======== 비즈니스 로직 =========
	public static OrderItem createOrderItem(Item item, int price, int count) {
		// 여기서 price는 할인, 쿠폰 등.. 가격에 변동이 생겼을 때 비즈니스 로직 작성 필요
		OrderItem orderItem = new OrderItem();
		orderItem.setItem(item);
		orderItem.setOrderPrice(price);;
		orderItem.setCount(count);
		
		// 주문한 만큼 재고 조정
		item.removeStock(count);
		
		return orderItem;
		
	}
}
<!-- orderForm.html-->
<!--
	주문회원과 상품선택에서 select박스 사용
-->
<div class="container">
	<div th:replace="fragments/bodyHeader :: bodyHeader" />

	<form role="form" action="/orders" method="post">
		<div class="form-group">
			<label for="member">주문회원</label> 
			<select name="memberId" id="member" class="form-control">
				<option value="">회원선택</option>
				<option th:each="member : ${members}"
						th:value="${member.id}"
						th:text="${member.name}"
					/>
			</select>
		</div>

		<div class="form-group">
			<label for="item">상품명</label> 
			<select name="itemId" id="item" class="form-control">
				<option value="">상품선택</option>
				<option th:each="item : ${items}"
						th:value="${item.id}"
						th:text="${item.name}"
					/>
			</select>
		</div>

		<div class="form-group">
			<label for="count">주문수량</label> 
			<input type="number" name="count" class="form-control"
				 id="count" placeholder="주문 수량을 입력하세요">
		</div>

		<button type="submit" class="btn btn-primary">Submit</button>
	</form>
	<br />
	<div th:replace="fragments/footer :: footer" />

</div>
profile
개발자

0개의 댓글

관련 채용 정보