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)
소스코드
참조