Programming-[Backend]/JPA

[JPA기본] 3. 영속성 컨텍스트 - JPA 내부 동작 방식

컴퓨터 탐험가 찰리 2021. 9. 26. 16:27
728x90
반응형

 

앞서 객체와 RDB의 상호소통의 가장 기초적인 예제를 살펴보면서 객체와 RDB간 매핑이 중요하다는 것을 간단히 체험해봤다. 이 부분에 대해서는 뒤에서 더 깊게 다루게 된다. 이런 ORM의 구조 외에 가장 중요한 점이 있다면 영속성 컨텍스트 개념이다. JPA가 동작하는 기본 방식으로 이 부분을 잘 이해해야 문제가 생겼을 때 대응을 잘할 수 있다.

 

 


 

1. 영속성 컨텍스트와 엔티티 생명주기

 

영속성 컨텍스트

영속성 컨텍스트는 엔티티를 영구 저장하는 환경이다. 이전 글에서 살펴본대로 어떤 객체(엔티티)의 값을 저장하기 위해서 persist() 메서드를 쓰는데, 이 경우 객체가 바로 DB로 INSERT 되는 것이 아니라 영속성 컨텍스트에 등록되는 것이다.

 

아래 도식에서처럼 JPA를 이용하여 어떤 객체를 조회하거나 persist 하는 경우, DB에 바로 접근하는 것이 아니라 중간 매개체인 영속성 컨텍스트에 접근하게 된다. 그중 1차 캐시에 접근하여 해당 객체에 대한 정보가 있는지를 검색하는데, 만약 1차 캐시에 객체 정보가 없다면 DB에 쿼리를 날려서 값을 가져오게 된다. 그리고는 1차 캐시에 저장하고 요청한 값을 반환한다.

 

이런 과정에 따라 엔티티의 상태를 구분하는데, 이를 엔티티 생명주기라고 하고 다음과 같이 정의한다.


 

엔티티 생명주기

영속성 컨텍스트와 엔티티는 다음 4가지 생명주기 개념을 바탕으로 동작한다.

 

1. 영속(managed)

2. 비영속(new/transient)

3. 준영속(detached)

4. 삭제(removed)

 

 

영속 상태는 1차 캐시에 엔티티가 등록되어 관리되는 상태를 의미한다. 이 상태에서 같은 객체를 다시 한번 조회하게 되면, 굳이 DB에 select문을 보내서 값을 찾아오는 것이 아니라 1차 캐시상에서 값을 바로 가져온다. 그렇기 때문에 성능상의 이점도 있고, 마치 JAVA에서의 컬렉션에서 값을 꺼내오는 것처럼 엔티티의 동일성을 보장받게 된다.

 

비영속 상태는 엔티티가 영속성 컨텍스트에 의해 관리되지 않는 상태를 의미한다. 처음 member 객체를 생성하고 persist 하는 경우에는 persist 전의 객체는 영속성 컨텍스트에 올라가지 않은 상태이므로 비영속 상태이다.

 

준영속 상태는 영속성 컨텍스트에서 해당 객체가 떨어져 나온 경우를 의미한다. 아래와 같은 메서드를 통해 객체를 준영속 상태로 만들 수 있는데, 이렇게 되면 객체의 필드값을 변경하더라도 DB에 반영되지 않는다. 뒤에서 다룰 변경 감지(dirty checking)이 되지 않는다.

 

삭제 상태는 영속성 컨텍스트 뿐아니라 실제 DB에서 해당 객체에 대한 정보를 삭제한다는 의미가 된다.

 


변경 감지와 엔티티 수정

 

영속 상태로 관리되는, em.find나 em.persist 처리된 객체는 1차 캐시에 등록된다. 그리고 등록될 당시의 필드값 등의 정보가 위 도식에서 Snapshot이라는 상태로 저장된다. JPA는 어떤 객체의 필드값이 변경되면 이 Snapshot일때의 상태와 비교하여 1차 캐시와 DB에 해당 객체의 정보를 업데이트 해준다.

 

Member memberA = em.find(Member.class, 10L);

memberA.setUsername("modified name!");

 

위 코드와 같이 영속 상태의 객체의 필드값을 set 해주는 것만으로도 따로 update나 save가 필요없다. 나중에 쿼리가 나갈 때 자동으로 변경사항이 반영되어 DB에 update 된다.

 

 


 

쓰기 지연 SQL 저장소

엔티티를 조회, 저장, 수정 등의 작업이 일어나면 바로 SQL문이 DB에 적용되는 것이 아니라 쓰기 지연 SQL 저장소라는 곳에 SQL문이 쌓인다. JPA는 이렇게 생성된 SQL문들을 플러시(flush)가 일어나는 경우 DB에 쿼리를 보내게 된다. 아래 코드에서 확인할 수 있듯이, 주석문 사이에 em.persist가 있음에도 불구하고 SQL문은 그 이후에 적용되었다. 플러시를 호출하는 transaction.commit()에서 SQL문이 나가게 된 것이다. 다음 섹션에서 플러시에 대해 알아본다.

 

 

 

Batch Size

 

쓰기 지연 SQL 저장소에서 한번에 나가는 SQL들의 최대 갯수를 지정해줄 수 있다. persistence.xml 파일에 아래 옵션을 작성해주면 된다.

 

<property name="hibernate.jdbc.batch_size" value="10"/>

 


 

 

2. 플러시

 

플러시를 하면 영속성 컨텍스트의 정보, 정확하게는 쓰기 지연 SQL 저장소의 SQL이 DB에 반영된다. 플러시를 요청하는 방법은 다음 3가지가 있다.

 

1. 직접 호출 : em.flush()

2. 트랜잭션 커밋 : tx.commit() - 플러시가 자동 호출된다.

3. JPQL 쿼리 실행 - JPQL 쿼리 전 플러시가 자동 호출된다.

 

1, 2번에 대해서는 충분히 알아봤으니, 3번의 경우를 알아보자.

 

1
2
3
4
5
6
7
8
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
 
//DB에 아무 정보도 없는 경우 
List<Member> queryResult = em.createQuery("SELECT m FROM Member m", Member.class)
                           .getResultList();
 
cs

 

JPQL의 쿼리를 호출할때, DB의 Member table에 아무런 정보가 없는 경우 값을 불러올 수 없는 상태가 된다. 그렇기 때문에 해당 JPQL 쿼리 전, persist문들을 자동으로 플러시 처리하여 정보를 쌓은 채로 쿼리를 실행한다.

 

em.persist와 em.createQuery의 SQL문 모았다가 tx.commit() 때 한꺼번에 나가는 것이 아니라, insert 후 select 쿼리문이 나간 것을 확인할 수 있다.

 

※플러시 모드 옵션

다음 명령어로 플러시 시점을 변경할 수 있다. 그러나 기본값인 AUTO로 사용하는 것이 좋다.

 

em.setFlushMode(FlushModeType.AUTO) : 커밋이나 쿼리 실행 시 플러시(기본)

em.setFlushMode(FlushModeType.COMMIT) : 커밋할때만 플러시

 


 

 

3. 준영속 상태

 

준영속 상태는 영속성 컨텍스트에서 엔티티가 떨어져 나와서 관리되지 않는 상태를 의미한다고 배웠다. 이 상태가 된 객체에 setUsername()등으로 상태값을 변경하더라도, 1차 캐시에 의해 관리되는 상태가 아니므로 변경 감지가 작동하지 않아서 해당 객체와 관련한 SQL문이 생성되거나 DB에 반영되지 않는다.

 

엔티티를 준영속 상태로 만드는 방법은 다음과 같다.

 

em.detach(entity) : 특정 entity만 준영속 상태로 변경

em.clear() : 영속성 컨텍스트를 완전히 초기화

em.close() : 영속성 컨텍스트를 종료

 


 

참조

 

 

1. 인프런_자바 ORM 표준 JPA 프로그래밍 - 기본편_김영한 님 강의

https://www.inflearn.com/course/ORM-JPA-Basic/lecture/21735?tab=curriculum

728x90
반응형