JPA에서 변경 감지로 업데이트를 하고 있다면 주의할 점
JPA를 쓰고 있다면 트랜잭션 안에서 엔티티를 불러온 뒤 필드를 수정하기만 해도 변경 감지로 업데이트 쿼리가 날아가게 된다.
transaction {
val user = userRepository.findById(userId)
user.name = "새 이름"
}
// UPDATE users SET name = "새 이름" where user_id = 1;
하지만 업데이트 해야하는 대상이 너무 많고 엔티티에도 필드가 너무 많다면 모두 불러와서 업데이트하기엔 메모리가 부족할 수 있다.
심지어 쿼리가 엔티티별로 나가기 때문에 트랜잭션이 끝나면 너무 많은 쿼리가 발생할 수 있다.
서버 입장에선 메모리도 부담되고, DB에도 쿼리가 너무 많아지니 부하가 발생할 수 있다.
transaction {
val users = userRepository.findAll();
users.forEach { user -> user.name = "새 이름" }
}
// UPDATE users SET name = "새 이름" where user_id = 1;
// UPDATE users SET name = "새 이름" where user_id = 2;
// ...
위 경우는 굳이 여러 쿼리로 날릴 필요없이 아래 쿼리 하나만 날려주면 모두 업데이트된다.
굳이 서버로 엔티티를 모두 불러올 필요도 없어서 메모리 부담도 안된다.
UPDATE users SET name = "새 이름";
그렇다고 update 쿼리만 날리면 편해서 좋다는 말을 하려는 건 아니다. JPA의 장점도 크다!
위의 이유로 update 쿼리도 날려야 할 때가 있고, JPA도 써야할 때가 있다. 혼용했을 때 주의할 점을 알아보자.
엔티티를 조회한 뒤 update 쿼리를 날린 경우
조회한 엔티티의 이름은 AAA
고 update 쿼리에서는 BBB
로 변경한다고 하자.
그리고 엔티티 이름을 기존과 같은 AAA
로 수정한다고 하자.
transaction {
val user = userRepository.findById(userId) // name = "AAA"
userRepository.updateNameById(1L, "BBB") // update 쿼리 발생
user.name = "AAA" // 기존에도 같은 이름이어서 트랜잭션이 끝나도 update 쿼리가 발생하지 않는다!
}
- user.name을
AAA
로 수정했지만 이름이 변경된건 아니니까 영속성 컨텍스트는 변경 감지로 업데이트 쿼리를 날리지 않는다. - 그니까 update 쿼리에서
BBB
로 변경한걸 user 엔티티의 name까지BBB
로 초기화해주지 않는다.
위 로직이 매우 간단해서 잘 안보일 수 있지만,
원래 의도는 BBB
로 업데이트했지만 결국 AAA
로 수정했으니까 user.name은 AAA
로 유지하길 원했을 수 있다.
하지만 변경 감지가 이뤄지지 않기 때문에 update 쿼리만 발생하고, 이름은 BBB
로 저장된다.
의도와 다르게 충분히 결과가 틀어질 수 있고 로직이 복잡할 수록 디버깅하는게 어려워지므로 버그 찾기가 힘들어진다.
update 쿼리를 사용하는 것도, JPA를 사용하는 것도 좋지만 혼용했을 때 이런 케이스는 없는지 미리 주의해야한다.
해결 방법은 상황에 따라 달리지겠지만 서로 업데이트하는 대상이 겹치지 않게 하던가, 영속성 컨텍스트를 초기화해줘도 된다.
꼭 쿼리, JPA를 한 로직에서 같이 사용할 필요 없으면 하나만 사용하는 것도 방법이다.
댓글남기기