- 영속성(persistency)이란 데이터를 영구적으로 저장하는 것을 의미합니다.
따라서 영속성 컨텍스트란 직역하자면 데이터를 영구적으로 저장하는 환경 정도로 해석될 수 있습니다.- 영속성 컨텍스트는 논리적인 개념 입니다.
엔티티 매니저를 통해서 영속성 컨텍스트에 접근할수 있습니다.
기본적으로 JPA는 객체지향 언어인 JAVA와 Database 사이의 패러다임 불일치를 해결하기 위해서 도입된 규약입니다.
일반적으로 Spring framework 사용해서 개발할때
database에서 특정 id값을 갖는 데이터를 수정할때 위 그림과 같이 작동합니다.
하지만 위 그림처럼 진행하게 되면 객체로 되어있는 java 코드를 데이터베이스에 그대로 적용할수없다.
그러므로,
JPA가 이러한 JAVA와 Database 사이의 패러다임 불일치를 해결해준다 그 과정에서 영속성 컨텍스트라는 JPA 핵심 개념을 만나게된다.
영속성 컨텍스트가 작동하기 위해 엔티티 매니저 팩토리와 엔티티 매니저를 통해 데이터베이스에 접근한다.
JPA는 앱 실행과 동시에 한 DB당 하나의 EntityManagerFactory를 생성합니다.
WAS가 종료되는 시점에 EntityManagerFactory는 사라집니다.
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("xxx");
entityManagerFactory.close();
이 EntityManagerFactory는 Transaction 요청이 하나 들어올 때마다 하나의 EntityManger를 생성합니다.
즉 고객의 요청이 들어오면 -> 하나의 스레드를 생성하여 EntityManager를 만들고 -> Transaction이 종료되면 해당 스레드를 종료합니다.
EntityManger의 Transaction은 다음과 같이 실행됩니다.
EntityTransaction trans = entityManager.getTransaction();
trans.begin();
trans.commit();
trans.rollback();
transaction이 시작되고, 해당 변경사항에 이상이 없으면 commit, 무언가 오류가 발견되면 rollback을 진행합니다.
엔티티 생명주기
• 비영속 (new/transient)
영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
• 영속 (managed)
영속성 컨텍스트에 관리되는 상태
• 준영속 (detached)
영속성 컨텍스트에 저장되었다가 분리된 상태
• 삭제 (removed)
삭제된 상태
추가로 준영속에는,
em.clear() //영속성 컨텍스트를 완전히 초기화
em.close() //영속성 컨텍스트 종료
비영속과 준영속은 사실 비슷한 느낌이 있습니다.
둘의 가장 큰 차이는 한번 영속상태가 된 적이 있는가 없는가의 차이입니다.
영속상태가 되려면 식별자가 반드시 존재해야 합니다. 그래서 영속 상태가 되었다가 다시 준영속 상태가 되면 식별자가 항상 존재하게 됩니다.
• 1차 캐시
• 동일성(identity) 보장
• 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
• 변경 감지(Dirty Checking)
• 지연 로딩(Lazy Loading)
위 그림처럼 데이터베이스에서 찾아올때 1차 캐시에 들러서 찾아올 id가 없으면 데이터베이스에서 셀렉트 쿼리문을 날리고 찾아온 값을 1차캐시에 넣고 다시 반환 하는 과정을 거친다.
커밋하는순간 쓰기지연 저장소에서 쿼리문을 날리고 커밋한다.
엔티티를 데이터베이스에서 찾아올때 1차 캐시부터 확인 한다고 했다, 찾아올 id 값이없으면 데이터베이스에서 해당하는 값을 찾아온다. 그리고 하나 더 기억해야하는게 있는데 찾아온 값이 1차캐시에 저장이 될때 스냅샷을 떠놓는다는것이다.
그러므로 애플리케이션 코드가 수정이 되고 커밋하는 시점,즉 flush()가 되는 시점에 스냅샷과 비교하여 쓰기지연 SQL 저장소가 UPDATE 쿼리문을 데이터베이스에 날려보낸다.
영속성 컨텍스트에 변경내용을 데이터베이스에 반영
플러시가 발생했다는건, 변경 감지가 되어서 수정된 엔티티가 쓰기 지연 저장소에 등록 되면
그때 부터 플러시로 데이터베이스에 쿼리문을 날리수 있게된다
예를들어,
EntityManagerFactory emf = Persistence.createEntityManagerFactory("xxx");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member=new Member();
// id 값은 자동
member.setName("dragonTiger");
em.persist(member); //이 순간 1차 캐시에 엔티티값이 할당되고
em.flush(); // 플러시를 할수있게된다 즉, INSERT 쿼리를 데이터베이스에 날려준다.
// flush()는 데이터베이스에 동기화 시키는것뿐이지 직접적으로 커밋까지 해야만 데이터베이스에 완벽히 저장되어진다.
tx.commit(); //트랜잭션 커밋을 해야만 데이터베이스에 값이 저장됨
} catch(Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
영속성 컨텍스트를 플러시 하는 방법은 3가지가 있다.
em.flush() - 직접 호출
트랜잭션 커밋 - 플러시 자동 호출
JPQL 쿼리 실행 - 플러시 자동 호출
위 그림처럼 persist()로 멤버 A,B,C 를 엔티티에 1차 캐시하면 아직 데이터베이스에 저장된 상태가 아니라 셀렉트 쿼리를 보낼때 당연히 A,B,C, 값이 보이지 않는다.
그러므로 JPQL 실행시에는 flush()를 하고 셀렉트 쿼리를 찾아온다 즉 쓰기지연저장소를 비워준다.
주의할점은 flush()를 했다고 커밋이 된게 아니라 데이터베이스와 연동상태에서 셀렉트 해온거라 셀렉트쿼리에 A,B,C 가 담긴다는 것이다. (나도 처음엔 헷깔렸다 커밋이 안되었는데 A,B,C를 셀렉트를 해올수가 있나? 데이터베이스와 연결 상태라 가능하다고한다.)
플러시 정리
플러시가 일어나면 1차 캐시가 삭제될까?
삭제 되지 않는다. 쓰기 지연 SQL 저장소에 있는 쿼리들만 DB에 전송되고 1차 캐시는 남아있다.
플러시는 영속성 컨텍스트를 비우지 않는다. 오해하면 안된다.
플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화 한다.
플러시가 동작할 수 있는 이유는 데이터베이스 트랜잭션이라는 작업 단위(개념)가 있기 때문이다.
어쨋든 트랜잭션이 시작되고 커밋되는 시점에만 동기화 해주면 되기 때문에, 그 사이에서 플러시 매커니즘의 동작이 가능한 것이다.
JPA는 기본적으로 데이터를 맞추거나 동시성에 관련된 것들은 데이터베이스 트랜잭션에 위임한다. 참고로 알아두자.
참고한 사이트
멍분이의 개발일지
자바 ORM 표준 JPA 프로그래밍 - 기본편