/**
* 상품 수정
*/
@PostMapping(value = "/items/{itemId}/edit")
public String updateItem(@ModelAttribute("form") BookForm form){
Book book = new Book();
book.setId(form.getId());
book.setName(form.getName());
book.setPrice(form.getPrice());
book.setStockQuantity(form.getStockQuantity());
book.setAuthor(form.getAuthor());
book.setIsbn(form.getIsbn());
itemService.saveItem(book);
return "redirect:/items";
}
준영속 엔티티는itemService.saveItem(book)
로직이 없으면 데이터를 setting할 때 변경 감지가 동작하지 않아서 데이터베이스 UPDATE가 발생하지 않음
수정을 시도하는 Book
객체
Book
객체는 이미 DB에 한 번 저장이 되어서 식별자가 존재함@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
Item findItem = em.find(Item.class, itemParam.getId()); //같은 엔티티를 조회한
다.
findItem.setPrice(itemParam.getPrice()); //데이터를 수정한다.
Dirty Checking
)이 동작해서 데이터베이스에 UPDATE SQL
실행@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
Item mergeItem = em.merge(itemParam);
}
merge()
를 실행한다- 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회한다
2-1. 만약 1차 캐시에 엔티티가 없으면 데이터베이스에서 엔티티를 조회하고, 1차 캐시에 저장한다- 조회한 영속 엔티티(
mergeMember
)에member
엔티티의 값을 채워 넣는다.
(member
엔티티의 모든 값을mergeMember
에 밀어 넣는다. 이때mergeMember
의 "회원1"이라는 이름이 "회원명변경"으로 바뀐다)- 영속 상태인
mergeMember
를 반환한다
- 준영속 엔티티의 식별자 값으로 영속 엔티티를 조회한다
- 영속 엔티티의 값을 준영속 엔티티의 값으로 모두 교체한다(병합한다)
- 트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 데이터베이스에
UPDATE SQL
이 실행된다.
null
로 업데이트 할 위험도 있음(병합은 모든 필드를 교체함)package jpabook.jpashop.repository;
...
@Repository
@RequiredArgsConstructor
public class ItemRepository {
private final EntityManager em;
public void save(Item item){
if(item.getId() == null) {
em.persist(item);
} else {
em.merge(item);
}
}
...
}
save()
메서드는null
) 새로운 엔티티로 판단해서 영속화(persist
)함merge
)save()
메서드 하나로 저장과 수정(병합)을 다 처리함persist()
로 영속화하고, 만약 식별자 값이 있으면 이미 한 번 영속화 되었던 엔티티로 판단해서 merge()
로 수정(병합)함save
)라는 의미는 신규 데이터를 저장하는 것뿐만 아니라, 변경된 데이터의 저장이라는 의미도 포함함dirty checking
) 기능이 동작해서 트랜잭션을 커밋할 때 자동으로 수정되므로 별도의 수정 메서드를 호출할 필요가 없고 그런 메서드도 없음save()
메서드는 식별자를 자동 생성해야 정상 동작함Item
엔티티의 식별자는 자동으로 생성되도록 @GeneratedValue
를 선언함save()
메서드를 호출하면 persist()
가 호출되면서 식별자 값이 자동으로 할당됨@Id
만 선언이 되었다면, 이 경우 save()
메서드를 호출하면 식별자가 없는 상태로 persist()
를 호출하여 식별자가 없다는 예외가 발생함null
로 업데이트를 해버림
- 컨트롤러에서 어설프게 엔티티를 생성하면 안됨
- 트랜잭션이 있는 서비스 계층에서 식별자(
id
)와 변경할 데이터를 명확하게 전달(파라미터 or dto)- 트랜잭션이 있는 서비스 계층에서 영속 상태의 엔티티를 조회하고, 엔티티의 데이터를 직접 변경
- 트랜잭션 커밋 시점에 변경 감지가 실행됨
package jpabook.jpashop.web;
...
@Controller
@RequiredArgsConstructor
public class ItemController {
private final ItemService itemService;
...
/**
* 상품 수정, 권장 코드
*/
@PostMapping(value = "/items/{itemId}/edit")
public String updateItem(@PathVariable Long itemId, @ModelAttribute("form")
BookForm form) {
itemService.updateItem(itemId, form.getName(), form.getPrice(),
form.getStockQuantity());
return "redirect:/items";
}
}
package jpabook.jpashop.service;
...
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class ItemService {
private final ItemRepository itemRepository;
...
/**
* 상품 수정 - 영속성 컨텍스트가 자동 변경
*/
@Transactional
public void updateItem(Long id, String name, int price, int stockQuantity)
{
Item item = itemRepository.findOne(id);
item.setName(name);
item.setPrice(price);
item.setStockQuantity(stockQuantity);
}
}
flush
가 일어나면서