상품 수정
@GetMapping("/items/{itemId}/edit")
public String updateItemForm(@PathVariable("itemId") Long itemId, Model model) {
Book item = (Book) itemService.findOne(itemId);
BookForm form = new BookForm();
form.setId(item.getId());
form.setName(item.getName());
form.setPrice(item.getPrice());
form.setStockQuantity(item.getStockQuantity());
form.setAuthor(item.getAuthor());
form.setIsbn(item.getIsbn());
model.addAttribute("form", form);
return "items/updateItemForm";
}
@PostMapping("/items/{itemId}/edit")
public String updateItem(@ModelAttribute("form") BookForm bookForm) {
Book book = new Book();
book.setId(bookForm.getId());
book.setName(bookForm.getName());
book.setPrice(bookForm.getPrice());
book.setStockQuantity(bookForm.getStockQuantity());
book.setAuthor(bookForm.getAuthor());
book.setIsbn(bookForm.getIsbn());
itemService.saveItem(book);
return "redirect:/items";
}
}
itemService에서 saveItem을 호출하면 Item Entityu인 book이 넘어간다.
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class ItemService {
private final ItemRepository itemRepository;
@Transactional
public Long saveItem(Item item) {
Long savedId = itemRepository.save(item);
return savedId;
}
public List<Item> findItems() {
return itemRepository.findAll();
}
public Item findOne(Long itemId) {
return itemRepository.findOne(itemId);
}
}
itemService에서 saveItem을 호출하면 트랜잭션이 걸린 상태에서 itemRepository의 save를 호출한다.
@Repository
@RequiredArgsConstructor
public class ItemRepository {
private final EntityManager em;
public Long save(Item item) {
if(item.getId() == null) { //처음엔 id가 없는 것은 새로 생성한 객체. 그래서 persist로 저장
em.persist(item);
} else {
em.merge(item); //update와 비슷함
}
return item.getId();
}
public Item findOne(Long id) {
return em.find(Item.class, id);
}
public List<Item> findAll() {
return em.createQuery("select i from Item i", Item.class)
.getResultList();
}
}
itemRepository에서 save를 호출하면 Id가 없을 경우에는 새로운 item이니까 persist()하고 있을 경우에는 merge()를 호출한다 .
items/{itemId}/edit에서 itemId를 url에서 수정하면 다른 사람의 것으로 갈 수 있어서, 서비스의 뒷단에서 유저의 권한을 체크하는 로직이 필요하다.
변경 감지와 병합
- 준영속 엔티티
- JPA 영속성 컨텍스트가 더는 관리하지 않는 엔티티. updateItem()에서 book은 Id가 있다. 이것은 데이터베이스에 한번 들어갔다가 나온 것이라고 볼 수 있다. 이것을 준영속성 상태의 객체라 한다. 임의로 만들어낸 엔티티도 기존 식별자를 가지고 있으면 준영속 엔티티로 볼 수 있다.
- JPA가 관리하는 영속 엔티티는 변경 감지가 일어나는데 준영속 엔티티는 변경 감지가 일어나지 않는다.
- 준영속 엔티티를 수정하는 2가지 방법
- 변경 감지 기능 사용
- 병합(merge) 사용
변경 감지 기능 사용
//ItemService.java
@Transactional
public void updateItem(Long itemId, Book bookParam) {
Item findItem = itemRepository.findOne(itemId);
findItem.setPrice(bookParam.getPrice());
findItem.setName(bookParam.getName());
findItem.setStockQuantity(bookParam.getStockQuantity());
}
트랜잭션 안에서 엔티티를 다시 조회하고, 변경할 값을 선택하면, itemRepository.save()를 호출할 필요없이 JPA가 변경 감지를 하여 알아서 수정한다.
병합 사용
- 병합은 준영속 상태의 엔티티를 영속 상태로 변경할 때 사용하는 기능이다.
- 병합 동작 방식
- merge()를 실행한다
- 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회한다.
- 만약 1차 캐시에 엔티티가 없으면 데이터 베이스에서 엔티티를 조회하고, 1차 캐시에 저장한다.
- 조회한 영속 엔티티에 파라미터로 넘어온 준영속 엔티티의 값을 채워 넣는다.
- 영속 상태인 영속 엔티티를 반환한다.
요약하면, 준영속 엔티티의 식별자 값으로 영속 엔티티를 조회하고, 영속 엔티티의 값을 준영속 엔티티의 값으로 모두 교체한다. 그리고 트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 데이터베이스에 UPDATE SQL이 실행된다.
주의점: 변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만, 병합을 사용하면 모든 속성이 변경된다. 병합시 값이 없으면 null로 업데이트 될 수도 있다.
(변경 감지 : dirty checking)
또, 위처럼 set을 사용하지말고, 의미있는 메서드를 만들어서 변경해야한다. 메서드를 만들어서 어디서 값이 변경되는지 역추적할 수 있다.
merge 쓰지말고, 변경감지 쓰기.
어설프게 컨트롤러에서 엔티틸 생성하지 말아라
//ItemService.java
...
@Transactional
public void updateItem(Long itemId, String name, int price, int stockQuantity) {
Item findItem = itemRepository.findOne(itemId);
findItem.setPrice(price);
findItem.setName(name);
findItem.setStockQuantity(stockQuantity);
}
...
//ItemController
...
@PostMapping("/items/{itemId}/edit")
public String updateItem(@PathVariable Long itemId, @ModelAttribute("form") BookForm bookForm) {
itemService.updateItem(itemId, bookForm.getName(), bookForm.getPrice(), bookForm.getStockQuantity());
return "redirect:/items";
}
...
'Spring' 카테고리의 다른 글
[JPA 기본편] JPA 기초 (1) | 2024.06.14 |
---|---|
[JPA 기본편] SQL 중심적인 개발의 문제 + JPA 소개 (0) | 2024.06.14 |
[Springboot & JPA 1] 주문 도메인 개발 (0) | 2024.05.04 |
[Springboot & JPA 1] 상품 도메인 개발 (0) | 2024.05.03 |
[Spring boot & JPA 1] 회원 도메인 개발 (0) | 2024.05.01 |