[Spring] CrudRepository.save()는 항상 insert 쿼리를 만들지 않는다
동기
프로젝트를 하면서 다른 분이 작성한 코드를 본 적 있는데, DB에 저장돼있는 엔티티 즉, 애플리케이션으로 가져왔을 때 영속성 컨텍스트에 의해
관리되고 있는 엔티티를 수정한 후 업데이트해야 하는 케이스가 있었다. 이때 save()
또는 saveAll()
을 호출했고 실제로 날아가는 쿼리가
insert 쿼리가 아닌 update 쿼리였고, 무조건 insert 쿼리로 날라갈거라는 편견이 있었어서 찾아보고 적어보게 됐다.
왜?
흔히 사용하는 JpaRepository를 상속하면 save()
를 사용할 수 있게된다. 정의는 CrudRepository에서 하고 있다.
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity);
...
}
구현부는 SimpleJpaRepository에 있다! isNew()
로 새 엔티티인지 판별하고 맞으면 insert, 그렇지 않으면 update를 한다.
isNew()
는 엔티티 상태에 따라서 새 엔티티 판별 기준이 달라지는데, 바로 아래에 작성한 isNew()
내용을 참고하자!
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
@Transactional
@Override
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) { // 새 엔티티인가?
em.persist(entity); // insert 쿼리 발생
return entity;
} else {
return em.merge(entity); // update 쿼리 발생
}
}
...
}
isNew()
어떻게 새 엔티티인지 판별하는가는 case-by-case다! 아래 경우의 수를 참고하자.
- 엔티티가 Persistable<ID>를 구현한 경우
Persistable<ID>
를 구현한 엔티티라면 해당 엔티티 안에 구현한 isNew()
를 기준을 따른다.
아래에서 나올 @Id
, @Version
적용 여부는 기준으로 사용하지 않는다. (Persistable<ID>
가 우선순위 더 높음)
아래 예시에서는 name 길이가 0이면 isNew()
가 true임을 보장한다.
@Entity
public class Member implements Persistable<String> {
@Id
private String id;
private String name;
@Override
public boolean isNew() {
return name.length() == 0;
}
}
- @Version이 적용된 필드를 갖고있을 경우
해당 필드가 가진 값의 null 여부에따라 isNew()
의 결과값이 달라진다.
null이면 true를, not null이면 false를 반환한다.
이때 @Id
는 isNew()
의 판단 기준으로 활용하지 않는다. (@Version이 우선순위 더 높음)
(참고) @Version은 낙관적 락(Optimistic Lock)에서 활용되기도 한다.
@Entity
public class Member {
@Id
private String id;
@Version
private Integer version;
}
- @Id가 붙은 타입, 원시 타입(primitive type)이 아닌 경우
원시 타입이 아니라면 null을 허용하는 타입이므로, null 여부에 따라 isNew()
의 결과값이 달라진다.
null이면 true를, not null이면 false를 반환한다.
@Entity
public class Member {
@Id
private long id;
}
- @Id가 붙은 타입, 원시 타입(primitive type)인 경우
원시 타입이라면 null을 허용하지 않으므로, 값이 0이냐에 따라 isNew()
의 결과값이 달라진다.
0이면 true를, 0이 아니면 false를 반환한다.
@Entity
public class Member {
@Id
private Long id;
}
결론
save()
를 보고 항상 insert 쿼리를 날리겠지 했던게 꼭 그렇지만은 않다는걸 알 수 있었다!
코드를 보고 대충 기대하는 결과가 있을 수는 있지만, 내가 직접 확인해봤거나 확실한 레퍼런스를 체크해본게 아니라면 꼼꼼히 확인해볼 필요가 있겠다는 생각이 들었다.
댓글남기기