[Spring_Boot] JPA SHOP - Item & 변경감지와 머지(merge)

최현석·2022년 12월 13일
0

Spring_Boot

목록 보기
28/31

🧩 아이템 수정 - 변경감지와 머지(merge)

준영속(detached)

  • 영속성 컨텍스트에 저장되었다가 분리된 상태로 현재는 영속상태가 아닌 상태이다.
  • 영속성 컨텍스트가 제공하는 기능을 사용하지 못한다.

준영속 엔티티를 수정하는 2가지 방법

  • 변경 감지 기능 사용 ( 권장 )
  • 병합(merge)사용 (권장 X)

* 준영속과 비영속의 차이점

  • 준영속과 비영속의 차이는 바로 영속상태가 되어본 경험이다.
  • 영속상태가 되기 위해서는 식별자 반드시 필요하다.
  • 준영속 상태의 엔티티는 식별자가 존재한다.
  • 비영속 상태의 엔티티는 식별자가 존재할수도, 존재하지 않을 수도 있다.
@PostMapping("/items/{itemId}/edit")
	public String updateItem(@PathVariable Long itemId, @ModelAttribute("form") Item form) {
		/*
		 * item -> 객체는 준영속 상태의 item
		 * 		   영속성 컨텍스트의 지원을 받을 수 없고, 데이터를 수정해도 변경 감지 기능은 동작 X 
		 */
		Item item = new Item();
		item.setId(item.getId());
		item.setName(item.getName());
		item.setPrice(item.getPrice());
		item.setStockQuantity(item.getStockQuantity());
        
//		itemService.saveItem(item); // merge

		// 변경감지		
		itemService.updateItem(itemId,form.getName(),form.getPrice(),form.getStockQuantity());
			return "redirect:/items";
	}
  • 새로 생성 된 item객체는 new를 통해 새로 생성된 객체이고, 아직 영속성 컨텍스트에는 등록되지 않았지만, 식별자를 가지고 있다.
  • 이처럼 임의로 엔티티 객체를 만들어 내도 식별자를 가지고 있으면 준영속 엔티티라 한다.

변경감지(영속 상태 만들기)

  • 영속성 컨텍스트에 속해있으려면 해당 데이터를 select해준다.
  • select문을 통해서 영속성 컨텍스트에서 가져올건데, 영속성 컨텍스트에 데이터가 없으면 DB에서 가져온다.
  • select 요청을 함으로써, 영속성 컨텍스트영역으로 요청을 보내서 그 안에 내 정보가 담기게된다.
  • findItem 이라는 객체는 영속 상태에 놓이게 된다.
    Item findItem = itemRepository.findOne(itemid);
	@Transactional
	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);
	}
  • 준영속 상태인 객체를 영속상태로 바꾸고 그 영속상태인 데이터를 가지고 set만을 해주면 변경감지를 통해 update가 수행된다.
  • Transaction이 종료되는순간 자동으로 변경된 부분을 감지해서 update를 수행한다.
    이처럼 변경점을 감지해서 update하는 것을 변경 감지라 한다.

🧩 Domain

@Entity
@Getter @Setter
public class Item {

	@Id @GeneratedValue
	@Column(name = "item_id")
	private Long id;
	
	private String name;
	private int price;
	private int stockQuantity;
	
	// 비지니스 로직
	// stock 재고 감소
	public void removeStock(int quantity) {
		int restStock = this.stockQuantity - quantity;
		// 부족하면
		if(restStock < 0) {
			// exception
			throw new NotEnoughStockException("need more stock");
		}
		this.stockQuantity = restStock;
	}
	
	// stock 증가
	public void addStock(int quantity) {
		this.stockQuantity += quantity;
	}
}

🧩 DTO

ItemForm

@Getter @Setter
public class ItemForm {
	
	private Long id;
	private String name;
	private int price;
	private int stockQuantity;
	
	
}

🧩 Web

createItemForm

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<body>

	<div class="container">
		<div th:replace="fragments/bodyHeader :: bodyHeader" />

		<form th:action="@{/items/new}"  th:object="${itemForm}" method="post">
			<div class="form-group">
				<label th:for="name">상품명</label> 
				<input type="text" th:field="*{name}" class="form-control" placeholder="이름을 입력하세요">
			</div>
			<div class="form-group">
				<label th:for="price">가격</label> 
				<input type="number" th:field="*{price}" class="form-control" placeholder="가격을 입력하세요">
			</div>
			<div class="form-group">
				<label th:for="stockQuantity">수량</label> 
				<input type="number" th:field="*{stockQuantity}" class="form-control" placeholder="수량을 입력하세요">
			</div>
			<button type="submit" class="btn btn-primary">Submit</button>
		</form>
		<br />
		<div th:replace="fragments/footer :: footer" />

	</div>
	<!-- /container -->

</body>
</html>

itemList

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<body>
	<div class="container">
		<div th:replace="fragments/bodyHeader :: bodyHeader" />

		<div>
			<table class="table table-striped">
				<thead>
					<tr>
						<th>#</th>
						<th>상품명</th>
						<th>가격</th>
						<th>재고수량</th>
						<th></th>
					</tr>
				</thead>
				<tbody>
					<tr th:each="item : ${items}">
						<td th:text="${item.id}"></td>
						<td th:text="${item.name}"></td>
						<td th:text="${item.price}"></td>
						<td th:text="${item.stockQuantity}"></td>
						<td>
							<a th:href="@{/items/{id}/edit(id=${item.id})}"
								class="btn btn-primary" role="button">수정</a> 
						</td>
					</tr>
				</tbody>
			</table>
		</div>
		
		<div th:replace="fragments/footer :: footer" />
	</div>
	<!-- /container -->
</body>
</html>

updateItemForm

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<body>

	<div class="container">
		<div th:replace="fragments/bodyHeader :: bodyHeader" />

		<form th:object="${form}" method="post">
			<input type="hidden" th:field="*{id}">
			<div class="form-group">
				<label th:for="name">상품명</label> 
				<input type="text" th:field="*{name}" class="form-control" placeholder="이름을 입력하세요" />
			</div>
			<div class="form-group">
				<label th:for="price">가격</label> 
				<input type="number" th:field="*{price}" class="form-control" placeholder="가격을 입력하세요" />
			</div>
			<div class="form-group">
				<label th:for="stockQuantity">수량</label> 
				<input type="number" th:field="*{stockQuantity}" class="form-control" placeholder="수량을 입력하세요" />
			</div>
			<button type="submit" class="btn btn-primary">Submit</button>
		</form>

		<div th:replace="fragments/footer :: footer" />

	</div>
	<!-- /container -->

</body>
</html>

🧩 Controller

createItemForm

@Controller
@RequiredArgsConstructor
public class ItemController {
	
	private final ItemService itemService;
	
	// request : items/new
	// response : items/createItemForm
	@GetMapping(value = "items/new")
	public String createForm(Model model) {
		model.addAttribute("itemForm", new ItemForm());
		return "items/createItemForm";
	}

	// 저장로직
	// request : /items/new
	// response : /
	@PostMapping("/items/new")
	public String create(ItemForm itemform) {
		Item item = new Item();
		item.setName(itemform.getName());
		item.setPrice(itemform.getPrice());
		item.setStockQuantity(itemform.getStockQuantity());
		
		itemService.saveItem(item);
		
		return "redirect:/";
	}
	
	// request : items
	// response : items/itemList
	@GetMapping("/items")
	public String list(Model model) {
		List<Item> items = itemService.findItems();
		model.addAttribute("items", items);
		return "items/itemList";
	}
	
	// request : items/2/edit
	// 1건 조회
	// response : items/updateItemForm 
	@GetMapping("/items/{itemId}/edit")
    public String updateItemForm(@PathVariable("itemId")Long itemId, Model model){
        Item item = itemService.findOne(itemId);
        ItemForm form = new ItemForm();

        form.setId(item.getId());
        form.setName(item.getName());
        form.setPrice(item.getPrice());
        form.setStockQuantity(item.getStockQuantity());

        
        model.addAttribute("form", form);
        return "items/updateItemForm";

    }
	
	@PostMapping("/items/{itemId}/edit")
	public String updateItem(@PathVariable Long itemId, @ModelAttribute("form") Item form) {
		/*
		 * item -> 객체는 준영속 상태의 item
		 * 		   영속성 컨텍스트의 지원을 받을 수 없고, 데이터를 수정해도 변경 감지 기능은 동작 X 
		 */
		Item item = new Item();
		item.setId(item.getId());
		item.setName(item.getName());
		item.setPrice(item.getPrice());
		item.setStockQuantity(item.getStockQuantity());
        
//		itemService.saveItem(item); // merge
		
		itemService.updateItem(itemId,form.getName(),form.getPrice(),form.getStockQuantity());
		return "redirect:/items";
	}
}

🧩 Service

ItemService

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ItemService {
	
	private final ItemRepository itemRepository;

	@Transactional
	public void saveItem(Item item) {
		itemRepository.save(item); 
	}

	public List<Item> findItems() {
		return itemRepository.findAll();
	}

	public Item findOne(Long itemId) {
		return itemRepository.findOne(itemId);
	}
	
	@Transactional
	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);
	}
}

🧩 Repository

ItemRepository

@Repository
@RequiredArgsConstructor
public class ItemRepository {
	
	@Autowired
	private final EntityManager em;
	
	public void save(Item item) {
		// 처움에 item의 id가 없으면 신규등록
//		if(item.getId() == null) {
			// 신규 
//			em.persist(item);
//		}else {
			// jpa를 통해서 db에 한번 들어간 값
//			em.merge(item);
//		}
		
		em.persist(item);
	}

	public List<Item> findAll() {
		return em.createQuery("select i from Item i", Item.class).getResultList();
	}

	public Item findOne(Long id) {
		return em.find(Item.class, id);
	}
	
}

0개의 댓글