JPA 영속성 컨텍스트가 더는 관리하지 않는 엔티티를 말한다.
영속성 엔티티는 변경감지가 일어난다. (무엇이 변경되었는지 DB가 알고 있다.) 즉, 트랜잭션 커밋시점에서 변경된 것을 빠르게 DB가 업데이트하지만 준영속은 내가 new
를 통해 직접 만들었기 때문에 변경해도 DB에 업데이트가 되지 않는다.
그렇다면 준영속은 어떻게 DATA를 업데이트할 수 있을까?
준영속 엔티티를 수정할 수 있는 두 가지 방법이 있다.
a. 변경 감지 기능 사용
b. 병합(merge) 사용
@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
Item findItem = em.find(Item.class, itemParam.getId()); //같은 엔티티를 조회한다.
findItem.setPrice(itemParam.getPrice()); //데이터를 수정한다.
}
트랜잭션 안에서 엔티티를 다시 조회, 변경할 값을 선택한 뒤 트랜잭션 커밋 시점에 변경 감지(Dirty Chekcing)이 동작해서 DB에 UPDATE
쿼리를 실행한다.
병합은 준영속 상태의 엔티티를 영속 상태로 변경할 때 사용하는 기능이다.
@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
Item mergeItem = em.merge(item);
}
//위의 병합(merge)코드는 아래 코드와 동일한 방식으로 동작한다.
@Transactional
public Item updateItem(Long itemId, Book param) {
Item findItem = itemRepository.findOne(itemId);
findItem.setPrice(param.getPrice());
findItem.setName(param.getName());
findItem.setStockQuantity(param.getStockQuantity());
return findItem
}
merge()
를 실행한다.mergeMember
)에 member
엔티티의 값을 채워넣는다. (member
엔티티의 모든 값을 mergeMember
에 밀어 넣는다. 이때 mergeMember
의 “회원1”이라는 이름이 “회원명변경”으로 바뀐다.)mergeMember
를 반환한다.주의
변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만, 병합을 사용하면 모든 속성이 변경된다. 병합시 값이 없으면null
로 업데이트할 위험도 있다. (병합은 모든 필드를 교체한다.)
@Repository
public class ItemRepository {
@PersistenceContext
EntityManager em;
public void save(Item item) {
if (item.getId() == null) {
em.persist(item);
} else {
em.merge(item);
}
}
//...
}
save()
메서드는 식별자 값이 없으면(null
) 새로운 엔티티로 판단해서 영속화(persist)하고 식별자가 있으면 병합(merge)id
값이 있으므로 병합 수행상품 리포지토리에선 save()
메서드를 유심히 봐야 하는데, 이 메서드 하나로 저장과 수정(병합)을 다 처리한다. 코드를 보면 식별자 값(Id
)이 없으면 새로운 엔티티로 판단해서 persist()
로 영속화하고 만약 식별자 값이 있으면 이미 한 번 영속화되었던 엔티티로 판단해서 merge()
로 수정(병합)한다.
결국 여기서의 저장(save
)이라는 의미는 신규 데이터를 저장하는 것뿐만 아니라 변경된 데이터의 저장이라는 의미도 포함된다. 이렇게 함으로써 이 메서드를 사용하는 클라이언트는 저장과 수정을 구분하지 않아도 되므로 클라이언트의 로직이 단순해진다.
여기서 사용하는 수정(병합)은 준영속 상태의 엔티티를 수정할 때 사용한다. 영속 상태의 엔티티는 변경 감지(Dirty checking)기능이 동작해서 트랜잭션을 커밋할 때 자동으로 수정되므로 별도의 수정 메서드를 호출할 필요가 없고 그런 메서드도 없다.
참고
save()
메서드는 식별자를 자동 생성해야 정상 동작한다. 여기서 사용한Item
엔티티의 식별자는 자동으로 생성되도록@GeneratedValue
를 선언했다. 따라서 식별자 없이save()
를 호출하면persist()
가 호출되면서 식별자 값이 자동으로 할당된다. 반면에 식별자를 직접 할당하도록@Id
만 선언했을 경우, 식별자를 직접 할당하지 않고,save()
메서드를 호출하면 식별자가 없는 상태로persist()
를 호출한다. 그러면 식별자가 없는 예외가 발생한다.
엔티티를 변경할 때는 항상 변경감지를 사용해라.
Id
)와 변경할 데이터를 명확하게 전달하라. (파라미터 or DTO)@Controller
@RequiredArgsConstructor
public class ItemController {
private final ItemService itemService;
/**
* 상품 수정, 권장 코드
*/
@PostMapping(value = "/items/{itemId}/edit")
public String updateItem(@ModelAttribute("form") BookForm form) {
itemService.updateItem(form.getId(), form.getName(), form.getPrice());
return "redirect:/items";
}
}
@Service
@RequiredArgsConstructor
public class ItemService {
private final ItemRepository itemRepository;
/**
* 영속성 컨텍스트가 자동 변경
*/
@Transactional
public void updateItem(Long id, String name, int price) {
Item item = itemRepository.findOne(id);
item.setName(name);
item.setPrice(price);
}
}