쟈미로그

[JPA] @Modifying과 영속성 컨텍스트 본문

JPA

[JPA] @Modifying과 영속성 컨텍스트

쟈미 2023. 3. 2. 16:46

@Query 어노테이션을 사용해서 Native Query, JPQL을 사용하면서 예상치 못한 에러들을 마주쳤었다.

단순히 메소드 위에 Query 어노테이션만 붙여서 쿼리를 작성하면 런타임에러가 발생했다.

 

org.hibernate.hql.internal.QueryExecutionRequestException: Not supported for DML operations [UPDATE com.jyami.jyamibank.account.domain.account.Account a SET a.balance = a.balance - :amount WHERE a.accountNum = :accountNum AND a.balance >= :amount]
	at org.hibernate.hql.internal.ast.QueryTranslatorImpl.errorIfDML(QueryTranslatorImpl.java:319) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
	at org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:370) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
	at org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.java:219) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
	at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1459) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
	at org.hibernate.query.internal.AbstractProducedQuery.doList(AbstractProducedQuery.java:1649) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
	at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1617) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
	at org.hibernate.query.internal.AbstractProducedQuery.getSingleResult(AbstractProducedQuery.java:1665) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]

 

구글링한 결과 Modifying 어노테이션을 해당 쿼리 위에 붙이면 된다고 해서 붙이니 해결할 수 있었다. 하지만 왜 되는지는 몰라서 검색해봤다!

 

 

@Modifying

@Modifying은 JPA가 제공하는 메소드 네이밍으로 생성된 쿼리 말고,

@Query 어노테이션으로 작성된 native query들 중 SELECT 제외한 INSERT, UPDATE, DELETE 쿼리에 사용되는 어노테이션이다.

 

즉, 네이티브 쿼리로 CUD 쿼리를 작성했다면 꼭 @Modifying을 붙여줘야하는듯하다.

하지만 중요한 옵션이 있는데 알아보자!

 

clearAutomatically = true

@Query 어노테이션으로 작성된 JPQL/native query는 영속성 컨텍스트를 거치지 않고 DB에 값을 바로 읽기/반영한다.

하지만 JPA 메소드를 사용히면 영속성 컨텍스트에서 1차 캐시된 값을 읽기/반영하기 때문에, 둘을 혼용하면 문제가 발생한다.

 

 

예시를 보면서 알아보자!

int result = articleRepository.updateTitle(1L, "after"); // "after"로 UPDATE
 
System.out.println(articleRepository.findById(1L).get().getTitle()); // "before"이 출력

 

이 경우, (updateTitle 메소드는 네이티브 쿼리이고, findById 메소드는 JPA 메소드임)

    1. updateTitle 쿼리 실행

    2. DB 데이터 바로 update → after로 변경

    3. 영속성 컨텍스트에는 변화 X (이전 데이터 그대로 남아있음)

    4. findById 실행

    5. 영속성 컨텍스트에 있는 값을 읽어옴 → before 출력

 

이런 문제를 예방하려면 native query가 실행될 때 영속성 컨텍스트를 바로 비우면 된다.

그러면 이후에 JPA 메소드가 실행됐을 때 영속성 컨텍스트에 해당 값이 없기 때문에 DB에서 값을 가져오게된다!

Modifying 어노테이션 옵션 중 clearAutomatically = true (default는 false)를 해주면 쿼리 실행마다 캐시를 비울 수 있다.

 

 

 

참고

https://joojimin.tistory.com/71
https://frogand.tistory.com/174?category=1017075

'JPA' 카테고리의 다른 글

[JPA] 영속성 관리 - 2  (0) 2023.05.30
[JPA] 영속성 관리 - 1  (0) 2023.05.03
[Spring Boot][JPA] nativeQuery 사용 시 NullPointerException 문제  (1) 2023.03.19
Comments