JPA를 개발하다 보면, 아래와 같은 행위를 할떄가 가끔씩 있다.
(Spring Data JPA를 사용한다고 가정)
1 | Member newMember = newMemberDTO.toEntity(); |
이는 간단하다.
Repository를 만들때 implements하는 JpaRepository의 구현체인 SimpleJpaRepository의 save 메서드를 보면 아래와 같다.
1 | // repository.save |
보다시피 엔티티의 식별자가 전달되었다면 merge, 그렇지 않다면 persist를 수행한다.
위와 같이 식별자를 강제로 넣어서 전달하면 해당 엔티티에 대해서 em.merge가 발생하게 되는 것
이다.
merge니까, 해당 식별자로 member 테이블을 조회하고, 엔티티가 존재하면 영속성 컨텍스트에 넣게 된다.
그리고 flush 되면 변경감지에 의해 update 문이 발생하게 될 것이다.(newEntity로 완전 대체되었기 때문에)
만약 해당 식별자에 해당하는 엔티티가 없다면, 새로 insert 될 것이다.
PUT REST API를 작성하기에 적절한 방법이다.(틀릴수도…)
그렇다면 아래와 같이 하면 어떻게 될까?
1 | Member newMember = newMemberDTO.toEntity(); |
newItem으로 대체하는데 oldItem의 값들이 몇개 필요해서 oldEntity를 조회한 다음 해당 값을 사용하는 케이스이다.
이럴 경우, find에 의해 처음 영속성 컨텍스트로 들고오면 아래와 같을 것이다.
key | entity |
---|---|
1 | oldEntity |
이 상태에서 아래의 save에 의해 merge가 수행될 것이고, 결과적으로 아래처럼 변할것이다.
key | entity |
---|---|
1 | newEntity |
즉 oldEntity는 아예 영속성 컨텍스트에 의해 관리되지 않는 준영속 상태가 되어버리므로, 위처럼 oldItem에 무언가 변경을 수행해도 아무일도 일어나지 않는다.
(당연한가?)
자식이 있을 경우
해당 엔티티에 속하는 자식 엔티티들이 있을 경우 조금 복잡해진다.(안 복잡할수도 있다)
1 | class Member{ |
위와 같다고 할 때, 아래와 같이 작성하면 문제가 발생한다.
1 | Member newMember = newMemberDTO.toEntity(); // orderList를 가지고 있음 |
이때는, newMember의 orderList에 대해 연쇄적으로 persist를 수행하고 끝나버릴 것이므로,
만약 기존의 1번 member에 해당하는 order가 있었다고 할 경우, 해당 order는 그대로 있고 신규로 전달된 order들이 저장되게 될 것이다.
그러므로 만약 기존 1번 member에 해당하는 order가 2개 있고, 전달된 order가 2개인 상태에서 위의 메서드를 실행하게 되면 order가 총 4개가 되어버린다.
사실상 이건 명확한 교체가 아니므로,
의도한 상황이라면 상관없는데, 그게 아니라면 orphanRemoval = true
를 사용하여 존재하지 않는 엔티티에 대해 삭제하게끔 해야한다.
1 | class Member{ |
1 | Member newMember = newMemberDTO.toEntity(); // orderList를 가지고 있음 |
이렇게 하면 기존 1번 member에 있던 order는 모두 삭제되고, newMember에 있는 order가 전부 insert 된다.
만약 기존 order에 새로 전달받은 order를 덧붙이고 싶으면 newMember의 orderList에 살려놓을 order들을 추가해줘야 한다.
1 | Member newMember = newMemberDTO.toEntity(); // orderList를 가지고 있음 |
참고로 여기서 merge가 진행되고 나면 기존에 oldMember의 자식들에 변형이 가해진다(무슨 기준인지는 모르겠다)
이거까지 신경쓰는건 좋지 않은 것 같다…
id를 바꾸고 merge 할 생각이라면 merge 할 대상만 신경쓰도록 하고,
기존 entity의 list에 뭔가를 수행하고 싶을 경우 merge 전에 해주는게 좋겠다.