Skip to main content Link Search Menu Expand Document (external link)
  • JpaRepository.save()
    • persist
    • merge
  • Projection

JpaRepository.save()

save() 는 단순히 엔티티를 저장해주는 기능을 수행하는 것이 아님. 경우에 따라 persist 또는 merge 로 동작한다.

  • Transient 상태의 객체라면 EntityManager.persist()
  • Detached 상태의 객체라면 EntityManager.merge()

persist

Persist() 메소드에 파라미터로 넘긴(save 대상) 그 엔티티 객체를 Persistent 상태로 변경한다. save() 결과로 반환받은 saved entity 가 곧 파라미터로 넘긴 그 save 대상과 같다.

@DataJpaTest
public class PostRepositoryTest {

    @Autowired
    PostRepository postRepository;

    @PersistenceContext
    EntityManager entityManager;

    @Test
    void saveTest() {
        Post post = new Post();
        post.setName("name_1");
        post.setDescription("description_1");
        Post savedPost = postRepository.save(post);

        Assertions.assertThat(entityManager.contains(post)).isTrue();
        Assertions.assertThat(entityManager.contains(savedPost)).isTrue();
        Assertions.assertThat(post).isEqualTo(savedPost);
    }

}

merge

주석 표시한 것들을 잘 기억하자. update 시 파라미터로 넘긴 entity는 영속화되지 않고 그것의 복사본이 영속화 된다. 그리고 영속화된 객체가 return 된다.

이 포인트가 주는 중요한 시사점은 update 로직 처리 이후 후속 작업을 해야할 경우 반드시 return 된 그 값을 사용해야 한다는 것이다. 왜냐하면 그 객체가 persistent 된 객체이기 때문이다. 반대로 말하면 save() 의 파라미터로 넘긴 객체를 사용해서는 안된다는 것이다.

왜 사용하면 안되냐? managed 객체가 아니기 때문이다. 즉, persistent 상태가 아니기 때문에 상태가 추적이 되지 않아서 dirty check 등 JPA의 이점을 누리지 못하기 때문이다.

    @Test
    void updateTest() {
        Post post = new Post();
        post.setName("name_1");
        post.setDescription("description_1");
        postRepository.save(post);

        Post newPost = new Post();
        newPost.setId(1L);
        newPost.setName("new_name_1");
        newPost.setDescription("new_description_1");
        Assertions.assertThat(entityManager.contains(newPost)).isFalse();

        // merge 발생. 이 때 newPost의 복사본이 영속화된다.
        Post updatedPost = postRepository.save(newPost);

        // 파라미터로 넘긴 entity인 newPost 는 detached
        Assertions.assertThat(entityManager.contains(newPost)).isFalse();

        // update 결과로 받은 entity인 updatedPost는 persistent
        Assertions.assertThat(entityManager.contains(updatedPost)).isTrue();
    }

Projection

entity의 일부 컬럼만 가져오는 기능인데 나는 실무에서 써본적이 없거니와 얻는 성능 효율 대비 코드 복잡도만 더 커지는 느낌이다. 일단은 이런게 있다 정도만 인지해둔다.

아래는 강의 노트 그대로 발췌.

  • 인터페이스 기반 프로젝션
    • Nested 프로젝션 가능.
    • Closed 프로젝션
      • 쿼리를 최적화 할 수 있다. 가져오려는 애트리뷰트가 뭔지 알고 있으니까.
      • Java 8의 디폴트 메소드를 사용해서 연산을 할 수 있다.
    • Open 프로젝션
      • @Value(SpEL)을 사용해서 연산을 할 수 있다. 스프링 빈의 메소드도 호출 가능. -쿼리 최적화를 할 수 없다. SpEL을 엔티티 대상으로 사용하기 때문에.
  • 클래스 기반 프로젝션
    • DTO
    • 롬복 @Value로 코드 줄일 수 있음