스프링 데이터 JPA - 인프런
JPA(Java Persistence API)를 보다 쉽게 사용할 수 있도록 여러 기능을 제공하는 스프링 데이터 JPA에 대해 학습합니다.

JPA를 사용할 때 반드시 알아야 하는 것중 하나가 엔티티 객체의 상태 변화인데 JPA를 쓰면서 Transient(New), Persistent, Detached, Deleted 이런 상태 변화와 그 특징에 대한 공부를 한적이 없다면 JPA를 잘못쓰고 있을 가능성이 크다.

이 중에서도 Persistent 상태가 가장 중요한데, 하이버네이트가 한 트랜잭션 내에서 불필요한 쿼리를 줄여주는 역할을 하기 때문이다. 흔히 1차 캐시라고 부르는 Pesistent Context가 해당 인스턴스를 이미 담고 있기 때문에 DB에서 쿼리를 하더라도 불필요한 select 쿼리가 발생하지 않는다.

private void savePost() {
    Post post = new Post();
    post.setTitle("keesun");
    
    Post newPost = postRepository.save(post);
    System.out.println(postRepository.findById(newPost.getId()));
}

이런 코드가 있다고 가정보자. 새 글을 저장하고 findById를 통해 id로 저장했던 Post를 다시 가져오는 코드다. 이때 postRepository가 스프링 데이터 JPA를 사용하지 않고 스프링 JDBC를 사용해 구현하거나 스프링 데이터 JDBC를 사용해 구현했다면 이 코드는 분명히 insert 쿼리 이후에 select 쿼리가 발생한다.

하지만, postRepository를 스프링 데이터 JPA가 제공하는 JpaRepository 인터페이스를 상속받아 구현했다면, 이 코드는 insert 쿼리만 발생하고 select 쿼리는 발생하지 않는게 이론적으로 맞다. save()를 호출했을 때 newPost (post와 같은 인스턴스)가 Persistent 상태로 영속화 컨텍스트인 EntityManager(또는 하이버네이트의 Session)에 캐시가 되기 때문이다. 그래서 해당 id를 가지고 있는 Post 인스턴스를 DB에서 꺼내오는게 아니라 캐시하고 있는 메모리에서 꺼내주고, 따라서 select 쿼리는 발생하지 않는게 맞다.

그리고 실제 해당 객체가 Persistent 상태인지 확인하려면 EntityManager를 주입받아서 contains 메소드를 통해 확인할 수 있다. 이렇게!

entityManager.contains(post);

과연 그럴까?

@Component
public class JpaRunner implements ApplicationRunner {

    @PersistenceContext
    EntityManager entityManager;

    @Autowired
    PostRepository postRepository;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        savePost();
    }

    @Transactional
    private void savePost() {
        Post post = new Post();
        post.setTitle("keesun");

        Post newPost = postRepository.save(post);
        System.out.println(postRepository.findById(newPost.getId()));
        System.out.println(entityManager.contains(newPost));
    }
}

이 코드를 실행하면 콘솔창에 다음과 같은 메시지를 확인할 수 있다.

2020-08-26 22:04:22.546  INFO 54359 --- [           main] m.w.demodomainclassconverter.App         : Started App in 2.062 seconds (JVM running for 2.458)
Hibernate: call next value for hibernate_sequence
Hibernate: insert into post (title, id) values (?, ?)
Hibernate: select post0_.id as id1_0_0_, post0_.title as title2_0_0_ from post post0_ where post0_.id=?
Optional[me.whiteship.demodomainclassconverter.Post@7ac48e10]
false

insert 쿼리 이후에 select 쿼리가 발생했고, EntityManager의 contains를 통해 Persistent 상태가 맞는지 (EntityManager가 담고 있는지) 확인해 봤으나 false가 출력됐다.

아니 왜?? save를 했으니 Persistent 상태가 되고 즉, EntityManager가 가지고 있을테니 select 쿼리가 발생하지 않아야 하고 contains 결과는 true가 나와야 하는거 아닌가?? 그게 맞다. 그런데 이런 일이 벌어진거에는 또 다른 이유가 있다. 그건 다음 시간에!