[SpringBoot] JPA - 동작원리(Feat.영속성 컨텍스트)
이번에는 JPA를 사용하면서 막혔던 부분들을 생각해보니 JPA에 관한 개념이 부족했다고 생각이 든다
그래서 JPA를 정리하며 기초를 잡아봐야겠다 (feat. 오늘 영속성 컨텍스트 때문에 null party )
JPA란 ?
우선 들어가기에 앞서 알아야 할 개념들을 조금 짚고 넘어가보자
ORM(Object-relational mapping)
- Object-relational mapping (객체 관계 매핑)
- 객체는 객체대로 설계하고, 관계형 데이터베이스(RDBMS)는 관계형 데이터베이스대로 설계를 한다.
- 이 때 ORM 프레임워크가 중간에서 매핑을 해주게 된다. - 대중적으로 사용되어지는 언어에는 대부분 ORM 기술이 존재한다고 한다.
- ORM은 객체와 RDB 이 두가지의 위에 존재하는 기술 이다.
JPA(Java Persistance API) 란
- EJB
- 과거의 자바 표준 (Entity Bean)
- 과거의 ORM이라고한다
- 문제점 1. 코드가 매우 지저분하다. 2. API 복잡성 높음 2. 속도가 느림 - Hibernate
- ORM 프레임워크, Open Source SW - JPA(Java Persistence API)
- 현재 자바 진영의 ORM 기술 표준, 인터페이스의 모음
- 즉 실제로 동작하는 것이 아님.
- JPA 인터페이스를 구현한 대표적인 오픈소스가 Hibernate
- JPA 2.1 표준 명세를 구현한 3가지 구현체 : Hibernate, EclipseLing, DataNucleus
JPA 특징
- 동일성 보장
- 동일성 보장이란 ? EntityManager에서 한 객체에 대해 여러 번 꺼내도 꺼낸 객체들은 모두 같은(`==`)
객체라는 것을 보장 해준다 - 쓰기 지연
- 쓰기 지연은 Entity를 저장(.persist())을 할 때 데이터베이스에 바로 쿼리가 전송되지 않는다.
트랜잭션이 종료되는 시점(commit, 내부적으로는 .flush())에 쿼리가 전송되는 것을 뜻함
- 영속성 컨텍스트 안에는 "쓰기 지연 SQL 저장소" 와 1차 캐시(Entity 저장소)가 나뉘어 존재함
.persist() 시점에는 쿼리를 쓰기 지연 하여 SQL 저장소에 저장해 두었다가, 트랜잭션이 종료되는 시점에
쓰기 지연 SQL 저장소에 있는 쿼리들을 한번에 데이터베이스로 보내는 구조이다.
* Service Layer에서 @Transactional을 걸어서 해당 서비스의 비지니스 로직이 끝날 때 쿼리에 저장이 되게끔
코드를 짬
- 변경 감지 (dirty checking)
- 변경 감지는 한 트랜잭션 안에서 발생하는 Entity의 변경사항을 감지 한 후 update를 해주는 것이다.
- 영속성 컨텍스트 안에 있는 1차 캐시(Entity 저장소)가 적재되는 순간을 기록하는 "스냅샷" 공간이 있는데
이 공간에 저장된 스냅샷과 <> 트랜잭션이 종료 되었을 때의 해당 Entity스냅샷을 비교하여
두 스냅샷 사이에 차이가 있으면(변경이 일어남) 변경된 정보를 update쿼리로 만들어서 쓰기 지연 SQL 저장소에
쿼리문을 적재하는 구조이다.
JPA사용하여 Data를 Update할 때 update쿼리문을 날리는 것이아닌 트랜잭션안에서 findbyId... 등을 이용해
영속성 컨텍스트 영역 안에있는 1차 캐시에 저장한 후 Entity의 값이 변경감지를 일으키면 스냅샷 공간의 스냅샷과 Entity스냅샷을 비교하여 변경된 값이 있으면 Data를 Update해준다.
-> 변경된 값이 없으면 영속성 컨텍스트 안에있는 1차캐시에 있는 Entity를 가져옴 B변경이 A와 관계가 없다면
A에는 B의 정보 변경을 감지 하지못한 값이 담겨져있는 상황을 겪어봄
JPA 동작 과정
- JPA는 애플리케이션과 JDBC 사이에서 동작함
- 개발자가 JPA를 사용하면, JPA 내부에서 JDBC API를 사용하여 SQL을 호출 , DB와 통신을 하게 된다.
- 개발자가 직접 JDBC API를 사용하는 것이 아님
1. 설정파일을 통해서 JPA를 설정해준다.
2. 설정 후 EntityManagerFactory를 생성
3. EntityManager를 생성하여 Entity를 영속성 컨텍스트(Persistence Context)를 통해 관리한다.
*영속성 컨텍스트는 엔터티를 영구 저장하는 환경
EntityManagerFactory
- EntityMager를 생성하는 객체이다. 하나의 EntityMagerFactory는 애플리케이션 전체에서 공유되는 싱글톤이다.
EntityMager
- Entity를 관리하는 객체
- 동시성의 문제가 발생할 수 있다. 그러므로 쓰레드 간에 공유하면 안된다 ( 요청 : 객체 = 1 : 1 규칙 )
Entity
- EntityManger가 관리하는 객체로 테이블과의 커넥션을 관리한다.
- 반드시 테이블과 관련된 필드만을 가져아하는 것은 아님
자 이제 영속성 컨텍스트(Persistence Context)에 대해 알아보자.
영속성 컨텍스트란 인스턴스로 존재하는 엔티티를 관리하고 영속화시키는 논리적 영역이다.
영속성 컨텍스트에서 관리하는 엔티티라고 해서 반드시 "영속화" 되어 DB에 저장되어지는것은 아니다.
영속성 컨텍스트에서 엔티티를 관리하고 필요에 따라 DB의 데이터를 저장, 조회, 수정, 삭제 가능
이러한 작업을 '엔티티 매니저(Entity Manager) 객체'가 담당한다.
위에서 설명했듯이 영속성 컨텍스트는 크게 2가지영역으로 나누어진다.
1차 캐시 저장소
영속성 컨텍스트가 관리하는 엔티티 정보를 보관 -> 이 상태를 "영속 상태"라고 한다.
-> 영속 상태는 아직 DB에 저장되어지지 않은 상태. 단순히 영속성 컨텍스트에서 관리를 하는 상태
쿼리문 저장소 (SQL 저장소)
필요한 쿼리문(SQL)을 보관 - > 최대한 여러 쿼리문을 모아 둠(DB에 접근하는 횟수를 최소화 하면
성능상 이점을 얻을 수 있기 때문이다.
저장해둔 쿼리문으로 DB에게 접근하는 행위는 EntityManager의 .flush()로 진행한다.
[엔티티의 생명주기]
JPA(영속성 컨텍스트) 입장에서 엔티티의 생명주기를 4가지로 나눌 수 있다.
(엔티티는 쉽게 말해 하나의 인스턴스이다. DB입장에서는 한건의 레코드 라고 생각하면 될것같다)
1. 비영속(new, transient) 상태
- 엔티티가 영속성 컨텍스트와 전혀 관련이 없는 상태입니다.
2. 영속(managed)상태
- 엔티티가 영속성 컨텍스트에서 관리되어지고 있는 상태이다.
영속 상태에 들어갔다고해서 DB에 저장된 상태가 아닌것을 다시 한번 더 주의하자!
(엔티티 매니저의 'persis()'를 사용하면 비영속 상태의 엔티티를 영속상태로 만들 수 있다.)
엔티티는 영속화되어 1차 캐시 저장소에 저장되고 쿼리문 저장소에는 해당 쿼리문이 저장되어질 것이다.
(여러개의 엔티티를 persist()하면 1차 캐시 저장소에는 여러개의 엔티티가 쌓이고, 쿼리문 저장소에도 해당 쿼리문들
이 여러개 쌓일 것이다.)
이렇게 모아둔 쿼리문은 플러시 flush()를 실행하게 될 때 DB에 반영 된다.
플러시를 하더라도 1차 캐시 저장소에서 관리중인 엔티티들이 사라지는 것은 아니다. 플러시는 영속성 컨텍스트와
DB를 동기화(Synchronize) 시키는 것이다.
생성한 엔티티를 입력할 때 이외에도 엔티티 매니저가 DB에서 조회(find())한 데이터도
'영속 상태'엔티티가 되어진다.
이 때 조회된 데이터는 1차 캐시 저장소에 먼저 저장되어진 후 , 저장된 엔티티 정보를 반환한다.
3. 준영속(detached) 상태
영속성 컨텍스트에서 관리되던 엔티티가 영속성 컨텍스트에서 관리되지 않을 때,
엔티티가 준영속 상태가 되어졌다고 한다.
4. 삭제(removed) 상태
삭제 상태는 엔티티를 영속성 컨텍스트에서 관리하지 않게 되고,
해당 엔티티를 DB에서 삭제하는 DELETE쿼리문을 보관한다. flush가 호출되어야 실제 DB에 접근!!
변경감지(Dirty Checking)
변경감지는 Update문과 관련이 깊은데 엔티티에 대해 변경사항이 생기면, 이에 대한 '변경을 감지'한다.
1차 캐시 저장소에는 본래 엔티티가 아니라, 엔티티에 대한 참조와 이 엔티티를 처음 영속화 시킬때의
복사본(SnapShot)을 가지고있다. 플러시가 호출이되면 실행하기 직전에 엔티티메니저는 복사본(SnapShot)
과 실제 엔티티를 비교한다. 이 때 저장해둔 복사본과 실제엔티티의 값이 다르면 엔티티 매니저는 변경을 감지
하게되고 적절한 Update문을 생성하여 플러시와 함께 쿼리문을 날린다.
(주의할 점 : 변경감지는 영속성 컨텍스트에서 관리되어지는 엔티티에 대해서만 이루어지기 때문에 준영속상태인
엔티티가 변경된다고 엔티티 매니저가 변경을 감지할 수 있는건 아니다.)
공부 자료 출처 : https://siyoon210.tistory.com/138