🙋🏻♀️JPA에 관해 공부를 하다가 문득 궁금한게 생겼다.
우선 flush와 commit에 대해 어떤 내용을 공부했는지 간단히 살펴보자 !
위의 두 상황을 놓고봤을 때 flush에 관해 궁금한점이 있다. (빨간 박스 체크)
❓ dirty checking에서는 트랜잭션 커밋 시점에 엔티티 매니저 내부에서 flush()가 호출된다고 했는데, 이 때 발생하는 flush()와 쓰기 지연 시 호출되는 flush는 같은 것이라고 봐도 될까?
flush는 1차 캐시와 DB 동기화를 위해 쿼리를 날린다고 배웠는데, 그럼 Dirty checking에서는 쿼리를 2번 날리는건가?
헷갈린다.
우선 Flush가 무엇인지 정확히 짚고 넘어가보자.
영속성 컨텍스트의 변경내용을 데이터 베이스에 반영
-> SQL이 DB로 날라가며, 즉 영속 컨텍스트의 변경 사항과 DB를 맞추는 작업을 수행한다.
데이터베이스에는 트랜잭션이라는 작업 단위가 존재하기때문에, 한 트랜잭션 내에서 아무리 flush가 발생해도 DB commit은 발생하지 않는다. DB commit은 해당 트랜잭션이 commit될 때 발생한다.
다시 돌아와서 궁금했던 사항에 대해 살펴보자.
✏️ 결론부터 말하자면, flush()는 어떤 작업들을 수행하는 것이고, flush는 그 수행 작업들 중 하나다.
1,2번 항목은 3번을 위해 필요한 과정이다.
변경 내용을 DB에 반영하는 것만 놓고 보면 3번을 flush라고 볼 수 있는것이고, 그 과정에서 필요한 것들이 1-2번이다.
정확히 말하면, flush는 변경사항을 DB와 동기화하는 것을 의미하기때문에 flush가 발생해도
-> Flush 후에도 영속성 컨텍스트는 엔티티들을 관리하고, 1차 캐시(1st-level Cache)와 같은 장점을 가지고 있다. (이후에 다른 작업이 있을 때 해당 엔티티들을 재사용할 수 있다)
단, 트랜잭션을 커밋하고 영속성 컨텍스트를 완전히 초기화하려면 트랜잭션의 범위가 종료되어야 한다.
범위가 종료되면 영속성 컨텍스트는 완전히 비워지고, 다음에 필요한 경우 새로운 트랜잭션과 함께 새로운 영속성 컨텍스트가 생성된다.
그럼, commit을 할 때 영속 컨텍스트는 어떻게될까? 🧐
트랜잭션이 커밋되는 시점에는 영속성 컨텍스트를 삭제한다.
컨텍스트 삭제란? 진짜 커밋시 삭제되는건가? 그렇다면 엔티티매니저의 close()와의 차이는 뭘까? ... 문의중!
set()으로 데이터를 변경하고, update()나 persist()를 호출하지 않지만 commit시 dirty checking을 통해 DB에 알맞게 데이터 변경이 일어난다고 배웠다.
dirty checking의 과정에서 1차 캐시의 스냅샷과 엔티티를 비교한다고 했는데, 이 엔티티는 1차 캐시에 있는 엔티티를 말한다.
Dirty Checking이 발생하기 전, set()만 했을 때 어떤 일이 발생하길래 1차 캐시의 엔티티와 비교할 수 있는걸까?
과연, set()만으로 변경값이 1차 캐시의 엔티티에 반영되는건가?
그렇다!
더 살펴보면, flush가 발생할 때, 변경감지가 일어나는데 이때 update 쿼리가 생성된다.
아직 DB에 반영되기 전인데, 코드단에서 set()으로 데이터를 변경하고 나서 find()를 사용하면 어떻게될까?
위에서 알아봤듯이, 1차 캐시에는 업데이트 된 엔티티가 관리되고있기 때문에 set으로 수정 의도한 데이터가 잘 불러와진다.
+변경 감지는 영속성 컨테스트가 관리하는 영속 상태의 엔티티에만 적용된다❗️
의문점
- 코드상에서 생성한 객체를 persist하지않고, remove()먼저 하면 어떻게 될까? 에러가 발생하나? 무슨에러가 발생하지?
- "삭제 대상의 엔티티 조회가 필요하다"의 뜻이 정확히 뭘까?
내가 remove()를 쓰면 해당 엔티티 자동으로 찾아서 1차 캐시에 등록해주는걸까? 아니면 예외처리 느낌으로 내가 직접 영속 컨텍스트에 있는지 확인해서 없으면 remove()전에 find를 하라는걸까?
위 의문점 2가지에 대해 알아보자.
이 의문점의 포인트는 사실 영속 컨텍스트이 1차캐시에 없는 엔티티를 remove()하려하면 어떤 일이 일어나는지이다.
예외가 발생하면 어떤 예외가 발생하는지도 궁금하다.
우선 H2 DB를 사용할거고, 현재 Member테이블에 있는 데이터는 아래와같다.
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Member member1 = new Member();
member1.setId(5L);
member1.setName("Ben");
System.out.println("before remove");
em.remove(member1);
System.out.println("after remove");
tx.commit();
em.close();
emf.close();
}
위와 같은 결과를 보이는데, 예외는 발생하지않지만 아예 delete쿼리가 안 날라간다.
이번에는 위 코드를 기반으로, member1의 set부분만 아래코드처럼 수정해서 테스트해봤다.
Member member1 = new Member();
member1.setId(2L);
member1.setName("HelloB");
결과를 보니, 오 IllegalArgumentException이 발생했다.
에러 내용을 보니, detach된 인스턴스를 제거하려다가 발생한것같다.
(예외 발생으로 인해, 이후에 after remove
도 뜨지않고 프로그램이 강제 종료됐다.)
-> 추측 : 코드에서 세팅한 값이 DB에 있는 데이터값과 일치하면, (id, name : 둘 다 일치해야하는지는 아직 모르겠다.) 일치하는 값이 있을텐데 영속 컨텍스트의 1차 캐시에 없으니 준영속된 엔티티라고 인식을 하나보다.
예상대로 결과도 그대로이다. (데이터가 삭제되지 않았다)
Member member1 = new Member();
member1.setId(2L);
member1.setName("HelloC");
동일한 예외가 발생했다.
DB의 데이터 역시 삭제되지않았다.
Member member1 = new Member();
member1.setId(3L);
member1.setName("HelloB");
1번 실험과 동일하게 예외는 발생하지않지만 그렇다고 delete 쿼리가 날라가지는 않는다.
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Member member1 = em.find(Member.class, 2L);
System.out.println("before remove");
em.remove(member1);
System.out.println("after remove");
tx.commit();
em.close();
emf.close();
}
정상적으로 delete 쿼리가 잘 날라가고, DB에서도 해당 데이터가 지워진것을 볼 수 있다.
💡 그리고 1,2,3,4에서의 테스트 결과를 보니, find()없이 remove()만 수행했음에도 1차 캐시에 엔티티가 없으니까 쿼리가 날라갈때 delete쿼리 전에 select가 먼저 그것도 자동으로 날라가는것을 알 수 있다.
+❗️em.remove(member1)를 호출하는 순간 member1은 영속 컨텍스트에서 제거되어 더 이상 수정할 수 없다.
부여된 id가 회수되지 않고, 다음 id를 사용하게 된다.
이 둘의 큰 차이를 알아보자.
JPA에서 엔티티 객체가 영속성 컨텍스트에 속하지 않은 상태이다. 즉, JPA가 엔티티를 관리하지 않는 상태이다.
비영속 상태의 엔티티는 데이터베이스와는 아무런 관련이 없으며, 단순히 Java 객체로만 존재한다. 예를 들어, new 키워드를 사용하여 엔티티 객체를 생성한 후, 데이터베이스에 저장하지 않은 상태가 비영속 상태이다!
JPA에서 엔티티 객체가 이전에 영속성 컨텍스트에 속했지만, 더 이상 영속성 컨텍스트에 속하지 않는 상태를 말한다.
준영속 상태의 엔티티는 영속성 컨텍스트로부터 분리되어, 영속성 컨텍스트의 관리를 받지 않게 된다.
(즉, 과거에 id값을 부여받았었던 적이 있는것)
일반적으로 영속성 컨텍스트에서 관리되던 엔티티를 영속성 컨텍스트를 종료하거나, EntityManager의 detach() 메서드를 호출하여 분리시킬 수 있다.
준영속 상태의 엔티티는 데이터베이스와는 동기화되지 않으며, 영속성 컨텍스트에서의 변경을 추적하거나 자동으로 업데이트하지 않는다. 따라서, 준영속 상태의 엔티티를 변경하더라도 해당 변경은 데이터베이스에 영향을 주지 않는다.
clear 메서드는 영속성 컨텍스트의 모든 엔티티를 초기화하는 역할을 한다.
이후에 다시 엔티티를 사용할 때는 데이터베이스로부터 다시 로딩하게 된다. clear 메서드는 트랜잭션 범위 내에서 사용할 수 있다.
close 메서드는 영속성 컨텍스트를 종료하고 관련 리소스를 해제하는 역할을 한다.
영속성 컨텍스트가 닫힌 후에는 엔티티를 로딩하거나 변경할 수 없다.
따라서,
반면에,
clear() 메서드 호출 후에는 영속성 컨텍스트에 있는 엔티티들은 모두 준영속 상태가 되므로, 변경 사항이 있더라도 데이터베이스에 반영되지 않는다.
따라서, ⚡️ clear() 메서드를 호출하면 해당 트랜잭션 내에서 이전에 변경한 내용이 있다면 모두 롤백되는 효과가 있다.
영속성 컨텍스트를 초기화하기 위해 clear() 메서드를 호출하는 경우는 일반적으로 큰 트랜잭션이나 메모리 사용을 최적화하기 위해 사용될 수 있다.
주의할 점은 clear() 메서드 호출 후에는 이전에 영속성 컨텍스트에서 로드한 엔티티들에 대한 변경 내용이 유실되므로, 주의해서 사용해야 한다!