JPA

영속성 컨텍스트의 이점 - 동일성 비교와 트랜잭션 쓰기 지연

방구석 대학생 2020. 9. 9. 21:48

"인프런 - 자바 ORM 표준 JPA 프로그래밍 강의를 듣고 작성한 글 입니다."

www.inflearn.com/course/ORM-JPA-Basic#

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다. 초급 웹 개발 프로그

www.inflearn.com

2. 자바 컬렉션과 같은 동일성(identify) 비교 보장( == 비교)

아래의 코드를 확인해보자.

Member findMemeber1 = em.find(Member.class, 101L);
Member findMemeber2 = em.find(Member.class, 101L);
System.out.println("result = " + (findMemeber1 == findMemeber2));

이 코드의 출력 결과로 result = true 를 확인할 수 있다.

 

JPA 는 자바 컬렉션에 가져왔을 경우 주소가 같은것처럼 영속성 엔티티의 동일성을 보장해준다. ( == 비교 보장)

이것이 가능한 이유는 바로 1차 캐시가 존재하기 때문이다.

 

1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공해준다.(즉, 서로 다른 요청 간에 데이터가 영향을 받지 않는다.)

즉, JPA 에서 같은 트랜잭션 안에서 같은 값을 == 비교 했을 경우 결과로 true 를 반환받는 것을 보장해준다.

 

* 트랜잭션 격리 수준?

- 동시에 실행되는 트랜잭션이 서로에게 영향을 미치지 못하도록 트랜잭션 사이를 격리하는것

예를 들면 서로 다른 트랜잭션이 동시에 같은 데이터를 수정하지 못하도록 해야 한다는 것이다.

격리 수준에는 아래와 같이 4가지가 있다.(아래로 갈수록 격리 수준이 높아짐)

 

- READ UNCOMMITTED (커밋되지 않는 읽기)

- READ COMMITTED (커밋된 읽기)

- REPEATABLE READ (반복 가능한 읽기)

- SERIALIZABLE (직렬화 기능)

 

그 중 REPEATABLE READ - 반복 가능한 읽기는 항상 일관성 있는 데이터 읽기를 보장하는 레벨로, 다른 트랜잭션에서 데이터를 조작하여도 영향을 받지 않는 격리 수준이다.

반복 가능한 읽기 등급의 트랜잭션 격리 수준

 

 

3. 트랜잭션을 지원하는 쓰기 지연(Transactional write-behind)

Entity 를 등록할 때 트랜잭션을 지원하는 쓰기 지연이 제공된다.

em.persist(memberA);
em.persist(memberB);

위와 같은 코드를 순차적으로 넣었을 때 JPA 안에서 어떤일이 벌어질까?

 

- memberA 를 저장하면 생기는 일

: 영속성 컨텍스트 안에는 1차 캐시 뿐만이 아니라 쓰기 지연 SQL 저장소 라는 것이 있다.

memberA 를 넣으면 일단 해당 entity 가 1차 캐시로 들어간다.

동시에 JPA가 해당 entity 를 분석해서 insert query 를 생성하여 쓰기 지연 SQL 저장소에 쌓아둔다.

-> 여기까지 아직 데이터베이스에 데이터를 넣는 과정은 아직 존재하지 않는다.

 

이후 memberB 를 넣으면 이번에도 entity 를 1차 캐시에 넣는다.

이때 또 JPA가 insert query 를 생성해서 쓰기 지연 SQL 저장소에 차곡차곡 쌓아둔다.

 

그렇다면 쌓여있는 쿼리는 언제 데이터베이스로 전달될까?

-> 트랜잭션이 커밋되는 시점에 쓰기 지연 SQL 저장소에 있던 쿼리들이 flush 되면서 데이터베이스로 전달된다.

(flush 는 이후에 작성할 글에서 설명한다.)

그리고 실제 데이터베이스 트랜잭션이 커밋된다.

즉, persist 하는 순간 데이터베이스에 entity 가 저장되는 것이 아니라, 영속성 컨텍스트에 차곡차곡 entity 와 쿼리가 쌓이게 되고

이후 트랜잭션이 커밋되는 순간 데이터베이스에 쿼리와 entity 가 전달되는 것이다.

 

그렇다면 굳이 왜 이런식의 구조로 진행될까?

 

여기서 버퍼링 이라는 기능을 쓸 수 있다.

만약 매 persist 마다 쿼리를 데이터베이스에 전달할 경우 개발자가 기능 최적화를 할 수 있는 여지가 잘 생기지 않는다.

그런데 계속 쿼리를 쌓아뒀다가 한번에 데이터베이스로 쿼리를 전달하면, 데이터베이스와 애플리케이션 간의 쿼리 전달과 같은 네트워크 통신 횟수를 획기적으로 줄여줄 수 있다.(JDBC Batch)

 

hibernate 같은 경우 해당 기능을 실행할 수 있는 아래와 같은 옵션을 persistence.xml 파일에 작성해 줄 수 있다.

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

이는 JDBC Batch(쿼리 버퍼링) 기능을 사용할 수 있는 JPA 옵션으로서, value 값에 적혀있는 크기만큼 쿼리를 모아서 한번에 데이터베이스로 쿼리를 전달해준다.

 

이런것들을 잘 활용하면 그저 JPA 옵션 딱 하나로 기본적으로 어느정도의 성능을 먹고 들어갈 수 있다.