JPA 영속성 컨텍스트(Persistence Context) 란?
엔티티를 영구 저장하는 환경
영속성 컨텍스트는 논리적인 개념으로 눈에 보이지 않고, 엔티티 매니저를 통해서 영속성 컨텍스트에 접근함
엔티티의 생명주기
비영속(new/transient) JPA 와 전혀 관련 없이 객체만 생성된 상태가 비영속 상태
1 2 3 var member = Member()member.id = 1L member.name = "helloA"
영속(managed) 영속성 컨텍스트에 관리되는 상태 영속성 상태라고 DB에 저장 되는 것이 아니고, commit 이후에 DB에 저장 된다.
1 2 3 4 5 6 7 8 9 10 val emf = Persistence.createEntityManagerFactory("hello" )val em = emf.createEntityManager()var member = Member()member.id = 1L member.name = "helloA" em.persist(member)
준영속(detached) 영속성 컨텍스트에 저장되었다가 분리된 상태
삭제(removed) 상제된 상태
엔티티 조회
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 var member = Member()member.id = "memeber1" val emf = Persistence.createEntityManagerFactory("hello" ) val em = emf.createEntityManager()val tx = em.transactiontx.begin() try { var memberA = Member() memberA.id = 1L memberA.name = "helloA" em.persist(memberA) em.flush() val member = em.find(Member::class .java , 1L) member.name = "helloUpdate" tx.commit() } catch (e: Exception) { tx.rollback() } finally { em.close(); emf.close(); } member.name = "회원1" em.persist(member) val findMember = em.find(Member::class .java , "member1")
em.persist(member)1차 캐시에 저장 후 find("memeber1") 하게되면 DB를 조회하는게 아니고 1차 캐시를 조회함
1 2 3 val findMember1 = em.find(Member::class .java , "member1") val findMember2 = em.find(Member::class .java , "member1")
find("memeber2")를 하면 영속성 컨텍스트 안 1차 캐시를 조회
1차 캐시에 값이 없으면 DB를 조회
1차 캐시에 member2 저장
member2를 반환
한번 1차 캐시에 저장되면 member2 조회시 1차 캐시에서 조회
find로 2번 호출하는데 실제로 select쿼리는 한번 발생, 이유는 findMember2는 1차 캐쉬에서 조회 주의점) 트랙잰션 단위로 움직임
영속 엔티티의 동일설 보장 동일한 트랜잭션 안에서는 1차캐시에 저장되어있기 때문에 동일한 주소값을 참조(Collection에 똑같은 객체를 사용하는것과 같음)
1 2 3 4 5 val findMember1 = em.find(Member::class .java , "member1") val findMember2 = em.find(Member::class .java , "member1") println(findMember1 == findMember2)
트랙잭션을 지원하는 쓰기 지연 JPA는 트랜잭션을 하기전까지 영속성 컨텍스트 안에 데이터를 쌓고 있다. 트랙잭션 commit을 해야 SQL을 데이터베이스로 보낸다.
persist에 memberA를 넣으면 1차 캐시에 저장과 동시에 memberA에 Entity를 분석하여 SQL 문을 생성하여 쓰기 지연 SQL 저장소에 저장
memberB도 동일하게 SQL문을 생성하여 쓰기 지연 SQL 저장소 저장
memberA와 memberB가 쓰기 지연 SQL 저장소에 쌓인다
commit()을 실행하면 쓰기 지연 SQL 저장소에 있던 SQL문이 flush로 데이터 베이스에 전달
실행 결과 wrtieBehind.kt 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 val emf = Persistence.createEntityManagerFactory("hello" )val em = emf.createEntityManager()val tx = em.transactiontx.begin() try { var memberA = Member() memberA.id = 1L memberA.name = "helloA" var memberB = Member() memberB.id = 2L memberB.name = "helloB" em.persist(memberA) em.persist(memberB) println("=============== persist" ) tx.commit() } catch (e: Exception) { tx.rollback() } finally { em.close(); emf.close(); }
result 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 =============== persist Hibernate: insert into Member (name, id) values (?, ?) Hibernate: insert into Member (name, id) values (?, ?)
주석처리 한 부분에서 insert 쿼리문이 실행 되지 않고 commit 시점에 sql문 실행
hibernate.jdbc.batch_size 옵션으로 쓰기지연 SQL 저장소에 쌓이면 처리 갯수 설정 가능(버퍼링 기능이나 실전에서 잘 사용하지 않음)
Entity 수정(변경 감지)
데이터가 처음 읽어온 상태를 스냅샷으로 저장
commit 시점에서 Entity와 스냅샷을 비교 하여 변경 된 부분의 update 문을 DB에 전달
dirtyChecking.kt 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 val emf = Persistence.createEntityManagerFactory("hello" )val em = emf.createEntityManager()val tx = em.transactiontx.begin() try { var memberA = Member() memberA.id = 1L memberA.name = "helloA" em.persist(memberA) em.flush() val member = em.find(Member::class .java , 1L) member.name = "helloUpdate" tx.commit() } catch (e: Exception) { tx.rollback() } finally { em.close(); emf.close(); }
result 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Hibernate: insert into Member (name, id) values (?, ?) Hibernate: update Member set name=? where id=?
JPA는 update 코드가 있는게 아니라 Collection 처럼 변경되는 시점을 자동으로 감지해서 수정
Entity 삭제 변경 감지와 동일하게 작동
1 2 val member = em.find(Member::class .java , 1L) em.remove(member)
플러시(Flush)
영속성 컨텍스트를 비우지 않음
영속성 컨텍스트의 변경내용을 DB에 동기화
commit 직전에만 동기화
준영속 상태 JPA가 영속성 컨텍스트에서 분리
1 2 val member = em.find(Member::class .java , 1L) em.detach(member)
소스코드
참조