JPA

2-1. 영속성 관리

희구 2021. 8. 2. 21:14

영속성 컨텍스트 

 

JPA에서 가장 중요한 2가지

  • 객체와 관계형 데이터베이스 매핑하기 (정적)
  • 영속성 컨텍스트 (실제 JPA가 내부에서 도대체 어떻게 동작하나?)

엔티티 매니저 팩토리와 엔티티 매니저

 

엔티티매니저 

:엔티티를 저장하고, 수정하고, 삭제하고, 조회하는 등 엔티티와 관련 된 모든 일을 처리한다.

 개발자 입장에서 엔티티매니저는 엔티티를 저장하는 가상의 데이터베이스로 생각하면 된다.

 

 

다음은 엔티티매니저팩토리를 생성하는 코드이다.

//공장만들기, 비용이 많이 든다
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

Persistence.createEntityManagerFactory("hello")를 호출하면

 

<persistence-unit name="hello"> <!--jpa를 만들건데 이름은 hello로 할거야-->
        <properties>
            <!-- 필수 속성 -->
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/> <!--무슨 DB 드라이버를 쓰는지-->
            <property name="javax.persistence.jdbc.user" value="sa"/>
            <property name="javax.persistence.jdbc.password" value=""/>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/> <!--접근 url-->
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>

            <!-- 옵션 -->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="true"/>
            <property name="hibernate.jdbc.batch_size" value="10"/>
            <property name="hibernate.hbm2ddl.auto" value="create" />
        </properties>
    </persistence-unit>

META-INF/persistence.xml에 있는 정보를 바탕으로 EntityManagerFactory를 생성한다.

 

 

 

 

엔티티매니저를 생성하는 코드

//공장에서 엔티티매니저 생성, 비용이 거의 안든다.
EntityManager em = emf.createEntityManager();

이제부터 필요할 때 마다 엔티티 매니저 팩토리에서 엔티티 매니저를 생성하면 된다.

 

 

엔티티매니저팩토리는 이름 그대로 엔티티매니저를 만드는 공장으로, 공장을 만드는 비용은 상당하다.

따라서 한 개만 만들어서 애플리케이션 전체에서 공유하도록 설계되어있다.

반면에 공장에서 엔티티매니저를 생성하는 비용은 거의 들지 않는다.

그리고 엔티티매니저팩토리는 여러 스레드가 동시에 접근해도 안전하므로 서로 다른 스레드 간에 공유가 가능하지만,

엔티티매니저는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 스레드 간에 절대 공유해서는 안된다.

 


일반적인 웹 애플리케이션

위 그림을 보면 하나의 EntityManagerFactory가 다수의 앤티티매니저를 생성했다.

EntityManager1은 아직 데이터베이스 커넥션을 사용하지 않는데,

엔티티매니저는 데이터베이스 연결이 꼭 필요한 시점까지 커넥션을 얻지 않는다.

예를 들어 트랜잭션을 시작할 때 커넥션을 획득한다.

EntityManager2는 커넥션을 사용중인데 보통 트랜젝션을 시작할 때 커넥션을 획득한다.

 

하이버네이트를 포함한 JPA 구현체들은 EntityManagerFactory를 생성할 때 커넥션풀도 만드는데

이것은 J2SE 환경에서 사용하는 방법이다.

JPA를 J2EE환경(스프링 프레임워크 포함)에서 사용하면 해당 컨테이너가 제공하는 데이터소스를 사용한다.


엔티티매니저팩토리를 통해서 고객의 요청이 올 때마다 엔티티매니저를 생성한다.

이 엔티티 매니저는 내부적으로 데이터베이스 커넥션을 사용하여 디비를 사용하게 된다.

그러면 영속성 컨텍스트는 도대체 뭘까?

 

영속성 컨텍스트

  • "엔티티를 영구 저장하는 환경" 이라는 뜻
  • JPA를 이해하는데 가장 중요한 용어
  • EntityManager.persist(entity);
    ->엔티티매니저로 엔티티를 저장하거나 조회하면 엔티티매니저는 영속성컨텍스트에 entity를 영구저장한다.

영속성 컨텍스트는 논리적인 개념으로 눈에 보이지 않는다.

엔티티 매니저를 통해서 영속성 컨텍스트에 접근한다.

엔티티매니저를 생성하면 1:1로 영속성 컨텍스트가 생성된다.

 


 

엔티티의 생명주기

비영속 (new/transient) 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태

영속 (managed) 영속성 컨텍스트에 관리되는 상태

준영속 (detached) 영속성 컨텍스트에 저장되었다가 분리된 상태

삭제 (removed) 삭제된 상태

 


1. 비영속 상태란?

: JPA와 전혀 관련이 없는 상태

그냥 엔티티객체를 생성만 한 상태.

순수한 객체 상태이며 아직 저장하지 않았다.

image-20210802183057985

 

2. 영속성 상태?

: 객체를 생성한 후에 EntityManager를 통해 엔티티를 persist로 영속성 컨텍스트에 저장했다.

이렇게 영속성컨텍스트가 관리하는 엔티티를 영속상태라고 한다.

 

image-20210802183124948

 

image-20210802183158336

이렇게 영속상태가 된다고만 해서 디비에 저장되는 것은 아니다.

디비는 이후에 커밋을 해야 저장된다.

 

 

 

3. 준영속 , 삭제 상태?

: 영속성컨텍스트가 관리하던 영속상태의 엔티티를 영속성컨텍스트가 관리하지 않으면 준영속상태가 된다.

 

image-20210802183401221

 

 

영속성 컨텍스트의 이점

애플리케이션과 데이터베이스 사이에 중간 계층이 있다고 생각하면 됨.

중간에 뭐가 있으면 좋은 점이 버퍼링을 할 수도 있고 캐싱을 할 수 도 있기 때문이다.

 

 

영속성 컨텍스트는 내부에 캐시를 가지고 있는데 이것을 1차 캐시라고 한다.

영속 상태의 엔티티는 모두 이곳에 저장된다.

쉽게 이야기하면 영속성 컨텍스트 내부에 Map이 하나 있는데 키는 @Id로 매핑한 식별자고 값은 엔티티 인스턴스다.

영속성 컨텍스트 1차 캐시

위 그림의 코드를 실행하면 위 그림의 처럼 1차 캐시에 회원 엔티티가 저장된다.

회원 엔티티는 아직 데이터베이스에 저장되지 않았다.

 

1차캐시의 키(@Id)식별자 값이다. 그리고 식별자 값은 데이터베이스 키본 키와 매핑되어 있다.

따라서 영속성 컨텍스트에 데이터를 저장하고 조회하는 모든 기준은 데이터베이스 기본키 값이다.

 

 

이렇게 되면 위처럼 멤버객체를 저장을 해 놓고 조회할 때 

JPA는 find("member1")로 먼저 1차캐시에 가서 뒤진다.(디비에 가서 뒤지는 것이 아님)

1차캐시에 멤버 엔티티가 있으면 캐시에 있는 값을 그대로 조회해온다.

 

1차 캐시에 없어 데이터베이스 조회

1차캐시엔 없는 member2를 조회할때

1차캐시에 찾으려는 값이 없으면 DB에서 조회한다.

DB에서 조회한 값을 1차캐시에 저장한다

그 후 그 값을 반환한다.

그 이후 member2를 또 다시 조회한다면 DB를 조회하지 않고 1차캐시값에서 조회할 수 있다.

 

 

 1차 캐시

: 맵에 저장하면 먼저 1차캐시에 가서 값을 찾는다.

엔티티매니저는 데이터베이스 트랜젝션 단위로 만들고 끝나면 종료한다.

고객의 요청이 들어와 비지니스가 끝나버리면 캐시가 다 지워진다.

데이터베이스에서 조회를 하면 데이터베이스트랜젝션 단위로 ..

데이터베이스 트랜잭션 안에서만 효과가 있다.

그렇기 때문에 대단히 성능의 효과를 얻진 못한다.

 

 

 //비영속
  Member member = new Member();
  member.setId(101L);
  member.setName("HelloJPA");

  //영속
  System.out.println("=== BEFORE ===");
  em.persist(member);
  System.out.println("=== AFTER ===");

Member findMember = em.find(Member.class, 101L);

System.out.println("findMember.id =" + findMember.getId());
  System.out.println("findMember.id =" + findMember.getName());

 

console

before / after가 지나고

findMember 값을 찍는 조회를 했는데 select쿼리가 나오지 않았다.

왜냐하면 member값이 1차캐시에 저장되니까

그리고 나는 똑같은 PK로 조회를 했기 때문에

값을 DB에서 가져오는 것이 아니라 1차캐시에서 먼저 찾아서 가지고 오는 것.

 

 

똑같은걸 두번 조회할 땐 두번째 부터는 1차캐시에서 가지고 온다.

 


영속 엔티티의 동일성(identity) 보장

영속엔티티의 동일성을 보장해준다.

Member a = em.find(Member.class, member1");
Member b = em.find(Member.class, member1");

System.out.println(a == b); //동일성 비교 true

em.find(Member.class, "member1")를 반복해서 호출해도 영속성 컨텍스트는 1차캐시에 있는 같은 엔티티 인스턴스를 반환한다.

따라서 둘은 같은 인스턴스고 결과는 당연히 참이다.

따라서 JPA의 연속성 컨텍스트는 성능상 이점과 엔티티의 동일성을 보장한다. (같은 트렌젝션 안에서)

 

= 1차 캐시로 반복 가능한 읽기(REPEATABLE READ)등급의 트랜잭션 격리수준을

데이터베이스가 아닌 애플리케이션 차원에서 제공

 

 

트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)

영속성컨텍스 안에는 1차캐시도 있지만 쓰기지연 SQL저장소라는 곳도 있다.

JPA가 A를 1차캐시에 저장하고 쿼리를 생성해서 쓰기지연 저장소에 쌓아둔다.

B를 넣으면 이때도 1차캐시에 저장해서 쓰기지연 SQL저장소에 쌓아둔다,

커밋하면 쓰기지연 SQL에 있던 애들이 Flush되면서 날라가고 실제 DB저장소에 저장된다

EntityTransaction tx = em.getTransaction();/*트렌젝션 생성 */
        //엔티티 매니저는 데이터 변경시 트랜젝션을 시작해야 한다.
        tx.begin(); /*트렌젝션 시작*/


            //영속

            Member member1 = new Member(150L, "A");
            Member member2 = new Member(160L, "B");

            em.persist(member1);
            em.persist(member2);
            //여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
            System.out.println("=================");

            //커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
            tx.commit(); /*커밋*/

엔티티매니저는 트랜젝션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 INSERT SQL을 차곡차곡 모아둔다. 그리고 트랜젝션을 커밋할 때 모아둔 쿼리를 데이터베이스에 보내는데 이것을 트랜젝션을 지원하는 쓰기 지연이라고 한다.

 

 

 

쓰기지연 회원 A 영속

영속성컨텍스트에는 1차캐시도 있지만 쓰기지연저장소도 존재한다.

persist(memeberA)로 회원A를 영속화하면 1차 캐시에 회원 엔티티를 저장하면서

동시에 JPA가 INSERT SQL을 만들어 쓰기지연 SQL 저장소에 쌓아둔다.

 

 

 

쓰기지연 회원 B 영속

memberB를 영속했다.

마찬가지로 회원 엔티티 정보로 등록쿼리를 생성해서 쓰기지연 SQL 저장소에 보관한다.

현재 쓰기 지연 INSERT SQL 저장소에는 등록 쿼리가 2건 저장되었다.

 

 

쓰기지연, 커밋

이 쿼리들이 DB로 날라가는건

commit을 해야지 flush가 되면서 DB로 날라간다.

 

flush란 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업인데 이때 등록, 수정, 삭제한 엔티티를 데이터베이스에 반영한다.

 

좀 더 구체적으로 이야기하면 쓰기 지연 SQL 저장소에 모인 쿼리를 데이터베이스로 보낸다.

이렇게 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화 한 후에 실제 데이터베이스 트랜젝션을 커밋한다.

 

//영속
Member member1 = new Member(150L, "A");
Member member2 = new Member(160L, "B");

em.persist(member1);
em.persist(member2);

System.out.println("======");

tx.commit

console

콘솔창을 보면 "====" 이후에 쿼리문이 나오는 것을 알 수 있다. -> 커밋 후 SQL생성

 

 

 

 

 


 

엔티티 수정 - 변경 감지(Dirty Checking)

  • 엔티티 수정

Member member = em.find(Member.class, 150L); //찾아와서
member.setName("ZZZZZ"); //값만 변경

jpa는 변경감지라는 기능으로 엔티티를 변경할 수 있는 기능이 제공된다

마치 우리가 생각하기에는 엔티티의 값을 바꾸려면 값을 바꾸고

update해달라는 코드를 써야할 것 같지만 우리는 값만 바꿔주고 저장하라는 코드를 작성하지 않아도 된다.

 

트랜젝션 커밋 직전에 주석으로 처리된 em.updqte()메소드를 실행해야할 것 같지만 이런 메소드는 없다.

이렇게 엔티티의 변경사항을 데이터베이스에 자동으로 반영하는 기능을 변경감지라고한다.

(실행해보면 콘솔창에서 Select쿼리로 조회하고 Update쿼리가 알아서 실행됨.. 캡쳐뜬게 없음 ㅠㅠ)

 

변경감지

JPA는 커밋을 하면 내부적으로 flush()가 발생된다.

플러시가 발생되면 엔티티와 스냅샷을 비교한다.

 

1차캐시 안에는 pk, entity, 스냅샷(내가 값을 읽어온 최초 시점을 스냅샷으로 떠놓음)이 있다.

 

내가 값을 변경해서 커밋되는 시점에 jpa가 1차캐시안의 값을 다 비교한다.

비교해보고 값이 바껴있으면 sql저장소에 만들어둔다. 그리고 이 쿼리를 데이터베이스저장소에도 반영한다.

 

 


엔티티삭제

//삭제 대상 엔티티 조회 

Member memberA = em.find(Member.class, “memberA"); 
em.remove(memberA); //엔티티 삭제

엔티티를 삭제하려면 먼저 삭제 대상 엔티티를 조회해야 한다.

em.remove()에 삭제 대상 엔티티를 넘겨주면 엔티티가 삭제된다.

 

엔티티를 즉시 삭제하는 것이 아니라 엔티티 등록과 비슷하게 삭제 쿼리를 쓰기지연 SQL저장소에 등록했다가

이 후 트랜젝션을 커밋해서 플러시를 호출하면 실제 데이터베이스에 삭제쿼리가 전달되면서 삭제된다.

 

참고로 em.remove(memberA)를 호출하는 순간 memberA는 영속성 컨텍스트에서 제거된다.

이렇게 삭제된 엔티티는 재사용하지 않고 자연스럽게 가비지 컬렉션의 대사이 되도록 두는 것이 좋다.

 

 

 

 

 

[출처] 김영한님의 ORM 표준 JPA 프로그래밍 - 기본편

(https://www.inflearn.com/course/ORM-JPA-Basic/dashboard)

자바 ORM 표준 JPA 프로그래밍 서적 

 

댓글수0