JPA의 기본 동작부터 알아보자!
JPA에서 사용하는 @Query 애노테이션은 기본적으로 데이터를 **조회(SELECT)**할 때 사용된다. 그래서 UPDATE나 DELETE처럼 데이터를 변경할 때는 JPA에게 추가적으로 알려줘야 한다.
이때 JPA가 헷갈리지 않도록 명확히 알려주는 방법이 바로 @Modifying 애노테이션이다! 🚨
@Modifying이 꼭 필요한 이유 3가지
① 변경 쿼리임을 명확히 알려주기
• JPA는 기본적으로 모든 @Query를 SELECT 쿼리라고 생각한다.
• 그런데 UPDATE나 DELETE 쿼리는 데이터베이스에서 실제로 데이터를 변경하는 쿼리라서, JPA의 1차 캐시(영속성 컨텍스트)와 충돌이 생기며 이로인한 동기화 과정에서 동기화가 제대로 되지 않아 데이터의 정합성이 깨지는 문제가 발생한다.
• 그래서 JPA에게 “이 쿼리는 SELECT가 아니고, 데이터를 변경하는 쿼리야!“라고 명확하게 알려줘야 하는것!!
② 자동으로 트랜잭션 처리가 되지 않기 때문
• 조회(SELECT)는 데이터를 변경하지 않아서 트랜잭션 관리가 필수가 아니다.
• 하지만 UPDATE나 DELETE는 데이터가 변경되기 때문에, 문제가 생기면 다시 되돌릴 수 있도록 트랜잭션 관리(@Transactional)가 꼭 필요하다.
• 그래서 @Modifying을 붙여주면 JPA가 해당 쿼리를 트랜잭션 내에서 실행할 수 있게 인식하게 된다.
③ 영속성 컨텍스트(1차 캐시)의 자동 초기화(clear)
• JPA는 성능을 위해 엔티티를 메모리(영속성 컨텍스트)에 저장한다.
• 그런데 JPQL로 UPDATE나 DELETE를 직접 실행하면 데이터베이스만 변경되고, JPA의 영속성 컨텍스트에는 이전 데이터가 남아있게 되며, 조회 시점에 데이터가 맞지 않는 문제가 발생한다.
• 이 문제를 방지하기 위해서 @Modifying(clearAutomatically = true) 옵션을 주면, 쿼리가 실행된 후 JPA가 영속성 컨텍스트를 자동으로 초기화해서 항상 최신 데이터 상태로 유지할 수 있다!
결론적으로 정리하면!
JPA에서 @Query 애노테이션만 사용해서 UPDATE 또는 DELETE 쿼리를 실행하면, DB에는 데이터가 반영되지만, JPA의 메모리(영속성 컨텍스트)에는 반영되지 않는다....
그래서 다음에 SELECT 쿼리를 실행하면 메모리에 있던 옛날 데이터를 읽게 되는 문제가 발생할 수 있다.
이런 문제를 해결하기 위해서 반드시 @Modifying 애노테이션을 사용해서 “이건 SELECT가 아니야!” 라고 알려주고, 필요하다면 clearAutomatically = true 옵션까지 추가해서 JPA의 캐시를 초기화시켜 줘야한다!
이렇게 하면 쿼리를 실행한 후에도 데이터베이스와 영속성 컨텍스트의 데이터가 항상 일치해서, 데이터 불일치 문제가 해결된다.
결론
SELECT (조회) | ❌ 필요 없음 | @Query는 기본적으로 조회(SELECT)를 위한 애노테이션! |
---|---|---|
UPDATE | ✅ 필요함 | JPA는 UPDATE 쿼리를 자동 인식 못함! 명시 필요! |
DELETE | ✅ 필요함 | DELETE도 JPA가 자동 인식 못하므로 명시 필수! |
INSERT | ❌ 필요 없음 | save()를 쓰면 JPA가 자동으로 처리해줌! |
트랜잭션 처리 (@Transactional 필요 여부) | UPDATE, DELETE는 필수 | 데이터 변경은 트랜잭션으로 관리하지 않으면 문제 생김! 🔥 |
'프로젝트 이슈 및 몰랐던점 정리 > CommunityAPI' 카테고리의 다른 글
[트러블 슈팅] ⚠️ Spring Boot LocalDateTime 변환 에러 해결법 (@JsonFormat vs @DateTimeFormat) (0) | 2025.03.09 |
---|---|
[트러블 슈팅] ⚠️ @RequestBody로 MultipartFile을 받을 수 없는 이유와 해결법(@RequestPart) (0) | 2025.03.09 |
[트러블 슈팅] Spring HATEOAS - 단일 리소스에 대한 API 엔드 포인트 제공 (0) | 2024.11.28 |
[트러블 슈팅] JPA - LazyInitializationException과 N+1 (1) | 2024.11.27 |
[트러블 슈팅] BindingResult 반환시 발생한 객체 직렬화 문제 (0) | 2024.11.26 |