쟈미로그
[JPA] @Modifying과 영속성 컨텍스트 본문
@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 |