이 글은 Hibernate Types의 개발자 Vlad Mihalcea의 블로그 글의 내용을 담고있다. 해당 블로그 글은 아래와 같은 이유로 save()
메소드를 호출하는 것이 안티패턴인 경우에 대해 설명한다.
미리 요약하자면 성능 오버헤드가 있다는 이야기이다. Mihalcea가 High-Performance Java Persistence라는 책의 저자인 만큼 성능에 중점을 두는 것 같다. 하지만 클라우드 환경에서는 백엔드 어플리케이션을 한없이 스케일 아웃할 수 있는데 이렇게까지 해야하나하는 생각이 든다. 그리고 JPA의 스펙에 맞추기 위해 너무 벤더 특정적인 코드를 만들게 되지 않을까하는 걱정도 된다.
나만 이렇게 생각한 것이 아닌지 블로그 글의 댓글창에 가면 반발하는 목소리가 많다 ㅋㅋㅋㅋ. Mihalcea도 이를 인지하고 있고 "성능이 더 필요한 경우에 사용하는 익스텐션 개념으로 생각하라"라고 말하고 있다.
아래 글에서 설명하겠지만 배치에 경우에는 확실히 save메소드를 사용하는데 주의해야할거 같다.
액티브 레코드 패턴이란?
관계형 데이터베이스에 인메모리 객체를 저장할때 사용되는 아키텍쳐 배턴이다. 모델에 쿼리 메소드를 정의하고, 해당 메소드를 사용하여 모델을 저장, 제거, 수정한다. 마틴 파울러의 P of EAA에서 등장했다
persist
를 호출하여 엔티티가 관리되도록하고, flush
를 통해 데이터베이스에 삽입한다 detached
인 상태에서 변경을 하면, 변경이 데이터베이스로 전파되도록 해야한다. 이를 위해 merge
나 update
를 한다. merge
는 엔티티의 영속성 컨텍스트에 의해 로딩된 새로운 객체에 상태를 복사하고 flush
시점에 업데이트가 필요한지 확인한다. update
는 현재 엔티티 상태로 flush
를 트리거 하도록 강제한다 remove
메소드는 삭제를 예약하고, flush가 삭제 쿼리를 트리거한다@Transactional
public <S extends T> S save(S entity) {
if (this.entityInformation.isNew(entity)) {
this.em.persist(entity);
return entity;
} else {
return this.em.merge(entity);
}
}
@Transactional
public void saveAntiPattern(Long postId, String postTitle) {
Post post = postRepository.findById(postId).orElseThrow();
post.setTitle(postTitle);
postRepository.save(post);
}
save
메소드의 호출이다merge
하면 MergeEvent
가 트리거 되면서 CPU 사이클을 사용하게 된다persist
대신 merge
를 호출하게 된다. 이로인해 불필요한 조회 쿼리가 실행 될 수 있다.public interface HibernateRepository<T> {
//The findAll method will trigger an UnsupportedOperationException
@Deprecated
List<T> findAll();
//Save methods will trigger an UnsupportedOperationException
@Deprecated
<S extends T> S save(S entity);
@Deprecated
<S extends T> List<S> saveAll(Iterable<S> entities);
@Deprecated
<S extends T> S saveAndFlush(S entity);
@Deprecated
<S extends T> List<S> saveAllAndFlush(Iterable<S> entities);
//Persist methods are meant to save newly created entities
<S extends T> S persist(S entity);
<S extends T> S persistAndFlush(S entity);
<S extends T> List<S> persistAll(Iterable<S> entities);
<S extends T> List<S> peristAllAndFlush(Iterable<S> entities);
//Merge methods are meant to propagate detached entity state changes
//if they are really needed
<S extends T> S merge(S entity);
<S extends T> S mergeAndFlush(S entity);
<S extends T> List<S> mergeAll(Iterable<S> entities);
<S extends T> List<S> mergeAllAndFlush(Iterable<S> entities);
//Update methods are meant to force the detached entity state changes
<S extends T> S update(S entity);
<S extends T> S updateAndFlush(S entity);
<S extends T> List<S> updateAll(Iterable<S> entities);
<S extends T> List<S> updateAllAndFlush(Iterable<S> entities);
}
@Repository
public interface PostRepository extends HibernateRepository<Post>, JpaRepository<Post, Long> {
...
}