우리가 Spring을 사용하고 JPA를 사용하면서 데이터를 저장하거나 변경할 떄 save()메서드를 주로 사용한다. 근데 이러한 save() 메서드는 저장할 때 변경할 때 동작의 흐름이 같을까??
우선 JPA save()는 객체의 상태에 따라 persist()와 merge() 메서드를 실행한다.
persist()는 새로운 객체를 영속성 컨텍스트에 저장한다. 즉 데이터베이스에 존재하지 않는 새로운 객체일 때 호출이 된다. persist는 엔티티의 ID가 없을 때 사용되며 해당 엔티티는 새로운 행으로 저장이 된다. 이 때 새로운 Entity인지 여부는 JpaEntityInformation의 isNew(T entity)에 의해 판단됩니다. 이는 아래에서 좀더 자세히 살펴보자
merge()는 이미 존재하는 엔티티 객체를 수정하는데 사용된다. 이 메서드는 데이터베이스 이미 존재하는 id가 포함된 객체의 상태를 영속성 컨텍스트와 동기화한다.
즉 save() 메서드는 이 두 가지 동작을 객체 상태에 따라 자동으로 수행한다.
위와 같은 의문 또한 생길 수 있다. 참고로 영속성 컨텍스트에 객체가 동기화되는 시점은 다음과 같다.
위와 같은 경우 사실 드물다 실제 DB에 존재하는 데이터를 조회하는 동작에서 부터 영속성 컨텍스트에 동기화가 되기 때문이다. 하지만 만일 발생한다면 persist()가 실행이 된다. 그러나 이미 존재하는 데이터에 대해서 persist()가 호출되기 때문에 예외가 실행되고 우리는 이 예외 발생시 merge가 실행될 수 있도록 호출해주어야 한다.
새로운 Entity인지 확인 여부는 JpaEntityInformation의 isNew() 메서드에 의해 판단이 된다. 해당 메서드는 @Version를 사용하는데 JPA는 이를 통해 엔티티의 버전을 추적할 수 있다
위와 같은 방식을 통해 새로운 엔티티인지 여부를 판단한다.
기본적으로 JPA는 ID가 null 및 0일 경우에만 새로운 객체라고 생각한다. 따라서 만일 데이터의 ID가 수동으로 할당되었다면 isNew()가 의도한 대로 동작하지 않을 수 있따.
이를 해결하려면 Persisitable인터페이스를 구현하여 새로운 엔티티가 새로 생성되었는지 명확히 지정해야한다.
이 때는 엔티티에서 Persistable 인터페이스를 구현해서 JpaMetamodelEntityInformation 클래스가 아닌 JpaPersistableEntityInformation의 isNew()가 동작하도록 해야 하기 때문이다.
만일 우리가 이미 존재하고 있는 객체를 update해야하는 경우라면 우리는 update쿼리 하나로 이를 해결 할 수 있다. 그러나 새로운 객체인지 판단할 수 없다면 우리는 이를 조회 후 insert문을 넣어야 되는 비효율적인 쿼리를 발생 시킬 수 있고 이는 데이터 일관성을 파괴할 수도 있기 때문에 persist와 merge를 구분해서 update문을 사용해게끔 하는 것이 중요하다.
즉 데이터 베이스 연산 최적화와 데이터 일관성 관점에서 중요하기 때문에 우리는 이를 구분해야 한다.