springframework/Spring Data JPA

Spring Data JPA : Update 쿼리

Jungsoomin :) 2020. 11. 18. 23:08

강의 수강 중 궁금했던 Update 쿼리에 대해 배운다.

  • DirtyChecking + WriteBehind 에 의한 Persistent 상태의 Entity 에 변화가 일어났으며, find()를 호출해야하는 상황이 오면 Update 문이 자동으로 실행
  • save() 호출 시 Entity 상태에 따른 EntityManager 의 persist() , merge() 사용으로 인한 Persistent 상태 전환 에서 사용되는 Update
  • find, count, delete 메서드는 제공하는데 update는 열심히 찾아보아도 없더라.

사실 제공기능을 공부하다보니 필요할까 라는 생각도 들었었다.


메서드로 제공되지 않는 이유

  • Persistent 상태의 Entity 변화를 감지(DirtyChecking) 후 변경이 있다면 변경(WriteBehind)
  • 데이터 베이스에 동기화시키는 것을 Flush 라고 하며 이때 Update 문이 실행된다.

직접 정의해서 쓰고 싶다면

  • @Modifying + @Query 로 직접 기입
  • 추천되지 않는 방법이다.
public interface CommentRepository extends JpaRepository<Comment,Long> {

    @Query("SELECT c, c.title as cTitle FROM #{#entityName} as c WHERE c.title = :title")
    List<Comment> findByTitle(@Param("title") String title, Sort sort);

    @Modifying
    @Query("UPDATE Comment AS C SET C.title = ?1 WHERE C.id = ?2")
    int updateTitle(String updateWord, Long id);
}

@Test
    public void testUpdate(){
        Comment comment = new Comment();
        comment.setTitle("Spring?");
        comment.setComment("Data JPA");

        Comment savedComment = commentRepository.save(comment);

        int updatedRow = commentRepository.updateTitle("Hibernate :)", 1L);

        assertThat(updatedRow).isEqualTo(1);

        List<Comment> selectedComment = commentRepository.findByTitle("Hibernate :)", Sort.by(Sort.Direction.ASC,"title"));
        selectedComment.forEach(System.out::println);

        assertThat(selectedComment.get(0).getId()).isEqualTo(1L);
        assertThat(selectedComment.get(0).getTitle()).isEqualTo("Hibernate :)");
    }

다시 find() 를 호출해도 변경된 레코드를 가져오지 않는다.

  • 1차 캐싱기능 때문
  • 하나의 트랜잭션으로 진행상태로 인해 Entity 객체는 여전히 Persistent 상태
  • Persistent 상태의 Entity 를 find() 해오려고 했으니 PersistentContext 는 캐싱하고 있는 Entity 를 꺼냄
org.opentest4j.AssertionFailedError: 
Expecting:
 <"Spring?">
to be equal to:
 <"Hibernate :)">
but was not.
Expected :"Hibernate :)"
Actual   :"Spring?"

쿼리 실행시 PersistentContext 안의 Cache 를 Clear 해주어야 find() 호출시 DB에 접근해서 레코드를 가져올 수 있다.

  • @Modifying 어노테이션의 clearAutomatically 속성을 true로 주면 메서드 실행 후 PersistentContext 에 Cache 된 데이터를 비운다.
  • @Modifying 어노테이션의 flushAutomatically 속성을 true 주면 메서드 실행 전에 PersistentContext 에 Cache 된 데이터를 DB에 동기화 시킴
public interface CommentRepository extends JpaRepository<Comment,Long> {

    @Query("SELECT c, c.title as cTitle FROM #{#entityName} as c WHERE c.title = :title")
    List<Comment> findByTitle(@Param("title") String title, Sort sort);

    @Modifying(clearAutomatically = true)
    @Query("UPDATE Comment AS C SET C.title = ?1 WHERE C.id = ?2")
    int updateTitle(String updateWord, Long id);
}

///
Hibernate: 
    select
        comment0_.id as col_0_0_,
        comment0_.title as col_1_0_,
        comment0_.id as id1_0_,
        comment0_.comment as comment2_0_,
        comment0_.title as title3_0_ 
    from
        comment comment0_ 
    where
        comment0_.title=? 
    order by
        comment0_.title asc
2020-11-18 22:53:26.327 TRACE 12304 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [Hibernate :)]
2020-11-18 22:53:26.331 TRACE 12304 --- [           main] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_] : [BIGINT]) - [1]
2020-11-18 22:53:26.335 TRACE 12304 --- [           main] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([comment2_0_] : [VARCHAR]) - [Data JPA]
2020-11-18 22:53:26.335 TRACE 12304 --- [           main] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([title3_0_] : [VARCHAR]) - [Hibernate :)]
2020-11-18 22:53:26.336 TRACE 12304 --- [           main] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([col_0_0_] : [BIGINT]) - [1]
2020-11-18 22:53:26.342 TRACE 12304 --- [           main] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([col_1_0_] : [VARCHAR]) - [Hibernate :)]
Comment{id=1, title='Hibernate :)', comment='Data JPA'}

DirtyChecking + WriteBehind 기능을 사용한 테스트 코드는 이러하다

	@Test
    public void testUpdate1(){
        Comment comment = new Comment();
        comment.setTitle("Spring?");
        comment.setComment("Data JPA");

        Comment savedComment = commentRepository.save(comment);

        comment.setTitle("Hibernate :)"); // DirtyChecking + WriteBehind

        List<Comment> selectedComment = commentRepository.findByTitle("Hibernate :)", Sort.by(Sort.Direction.ASC, "title"));
        selectedComment.forEach(System.out::println);

        assertThat(selectedComment.get(0).getId()).isEqualTo(1L);
        assertThat(selectedComment.get(0).getTitle()).isEqualTo("Hibernate :)");
    }
    
 ///
 Hibernate: 
    select
        comment0_.id as col_0_0_,
        comment0_.title as col_1_0_,
        comment0_.id as id1_0_,
        comment0_.comment as comment2_0_,
        comment0_.title as title3_0_ 
    from
        comment comment0_ 
    where
        comment0_.title=? 
    order by
        comment0_.title asc
2020-11-18 22:59:01.282 TRACE 16880 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [Hibernate :)]
2020-11-18 22:59:01.288 TRACE 16880 --- [           main] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_] : [BIGINT]) - [1]
2020-11-18 22:59:01.289 TRACE 16880 --- [           main] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([col_0_0_] : [BIGINT]) - [1]
2020-11-18 22:59:01.305 TRACE 16880 --- [           main] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([col_1_0_] : [VARCHAR]) - [Hibernate :)]
Comment{id=1, title='Hibernate :)', comment='Data JPA'}