본문 바로가기

Programming/Java & JSP & Spring

JPQL과 영속성 컨텍스트

스프링 웹 프로젝트를 작업할 때, 온전히 JPA만으로 작업하기에는 어려움이 따를 수 있다.

예를 들어, 테이블 중 특정 필드만이 필요한데, JPA로 모든 필드를 메모리에 올리기에는 비효율적인 문제등을 들 수 있을 것이다.

 

JPQL과 JPA를 조합해서 쓸 때 몇가지 주의할 점과 동작하는 방식에 대해 알아본다.

 

먼저 JPQL의 개념을 간단하게 다시 한번 살펴보자.

 

JPQL

JPQL은 SQL과 비슷한 문법을 가진 객체 지향 쿼리이다.

JPQL의 탄생 배경은 JPA에서 제공하는 메서드 호출만으로 섬세한 쿼리 작성이 어렵다는 문제로부터 비롯되었다.

 

JPQL로 조회한 엔티티와 영속성 컨텍스트

JPQL로 엔티티를 조회하면 해당 엔티티는 영속성 컨텍스트에서 관리된다.

하지만, 엔티티가 아니라면 영속성 컨텍스트에서 관리되지 않는다.

select m from Member m // 엔티티 조회 (관리 O) 

select o.address from Order o // (관리 X)

JPQL로 데이터베이스에서 조회한 엔티티가 이미 영속성 컨텍스트에 있다면 JPQL로 데이터베이스에서 조회한 결과를 버리고 대신에 영속성 컨텍스트에 있는 엔티티를 반환한다.

이때, 이미 영속성 컨텍스트에 존재하는지 여부를 식별자를 통해 비교한다. 즉, 나머지 값이 다르더라도 식별자만 같으면 JPQL에서 조회한 데이터를 버린다는 것이다.

만약 JPQL로 조회한 엔티티가 존재하지 않는다면 영속성 컨텍스트에 담고 반환한다.

Spring Data JPA find() vs JPQL

JPA의 find() 메소드는 엔티티를 영속성 컨텍스트에서 먼저 찾고 없으면 데이터베이스에서 찾는다.

따라서 해당 엔티티가 영속성 컨텍스트에 존재한다면 성능상 이점(2차 캐시 역할)이 있다.

 

JPQL은 항상 디비에서 먼저 찾는다. 왜냐하면 JPQL은 SQL로 변환되어 데이터베이스에 바로 날려보기 때문이다.

항상 디비에서 찾지만 디비에서 찾은 엔티티가 영속성 컨텍스트에 존재한다면 디비에서 찾은 엔티티를 버리고 영속성 컨텍스트에 있는 것을 반환값으로 넘긴다.

JPQL 특징 정리

  • JPQL은 항상 데이터베이스를 먼저 조회한다.
  • JPQL로 조회한 엔티티는 영속 상태이다.
  • 영속성 컨텍스트에 이미 존재하는 엔티티가 있으면 기존 엔티티를 반환한다.

JPQL과 Flush

JPA는 기본적으로 영속성 컨텍스트를 이용하여 쓰기 지연을 한다.

하지만 기본적인 동작은 JPQL을 만드는 순간 Flush를 하여 DB와 SYNC를 맞춘다.

 

이렇게 되면 고민해야 될 사항은 JPQL을 자주 사용하다보면 자주 COMMIT이 일어난다는 점이다.

수동으로 바꾸기 위해서는 플러시모드를 다음과 같이 변경하여 사용해야한다.

em.setFlushMode(FlushModeType.COMMIT) // default는 FlushModeType.AUTO 
em.flush() // flush 직접 호출

그렇다면 트랜잭션은 어떻게 되는걸까?

위에서 JPQL을 만나면 Flush가 된다고 했다. 내가 방금 필드를 수정한 것이 DB와 동기화가 됐다는 것이고 JPQL이 실행됐을 것이다.

그런데 만약 JPQL을 실행하고 나서 에러가 발생했다면 롤백이 안되는 것인가?

결론은 롤백이 된다. DB와 영속성 컨텍스트를 SYNC만 맞췄을 뿐, 트랜잭션이 적용되지 않는 것은 아니다.

JPQL Update를 조심한다

JPA와 함께 사용할 경우 이런 경우를 생각해보자

  1. JPA를 통해 1번 데이터 쿼리
  2. JPQL update query로 1번 데이터 수정
  3. JPA를 통해 1번 데이터 다시 쿼리 → 수정된 데이터가 확인되지 않음

위의 예를 봐서는 전혀 실수를 안할 것 같은 구조지만, 실무에서는 복잡한 로직 속에서 실수할 수 있는 포인트다.

→ 트랜잭션 Isolation Level과는 무관하다. 2번이 commit된 것은 아니기 때문에

→ 1번을 JPQL로 쿼리를 해도 결과는 같다. JPQL로 쿼리해도 영속성 컨텍스트에 담기 때문에