JPA Persistence Context(+EntityManager)

박미소·2024년 1월 21일
0

코틀린

목록 보기
31/44

영속성 컨텍스트


DB와 프로그램 사이에 공간이 있는데 이 공간이 영속성 컨텍스트이다.


  • JPA 의 영속성 컨텍스트로 관리되는 엔티티들

  • 엔티티의 생명주기

    • 비영속 -> transient(new): 새로 작성된 엔티티가 영속성 컨텍스트에 담기기 전 상태
    • 영속 -> managed: 영속성 컨텍스트에 담겨 관리되는 상태
    • 준영속 -> detached: 영속성 컨텍스트에 저장되었다가 분리된 상태
    • 삭제 -> removed: 영속성 컨텍스트에 저장되었다가 삭제된 상태


영속성 컨텍스트 (+ EMF, EM)


@GetMapping("/practice1")
fun practice1() {
	val emf = Persistence.createEntityManager("hi")
    val em = emf.createEntityManager()
    
    // 객체를 생성한 상태 (비영속)
    val member = Member()
    member.id = 101L
    member.title = "테스트"
    
    val transaction = em.transaction
    transaction.brgin()
    
    println("BEFORE")
    em.persist(member)     // 객체를 저장한 상태(영속)
    println("AFTER")
    
    transaction.commit()
    
    em.close()
    emf.close()

실행 순서: BEFORE -> AFTER -> insert member

println("BEFORE") 보다 먼저 트랜잭션이 시작되면서 DB 와의 커넥션이 열렸지만,

member를 1차 캐시에 올려놓은 이후지만,

println("AFTER") 프린트문 출력 이후에 커밋이 되면서 member 엔티티를 영속성 컨텍스트에 저장하는 순서가 제일 마지막이 되었다.

=> member가 1차 캐시에 가만히 있다가 커밋을 만나면 DB로 들어가게 된다.




1차 캐시


@GetMapping("/practice2")
    fun practice2(){

        // EntityManager 생성
        val emf = Persistence.createEntityManagerFactory("hello")
        val em = emf.createEntityManager()


        //객체를 생성한 상태(비영속)
        val member = Member()
        member.id = 1001L
        member.title = "회원"

        val transaction = em.transaction
        transaction.begin()

        //1차 캐시에 저장됨
        em.persist(member) // insert

        //3번 멤버 조회
        val member1 = em.find(Member().javaClass, 103L)  // select

        println("조회 결과 : " + member1.title) // println

        transaction.commit()
        em.close()
        emf.close()
    }

실행순서: select member1 -> insert member

1001번의 회원을 1차 캐시에 저장했다.

em.find 로 103번의 멤버를 조회하게 되어 103번의 member1을 1차 캐시에 올려 먼저 찾은 다음,

100번의 member가 1차 캐시에 다시 올려놓아지고 커밋시 DB에 저장된다.(이 일을 모두 엔티티 매니저가 한다)




영속성 엔티티의 동일성 보장


@GetMapping("/practice3")
    fun practice3(){

        // EntityManager 생성
        val emf = Persistence.createEntityManagerFactory("hello")
        val em = emf.createEntityManager()

        val transaction = em.transaction
        transaction.begin()

        //103번 멤버 조회
        val member1 = em.find(Member().javaClass, 103L)
        //103번 멤버 조회
        val member2 = em.find(Member().javaClass, 103L)

        println("비교 결과 : " + (member1 === member2))

        transaction.commit()
        em.close()
        emf.close()

    }

두개는 같은 것일까? 같은 것이 맞다.

1차 캐시에 있던 103번의 Member를 1차 캐시에서 또 조회하게 된다. 성능상 이점이 있다.




트랜잭션이 지원하는 쓰기지연 Transactional write-behind


 @GetMapping("/practice4")
    fun practice4(){

        // EntityManager 생성
        val emf = Persistence.createEntityManagerFactory("hello")
        val em = emf.createEntityManager()

        val transaction = em.transaction
        transaction.begin()


        val member1 = Member()
        member1.id = 10000L
        member1.title = "회원님1"

        val member2 = Member()
        member2.id = 10001L
        member2.title = "회원님2"

        // 이 때 Insert 쿼리를 보내게 될까?
        em.persist(member1)
        em.persist(member2)


        println("=============================")

        transaction.commit()
        em.close()
        emf.close()
    }

실행순서: println("=============================") -> insert member1 -> insert member2

영속성 컨텍스트 내 쓰기 지연 SQL 저장소가 있는데,

엔티티 매니저는 transaction을 commit하기 직전까지 내부 쿼리 저장소인 쓰기 지연 SQL 저장소에 쿼리문을 원기옥처럼 모아놓는다.

트랜잭션 커밋시 이 쿼리들이 DB로 한번에 뿌려진다.

트랜잭션이 커밋될때 한꺼번에 데이터베이스에 적용된다.

이것을 쓰기 지연 (Transactional write-behind)이라고 한다.




변경 감지 Dirty Checking


@GetMapping("/practice5")
    fun practice5(){
        // EntityManager 생성
        val emf = Persistence.createEntityManagerFactory("hello")
        val em = emf.createEntityManager()

        val transaction = em.transaction
        transaction.begin()

        //103번 멤버 조회
        val member1 = em.find(Member().javaClass, 103L)
        member1.title = "바뀔까"

        transaction.commit()
        em.close()
        emf.close()
    }

실행순서: 103번의 Member 조회 -> 데이터 변경 -> 커밋 시 적용됨

업데이트 쿼리문이 자동생성돼 member1의 title 이 '바뀔까'로 바뀐다.

이 때 Dirty Checking(변경감지)이 이용된다.

103번 Member 엔티티가 엔티티 매니저에 의해 1차 캐시에 올려지면서 영속성 컨텍스트에 들어올 때 JPA는 최초의 엔티티 상태를 복사해 스냅샷에 저장해놓는다.

트랜잭션 커밋시 엔티티 매니저가 내부에서 먼저 flush()를 호출한다.

flush(): 영속성 컨텍스트의 변경내용을 DB와 동기화, 싱크를 맞추는 역할.

엔티티와 최초의 스냅샷이 다르다면 변경내용(member1.title = "바뀔까")을 Update 쿼리로 자동생성해 쓰기 지연 SQL 저장소에 저장한다.

쓰기 지연 저장소의 업데이트 쿼리가 발동돼 엔티티의 변경사항이 DB에 적용되고 트랜잭션이 끝나고 커밋된다.

0개의 댓글