코딩스토리

영속성 컨텍스트 본문

Spring/JPA

영속성 컨텍스트

kimtaehyun98 2022. 3. 14. 00:36

# 해당 포스팅은 인프런 김영한 강사님의 "자바 ORM 표준 JPA 프로그래밍 - 기본편" 강의 및 교재를 참고하여 작성한 글입니다. 

 

영속성 컨텍스트

영속성 컨텍스트란 "엔티티를 영구적으로 저장하는 환경"이다.

이는 논리적 개념으로 실질적으로 구현되어 있지는 않다. 

따라서 JPA에서는 Entity Manager를 영속성 컨텍스트로 이해하면 된다.

(정확한 표현은 Entity Manager안에 1대 1로 영속성 컨텍스트가 있는 것이다.)

 

상태

먼저 비영속 상태는 말 그대로 영속되지 않은 상태이다.

따라서 JPA가 관리하지 않는 상태를 말한다. 

예를 들어 자바단에서 new Member()를 통해 새로운 객체를 생성하고 아무 작업도 하지 않는다면 이를 비영속 상태라 할 수 있다.

 

다음으로 영속 상태는 영속성 컨텍스트에 관리되고 있는 상태이다.

보통 em.persist() 함수를 사용하여 영속성 컨텍스트에 등록한다.

주의해야 될 점은 영속 상태, 즉 영속성 컨텍스트에서 관리되고 있다고 해서 반드시 DB에 저장되어 있는 것은 아니다.

(이와 관련된 부분은 아래에서 다룰 것이다.)

 

영속성 컨텍스트의 장점

그럼 JPA에서는 왜 이런 논리적 개념을 사용할까?

 

1) 캐시 가능

JPA의 Entity Manager는 1차 캐시를 가지고 있다.

사실상 이 1차 캐시를 영속성 컨텍스트라 봐도 되는데 이곳에는 트랜잭션 내에서 사용되는 데이터들이 담겨있다.

 

잠깐 컴퓨터 구조 시간에 배웠던 캐시의 장점을 생각해보면

메모리의 계층화 후 메인 메모리까지의 접근 시간을 줄이고, 속도의 장점을 얻기 위해 캐시를 사용한다고 배웠다.

 

JPA에서도 마찬가지이다.

DB와의 커넥션을 최소화해주는 것에 캐시가 사용된다.

 

예를 들어 em.find() 함수를 통해 어떠한 데이터를 찾아오려고 할 때 JPA는 바로 DB에 쿼리를 날리는 것이 아니라 Entity Manager 내부의 1차 캐시부터 찾아본다.

 

그 후 없다면 그때서야 DB에 쿼리를 날려 받아오고, 받아온 데이터를 캐시에 담아놓는다.

 

2) 영속 Entity 동일성 보장

JPA의 주목적은 DB의 데이터들을 마치 JAVA Collection에서 객체를 꺼냈을 때처럼 사용하는 것이다.

 

아래와 같은 코드를 보자.

Member memberA = em.find(Member.class, 1);
Member memberB = em.find(Member.class, 1);

만약 memberA == memberB를 출력한다면 "true"를 출력한다.

또한 memberA.setName("태현")을 통해 이름을 변경한다면 memberB의 이름도 변경된다.

 

JPA의 주목적처럼 equal 연산도 객체를 참조하는 것처럼 작동하는 것이다.

이러한 부분이 가능한 이유는 1차 캐시의 "반복 가능한 읽기" 특성을 이용하기 때문이다.

 

3)  Entity 등록 시 쓰기 지연

 

JPA에서는 어떠한 정보를 DB에 등록하려면 em.persist(member)처럼 persist 함수를 사용한다.

 

이때 insert 쿼리는 persist() 함수를 사용할 때가 아닌 TX의 commit 이후에 실행된다.

 

아래의 코드는 새로운 멤버를 추가하는 코드이다.

Member member = new Member();
member.setName("태현");
member.setId(2L);
			
em.persist(member);
System.out.println("persist 이후");
			
System.out.println("commit 이전");
tx.commit();

 해당 코드를 실행한 결과는 다음과 같다.

즉 commit 이후에 insert 쿼리가 날아가는 것을 확인할 수 있다.

 

이는 1차 캐시와 "쓰기 지연 SQL 저장소"라는 개념으로 설명할 수 있다.

 

먼저 persist() 수행 시 당연히 1차 캐시에 데이터가 저장된다.

그리고 JPA가 생성한 Insert 쿼리는 쓰기 지연 SQL 저장소에 쌓이게 된다.

 

최종적으로 commit 수행 시 지금까지 쌓였던 SQL들이 한 번에 날아간다.

이러한 방식의 장점은 Insert문들을 한 번에 보낼 수 있다는 것이다.

즉 DB와의 Connection을 계속해서 유지할 필요가 없다는 것이다. 

 

4) Entity 수정, 변경 감지

다음과 같은 코드를 살펴보자.

Member findMember = em.find(Member.class, 2L);
findMember.setName("Taehyun");
System.out.println("이름 수정 직후");
			
System.out.println("커밋 직전");
tx.commit();

먼저 멤버를 찾아온 뒤 찾아온 객체의 이름을 변경했다.

 

지금까지 쌓아온 상식으로는 아무 일도 없어야 한다.

하지만 실제로는 아래와 같은 결과가 나타난다.

커밋 이후 update 쿼리가 날아간다.

 

이는 JPA의 변경 감지 기능 때문이다.

 

JPA는 1차 캐시에 어떠한 데이터가 들어올 때 SNAPSHOT을 생성한다.

이후 어떠한 작업이 수행되었을 시 SNAPSHOT과 비교를 통해 변경이 되었는지 확인한다.

이 부분이 변경 감지 기능이다.

 

이렇게 변경이 확인되면 위에서 언급했던 "쓰기 지연 SQL 저장소"에 update 쿼리를 쌓아놓고 커밋 시 전부 보낸다.

 

5) 플러쉬(Flush)

 

지금까지 공부한 부분들을 보면 결국 Commit 시점에 쿼리들이 날아간다.

 

그럼 만약 커밋 전에 쿼리를 날리고 싶다면 어떻게 해야 할까?

 

그럴 때 Flush 함수를 사용하면 된다.

Member findMember = em.find(Member.class, 2L);
findMember.setName("태현");
System.out.println("이름 수정 직후");
em.flush();
			
System.out.println("커밋 직전");
tx.commit();

위의 코드에서 em.flush() 함수만 추가하였다.

수정과 커밋 사이에 쿼리가 실행되었음을 확인할 수 있다.

 

 

이렇게 JPA의 가장 기초적인 개념인 영속성 컨텍스트에 대해 알아보았다.

 

작년에 SQL을 하나도 모른 채로 JPA를 공부할 때에는 이해가 잘 안 되었는데

SQL을 공부하고 난 뒤 내용을 보니 그저 신기할 뿐이다.. 

 

'Spring > JPA' 카테고리의 다른 글

값 타입  (0) 2022.05.09
프록시를 사용한 즉시 로딩, 지연 로딩  (0) 2022.04.28
Comments