해당 글은 김영한 님의 ["자바 ORM 표준 JPA 프로그래밍"] 을 스터디 하면서 정리하는 글 입니다 !👨💻
JPA 를 공부하다 보면 어려운 용어가 많이 나옵니다. 🥲
JPA 라는 것이 아무래도 데이터베이스와 직접적으로 연관이 있는 만큼 데이터베이스에 대한 이해 또한 필요로 합니다.
이번 글에서는 JPA를 본격적으로 시작하기에 앞서, 꼭 알아야하는 개념에 대해 정리하겠습니다 💡
제가 실제로 JPA 를 처음 접했을 때 가장 생소했던 단어가 '영속성' 이였습니다.
영속성(Persistance) 란 데이터를 생성한 프로그램이 종료되어도 사라지지 않는 데이터의 특성을 말합니다. 즉, 컴퓨터에서 비휘발성 메모리에 데이터를 저장함으로써, 프로그램이 종료되어도 해당 데이터가 남아있는 특징을 얘기하는 겁니다!
Entity Manager 를 공부하기 전에 Entity 부터 공부 해봅시다.
Entity 는 영속성을 지지는 객체를 의미합니다.
JPA 의 핵심 기능은 ORM 기능입니다. 즉 클래스의 객체를 테이블의 레코드로 매핑하는 기능을 말하는데, 여기서 Entity 는 매핑 대상이 되겠죠?
자, 그렇다면 Entity Manager 는 이름에서도 알 수 있듯이, 이렇게 영속성을 지니는 Entity 를 저장하고, 수정하고, 삭제하고 조회하는 등 Entity와 관련된 모든 일을 처리하는 역할을 하는 일종의 관리자겠죠? 즉, 영속성 컨텍스트를 통해 Entity 를 관리합니다.
영속성 컨텍스트는 밑에서 자세히 설명하겠습니다.
쉽게 개발자 입장에서 보자면, Entity 를 저장하는 가상의 데이터베이스로 생각하면 됩니다.
그리고 이러한 Entity Manager 는 Entity Manager Factory 로부터 생성됩니다.
Entity Manager Factory 특징
- 생성 비용이 크다.
- 생성 비용이 크기 때문에 한 개만 만들어서 쓰레드간 공유한다.
- Thread-Safe 하다.
Entity Manager 특징
- persist(), remove(), find() 등 다양한 메소드를 통해 Entity 를 관리한다.
- Thread-Safe 하지 않다.
- 생성시 데이터베이스 연결이 꼭 필요한 시점까지 커넥션을 얻지 않는다.
추가적으로 EntityTransaction 이라는 개념도 존재합니다.
데이터와 관련된 모든 작업(조회 제외)은 Transaction 이라는 단위 안에서 이루어집니다.
// Entity Manager Factory 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpaTest");
// Entity Manager 생성
EntityManager em = emf.createEntityManager();
// Entity Transaction 생성
EntityTransaction transaction = em.getTransaction();
// Transaction 시작
transaction.begin();
// 엔티티 영속화(아직 데이터베이스 반영 x)
em.persist(memberA);
em.persist(memberB);
transaction.commit(); // 커밋(데이터베이스 반영)
해당 코드는 EntityManagerFactory 생성을 통해 EntityManager 생성 후, Transaction을 시작&종료 하는 과정을 집약화 한 코드입니다.
만약, 트랜잭션 과정에서 에러 발생시 transaction.rollback() 을 통해 모든 작업을 취소합니다 ✅
이미지를 통해 좀 더 깊은 이해를 해볼까요?
클라이언트로 부터 데이터 작업 관련 요청(DB 커넥션 필요)이 들어오는 경우,
EntityManagerFactory 에서 EntityManager를 생성하는데, 이는 '커넥션 풀' 이라는 영역에서 실행됩니다.
JPA 구현체(ex. hibernate)는 먼저 EntityManagerFactory 생성시 커넥션 풀을 만들고 DB 작업이 필요한 시점에 EntityManager 생성 후, 커넥션 풀에서 커넥션을 하나 얻어옵니다.
이를 모두 거쳐 정상적으로 커넥션을 얻어오면 Transaction이 시작됩니다 🙆🏻
JPA 를 통한 데이터베이스 작업시 '영속성 컨텍스트(Persistance Context)' 역할이 굉장히 중요합니다.
영속성 컨텍스트는 실제 영속성을 지니는 Entity 를 영구 저장하는 환경입니다.
해당 그림을 통해 영속성 컨텍스트를 이해해 보겠습니다.
프로그램 구동시, 어떠한 경우에는 저장이 필요한 데이터를 데이터 베이스에 저장합니다.
이때 바로 데이터베이스에 저장되는 것이 아닌 영속성 컨텍스트에 해당 데이터를 먼저 저장하고 어
떠한 명령을 통해 한번에 데이터베이스에 저장하게 됩니다.
이때 무조건 거치는 영역이 영속성 컨텍스트입니다.
참고로, 영속성 컨텍스트에 존재하는 Entity 가 데이터베이스에 반영되는 시점을 '플러시(flush)' 라고 합니다.
이때 무조건 거치는 영역이 영속성 컨텍스트입니다.
영속성 컨텍스트에 저장된 Entity 는 각자의 식별자 값(@Id) 로 구분됩니다.
그렇다면 왜 데이터를 바로 데이터베이스에 저장하지 않고 영속성 컨텍스트에 먼저 저장을 할까요?
해당 이유에는 크게 4가지가 있습니다.
1차 캐시란 영속 상태의 엔티티가 실제로 저장되는 곳
쉽게 말해 메모리(RAM)에 Map 이 존재(key = 식별자, value = Entity)
Entity 생성 후 저장 시, 1차 캐시에 저장
Entity 조회 시, 데이터베이스를 조회하기 전에 1차 캐시 조회
1차 캐시에 존재하지 않다면, 데이터베이스에서 조회 후 1차 캐시 저장 후 해당 Entity 반환
결론 : 1차 캐시를 통해 비교적 짧은 시간에 Entity 접근 및 사용이 가능(성능상 이점)
1차 캐시라는 기능으로 인해 동일한 식별자에 대해 조회된 Entity는 참조 값이 같은 Entity 입니다.
쉽게 말해, 반복해서 호출 해도 1차 캐시에 존재하는 Entity를 반환합니다.
결론: 같은 식별자에 대한 Entity 조회는 동일성 보장
memberA와 memberB 를 데이터베이스에 저장한다고 가정해봅시다!
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋
해당 코드에서 볼 수 있듯이, Entity Manager 는 트랜잭션을 커밋하기 직전까지 데이터베이스에 Entity를 저장하지 않고, 내부 쿼리 저장소에 INSERT SQL 을 차곡차곡 모아둡니다.
그리고 트랜잭션을 정상적으로 커밋할 때 비로소 모든 쿼리를 데이터베이스에 보내는데,
이것은 쓰기 지연(transactional write-behind) 라고 합니다.
코드를 좀 더 분석해보겠습니다.
먼저, Entity Manager를 통해 memberA 를 영속화 하였습니다.
그러면 1차 캐시에 해당 Entity 를 저장과 동시에 데이터베이스에 등록하기 위한 등록 쿼리를 내부 쿼리 저장소에 보관합니다.
마찬가지로 memberB 도 똑같은 과정을 거칩니다.
memberA와 memberB의 영속화가 정상적으로 실행되면 트랜잭션을 커밋하게 됩니다.
이때 Entity Manager는 영속성 컨텍스트를 데이터베이스에 반영(Flushing)합니다.
이러한 동기화 작업은 내부 쿼리 저장소에 있는 쿼리를 데이터베이스에 보내는 것을 의미합니다.
(참고 : 데이터베이스는 쿼리를 통해 조작 가능 )
영속성 Context를 이용하면 편리한 방법으로 Entity 를 수정할 수 있습니다.
변경 감지라는 기능을 사용하는데요, 그림을 통해 이해해보겠습니다.
JPA는 엔티티를 "영속성 Context에 보관시, 최초 상태를 복사해서 저장해 두는데 이것을 스냅샷" 이라고 합니다.
스냅샷을 통해 플러시 시점에 스냅샷과 엔티티를 비교해서 변경된 엔티티를 찿습니다.
여기서 중요한 점은 영속성 Context 에 존재하는 Entity 만 변경 감지 기능에 대상이 됩니다.
변경감지의 동작 원리를 순서대로 정리 해보겠습니다.
앞에서 배운 내용을 토대로, 저희는 이렇게 정리해볼 수 있습니다.
Entity 는 데이터베이스에 저장 대상이 되는 데이터이며, 해당 Entity는 Entity Manager에 의해 관리가 되며 영속성 컨텍스트 에 저장시 영속성을 가진다.
결국 Entity 는 JPA 에 대상이 된다는 것을 알 수 있습니다.
Entity 는 4가지 상태가 존재합니다.
앞에서 배운 내용을 토대로 JPA 는 데이터베이스에 접근하기 전에 영속성 컨텍스트라는 영역을 통해 다양한 이점을 가진다라는 것을 알 수 있습니다.
그렇다면 영속성 컨텍스트에 변경되는 Entity를 실제 데이터 저장소인 데이터베이스에 반영해야겠죠?
Flush 란 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 행위를 말합니다.
쉽게 말해, 영속성 컨텍스트에 쌓인 내부 쿼리 저장소의 쿼리를 데이터베이스에 전송함으로써,
영속성 컨텍스트와 데이터베이스를 동기화하는 행위라고 할 수 있습니다.
영속성 컨텍스트를 flush 하는 방법에는 3가지가 존재합니다.
👨💻 호출 방법은 어렵지 않으니, 구글링을 통해 공부해봅시다!
JPA 프로그래밍을 하면서 필수적으로 알아야 할 몇가지 개념에 대해 공부해 보았습니다.
해당 개념을 숙지하셨고, JPA 연관관계가 궁금하시다면 다음 글 JPA 연관관계 매핑 을 참고해주세요 💪