1 분 소요

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를 한 로직에서 같이 사용할 필요 없으면 하나만 사용하는 것도 방법이다.

카테고리:

업데이트:

댓글남기기