Spring Data JPA - Update 쿼리 메소드

Spring Data JPA

백기선님의 강의인 Spring Data JPA 강의를 듣고 공부한 내용을 정리한 글

Update 쿼리 메소드

쿼리 메소드는 아래와 같이 사용할 수 있다.

  • find

  • count

  • delete

  • update는 어떻게..?

그럼 update는 어떻게 해야할까?

주로 update persistence로 관리하다가 객체의 변화가 일어나면 flush라는 것을 하는데 이 때 update 쿼리가 날아간다.

그래서 update 쿼리를 직접 쓰는 경우가 거의 없다.

그런데 Update를 명시적으로 쓰고 싶은 경우가 있을 수 있다.

이 때는 @Query 애노테이션과 JPQL을 사용하여 쿼리를 만들어줄 수 있다.

단 아래와 같이 @Modifying이라는 애노테이션도 추가로 붙여야한다.

@Modifying
@Query("Update ...")
fun updateXXX() {

}

그러나 이 방법은 일반적으로 추천되지 않는다.

왜 일까?

예제

    private fun savePost(): Post {
        val post = Post()
        post.title = "Spring"
        return postRepository.save(post)
    }

    @Test
    fun updateTitle() {
        val post = savePost()
        val title = "hibernate"

        postRepository.updateTitle(title, post.id!!)

        assertEquals(post.id!!, 1)

        val updatedPost = postRepository.findByIdOrNull(post.id!!)
        assertEquals(updatedPost?.title, title)
    }

직접 코드를 보면 그 이유를 알 수 있다.

post를 update한 이후 find 쿼리 메소드를 사용해서 post를 가져오면 어떤 일이 일어날까?

다연히 updatedPost의 title이 “hibernate”일 것이라고 예상할 것이다.

왜냐면 방금 update query를 날렸으니까.

그러나 놀랍게 이 테스트는 실패한다. 왜일까?

Hibernate: 
    update
        post 
    set
        title=? 
    where
        id=?

org.opentest4j.AssertionFailedError: 
Expected :Spring
Actual   :hibernate
<Click to see difference>

실제 하이버네이트 쿼리를 보면 분명히 update 쿼리는 날아갔는데,

Select 쿼리가 날아가지 않았다.

이것이 바로 이 방법을 권장하지 않는 이유이다.

그 이유는 post 객체가 아직 persistent context에 존재하기 때문이다.

아직 트랜잭션이 끝나지 않았기 때문에 캐시가 유지되기 때문이다.

다시 말해서 post 객체가 아직 persistent context에게 관리 받고 있고 (트랜잭션이 끝나기 전까지는 persistent context가 이를 관리한다)

그리고 이 때 find 쿼리 메소드를 사용하면 캐싱하고 있는 것을 그대로 가져온다.

물론 update 쿼리는 db에 날아갔지만, persistence 상태에 있는 post 객체가 캐시에 여전히 남아있기 때문에 find 쿼리 메소드를 날려도 캐싱된 객체를 반환한다.

그래서 jpa에서는 이런 경우를 막기 위해 @Modifying 애노테이션에 다음과 같은 설정 값을 지원해주고 있다.

@Modifying(clearAutomatically = true)

이 옵션의 주석을 살펴보면 다음과 같다.

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Documented
public @interface Modifying {

	/**
	 * Defines whether we should flush the underlying persistence context before executing the modifying query.
	 * 
	 * @return
	 */
	boolean flushAutomatically() default false;

    /**
	 * Defines whether we should clear the underlying persistence context after executing the modifying query.
	 *
	 * @return
	 */
	boolean clearAutomatically() default false;

flushAutomatically는 persistence context 상태라면 flush 해서 update 해주겠다는 것이고,

clearAutomatically는 캐시를 비워주는 역할을 한다.

우리가 clearAutomatically를 true로 한 이유도 persistence context 캐시를 비워주어야 다시 읽어올 수 있기 때문이다.

그러나

이 방법은 추천되지않는다.

여전히 이런 문제가 있기 때문이고

delete는 또 다른 문제가 있다고 한다.

그러니 가급적이면 applciation 로직으로 작성하는게 낫다.

    private fun savePost(): Post {
        val post = Post()
        post.title = "Spring"
        return postRepository.save(post)
    }

    @Test
    fun updateTitle() {
        val post = savePost()
        post.title = "hibernate"


        val updatedPost = postRepository.findAll()
        assertEquals(updatedPost[0].title, "hibernate")
    }
// update 쿼리가 날아간 모습
Hibernate: 
    update
        post 
    set
        content=?,
        created=?,
        title=? 
    where
        id=?

명시적으로 update 쿼리를 날리진 않았지만 하이버네이트가 알아서 값이 바뀐 것을 감지하고 update 쿼리를 db에 날려준다.

그러니 만약 update가 필요하다면 application 레벨에서 이를 처리하도록 하자.

Reference

인프런 백기선님의 스프링 Data JPA



© 2022. by minkuk

Powered by minkuk