영속성 컨텍스트와 전화 관계 없는 객체 상태를 의미한다
영속성 컨텍스트가 JPA에서 DB와 통신하는 중간 다리 역할을 하기 때문에 결국 객체가 영속 컨텍스트에 의해 관리를 받고 있어야만 DB와 통신하며 데이터에 대한 처리가 가능해지는 것이다.
그렇다면 영속성 컨텍스트와 아예 관련이 없는 비영속 상태의 객체는 어떤 의미를 지닐까?
그냥 "순수한 객체 상태", 즉 DB와 연동할 수 없는 단순한 POJO 객체에 불과하다.
이런 비영속 상태의 객체를 persist() 명령을 통해 영속성 컨텍스트에 등록시켜 관리 상태로 바꿔줘야지만 객체를 통해 DBD 데이터를 처리할 수 있게 되는 것이다.
영속성 컨텍스트에 저장된 Entity 상태를 말한다.
아까 비영속 상태는 영속성 컨텍스트와 아무 관계가 없었지만, 위 사진을 보면 Member라는 Entity가 영속성 컨텍스트 내부에 들어왔음을 알 수 있다.
즉, "영속 상태"란 Entity가 영속성 컨텍스트(1차 캐시)에 저장되어 관리받고 있는 상태를 말한다.
영속 상태는 영속성 컨텍스트에 의해 관리되며, 자연스럽게 DB 데이터를 처리할 수 있는 Entity가 될 수 있다.
즉, 원래 POJO 객체였던 비영속 객체를 영속성 컨텍스트에 등록시킴으로써 DB와 통신하며 내가 원하는 데이터 처리를 DB에 적용시킬 수 있는 객체가 되는 것이다.
삭제된 상태의 객체를 의미한다.
영속성 컨텍스트에 저장되어 있는 객체가 삭제됨과 동시에 객체가 가지고 있던 데이터 또한 DB에서 삭제된 상태를 의미한다.
영속성 컨텍스트에 저장되었다가 분리된 상태의 Entity를 말한다.
필자는 준영속 상태가 가장 헷갈리는 개념임과 동시에 가장 중요도가 낮은 객체 상태라고 생각한다.
먼저 준영속 상태란 Entity가 원래 영속성 컨텍스트에 의해 관리 받고 있다가 영속성 컨텍스트에 의해 관리받지 않는 상태로 바뀌는 경우의 Entity 상태를 의미한다. 이 때 쓰기 지연 SQL 저장소에 저장된 준영속 상태로 변환된 Entity와 관련된 Query문들도 동시에 제거된다.
준영속 상태와 가장 헷갈리는 개념은 비영속 상태이다. 과거가 어찌되었든 비영속 상태와 준영속 상태는 모두 현재 영속성 컨텍스트에 의해 관리받고 있지 않다는 공통점이 존재하기 때문이다.
하지만 이 "과거"가 생각보다 두 상태의 중요한 차이점을 만들어낸다.
준영속 상태와 비영속 상태의 가장 큰 차이점은 과거에 일어났던 "영속성 컨텍스트의 방문 여부"이다.
영속성 컨텍스트에 대해 공부한 것을 다시 생각해보자. Entity가 영속성 컨텍스트에 등록되었을 때 필수적으로 필요한 Data가 무엇일까?
바로 식별자(@Id) 값이다. 현재 상태가 어쨌든 준영속 상태의 Entity는 과거에는 영속성 컨텍스트에 등록되었고, 영속성 컨텍스트에 등록된 적이 있다는 의미는 식별자를 통해 해당 Entity를 구분할 수 있었었다는 의미를 가진다.
하지만 비영속 상태는 애초에 영속성 컨텍스트에 등록된 적이 없다. 즉 식별자(@Id) 값을 통해 Entity를 구분할 필요성이 없었던 것이며 식별자 값이 null이더라도 아무런 문제가 없는 객체인 것이다.
즉, 준영속 상태는 과거에 영속성 컨텍스트에 방문했었기 때문에 무조건 식별자 값을 가지고 있으며 준영속 상태의 객체끼리라도 식별자 값을 통해 Entity 일치 여부를 판단 할 수 있게 되는 것이다.
그런데 여기서 또 헷갈리는 점이 있는데, 준영속 상태의 객체는 사실 식별자를 통해 Entity 구분이 불가능하다는 것이다.
아니 위에서는 식별자 값을 통해 Entity 일치 여부를 판단 할 수 있었다고 했는데, 바로 아래에는 불가능하다는 것은 무슨 말이냐?
준영속 상태는 현재 영속성 컨텍스트에 의해 관리받고 있지 않는 상태이다. 즉 식별자 값이 Unique하지 않아도 에러가 발생하지 않는다는 의미이다.
만약 영속 상태 객체 2개가 존재하는데 PK 값이 동일하다면 DB에서는 에러를 발생 시킬 것이다. PK값은 모든 Row Data를 구분할 수 있어야 하는데 만약 PK 값이 동일해진다면 그 PK 값으로 뽑고 싶은 데이터가 무엇인지 모호해 질 것이기 때문이다. 영속 상태의 객체는 이 특징을 그대로 가지고 있어야 하고, 실제로 식별자 값이 다르면 1차 캐시에서 Entity를 뽑아낼 수 없기 때문에 에러가 발생할 것이다.
하지만 준영속 상태는 현재 영속성 컨텍스트에 의해 관리받고 있지 않는 상태이다. 따라서 과거 식별자 역할을 했던 멤버 변수의 값을 변경시켜 다른 식별자 값과 일치한다고 해도 현재 DB와 아무 관계 없는 POJO 객체에 불과하므로 별다른 문제가 발생하지 않는 것이다.
즉, 준영속 상태로 바뀐 직후에는 식별자 값을 통해 Entity를 구분할 수 있겠지만 시간이 지나면 식별자 값이 개발자나 User에 의해 변동될 수도 있고, 이는 아무런 에러를 발생시키지 않기 때문에 식별자 값으로 Entity 일치 여부를 판단하기엔 너무 높은 위험성이 존재하는 것이다.
준영속 상태의 Entity도 "변경 감지 기능(Dirty Checking)"이나 "병합(Merging)"으로 Entity를 통해 DB와 통신할 수 있다고는 한다. 하지만 자세한 과정을 살펴 보면 결국 꼼수로 준영속 상태의 Entity를 임시로 영속 상태로 바꾼 다음에 DB와 통신한 뒤 다시 준영속 상태로 전환하는 것에 불과하다는 느낌을 받았다.
이렇게 처리할 바에는 맘 편하게 객체를 다시 영속 상태로 변환하는 것이 더 좋은 방식이라고 판단되어 따로 설명하지는 않겠다.
준영속 상태의 가장 큰 특징은 지연 로딩이 불가능하다는 것이다.
나중에 JPA의 지연 로딩 방법에 대해 자세히 다루겠지만, 간단히 설명하자면 JPA는 지연 로딩할 데이터를 실제 객체 대신 Proxy 객체로 Loading한 이후 Proxy 객체를 사용해야 할 때 영속성 컨텍스트를 통해 나머지 데이터를 가지고 오는 방식을 활용한다. 여기서 주의 깊게 봐야 할 것은 "영속성 컨텍스트를 통해"라는 문구이다.
결국 지연 로딩이라는 것은 영속성 컨텍스트의 도움이 있어야 하는 것이며, 영속성 컨텍스트의 관리를 받지 못하는 준영속 상태의 객체는 Proxy 객체를 활용할 수 없어 지연 로딩이 불가능하게 되는 것이다.
설명하기 앞서 개발자가 직접 객체를 준영속 상태를 만드는 일은 매우 드물다. (개인적인 생각이지만 개발자가 직접 준영속 상태로 만드는 것에 대한 이점이 없기 때문인 것 같다)
따라서 개념적으로만 이해하고 넘어가도 충분할 것 같다.
특정 Entity만 준영속 상태로 전환하는 것으로써 내가 지정한 Entity만 영속성 컨텍스트에서 관리받지 못하게 하는 것이다.
A, B, C라는 영속 상태의 Entity가 존재 했을 때 detach(A)를 수행하면 A Entity만 영속성 컨텍스트에 의해 관리받지 못하며 준영속 상태로 변하는 것이다.
B, C Entity는 여전히 영속 상태이며 A Entity 또한 객체가 메모리 상에서 아예 사라진 것은 아니고 단순히 영속성 컨텍스트에 의해 관리받지 못하는 상태일 뿐이다.
영속성 컨텍스트를 완전 초기화하는 방식으로 영속성 컨텍스트에 존재하는 모든 Entity를 영속성 컨텍스트 관리받지 못하도록 내쫓는 방식이다.
영속 상태인 A, B, C Entity가 존재할 경우 영속성 컨텍스트를 초기화 시켜 A, B, C 모두 준영속 상태로 만들어버리는 메서드이다. 존재하는 모든 Entity가 준영속 상태가 되었으므로 1차 캐시와 쓰기 지연 SQL 저장소에 저장된 데이터는 하나도 없을 것이다.
영속성 컨텍스트를 완전히 종료시키는 것이다.
clear()나 detach()는 영속성 컨텍스트가 존재는 하지만 그 안에 존재하는 객체를 내쫓음으로써 객체 관리를 더 이상 수행하지 않게 한 것이였다. 하지만 close()는 그냥 영속성 컨텍스트가 없어진 것이다.
존재하지 않는 객체가 존재하는 Entity를 관리할 수 없기 때문에 기존에 영속성 컨텍스트에 등록된 객체들은 더 이상 관리 받지 못하는 상태인 준영속 상태로 변환된다.
먼저 새로운 POJO 객체가 만들어진다(new 상태)
이후 persist()를 통해 해당 객체를 영속성 컨텍스트에 등록시킴으로써 영속 상태(managed)로 만든다.
이 영속 상태의 객체는 DB와 통신할 수 있어서 DB 데이터를 처리하는 중간 다리 역할을 수행할 수 있다.
detach(), clear(), close() 메서드를 통해 영속 상태의 객체를 영속 컨텍스트의 관리하에서 내쫓을 경우 객체는 준영속 상태(detached)로 변환된다. 또한 merge()를 통해 다시 영속 상태로 등록할 수 있는데, 이 merge()라는 것이 위에서 간단히 언급했던 "병합"이다.
하지만 병합이라는 것은 detached 객체에 저장된 데이터를 DB에서 찾아 DB에서 찾은 데이터와 준영속 상태의 객체의 차이점을 비교하여 변경점을 적용시키는 개념인데, 이 때 영속 상태가 되는 것은 "DB에서 검색한 데이터"이다. 즉, 준영속 상태의 객체가 다시 영속 상태로 변환되는 것과는 조금 거리가 있다고 생각한다.
영속 상태의 객체는 flush() 메서드를 통해 변경점을 DB에 적용시킬 수도 있고 INSERT Query로써 객체를 저장시킬 수도 있을 것이다.
JPQL 같은 DB와 직접 통신하여 얻은 객체나 find() 메서드를 통해 DB에서 가지고 온 데이터는 바로 영속 상태(Managed)의 객체에 데이터를 저장하기 때문에 New 상태를 거치지 않고 바로 Managed 상태가 됨을 볼 수 있다.
또한 remove() 메서드를 통해 영속 상태의 Entity를 삭제 상태의 Entity로 만들 수 있으며, persist()를 통해 영속성 컨텍스트에 제거했던 객체를 다시 영속 상태로 바꿀 수도 있다.
삭제 상태의 Entity에 flush가 적용되면 해당 Entity의 스냅샷은 존재하는 상태에서 현재 Entity가 존재하지 않는 상태가 되기 때문에 DB는 영속성 컨텍스트의 상태와 연동하기 위해 해당 Entity가 저장한 Row Data를 삭제하게 되는 것이다.