[JPA] 영속성

Seongho·2023년 3월 22일
1

JPA

목록 보기
1/3

엔티티 매니저

EntityManager는 클라이언트의 요청에 따라 엔티티에 관련된 모든 일을 처리한다.
EntityManager는 EntityManagerFactory에서 생성하는데,

EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");     //(1)
EntityManager em = emf.createEntityManager();	 //(2)       

(1)에서 META-INF/persistence.xml 에 있는 정보에 따라 EntityManagerFactory 를 생성한다. EntityManagerFactory는 애플리케이션 전체에서 하나만 생성하여 애플리케이션 전체가 공유하며 사용한다.
EntityManager는 쓰레드간에 공유할 수 없다.

영속성 컨텍스트

엔티티를 영구 저장하는 환경이다. 애플리케이션과 데이터베이스 사이에서 엔티티를 보관하는 가상의 데이터베이스 같은 역할을 하는데, 엔티티 매니저가 영속성 컨텍스트에 접근하여 엔티티를 관리한다.

EntityManager.persist(entity);		//영속성 컨텍스트에 저장

엔티티 생명주기

  • New (비영속) : 엔티티 객체가 생성된 상태
  • managed (영속) : 영속성 컨텍스트에 관리되는 상태. 데이터베이스에 저장된 상태는 아님.
  • detached (준영속) : 영속성 컨텍스트에 저장되었다가 분리된 상태
  • removed (삭제) : 영속성 컨텍스트와 데이터베이스에서 삭제된 상태

비영속

Member member = new Member();
member.setId(1L);
member.setName("memberA");

객체를 생성하고 em.persist(member); 하기 전의 상태

영속

em.persist(member);

비영속 상태에서 영속성 컨텍스트에 엔티티 객체를 저장한 상태

준영속

영속 상태의 엔티티를 영속성 컨텍스트에서 분리시킨 상태이다. JPA가 엔티티를 더이상 관리하지 않기 때문에 데이터베이스에 값이 업데이트되지 않는다.

Member member = em.find(Member.class, 1L);
member.setName("memberAAAA");				
//
em.detach(member);		//준영속 
//
tx.commit();

위 코드에서 원래는 setName으로 영속상태 객체의 데이터를 변경하면 update 쿼리가 쓰기지연 SQL 저장소에 저장되고, commit에서 쿼리가 flush 되는데, 중간에 member를 준영속 상태로 만들면, 영속성 컨텍스트에서 더이상 member 객체를 관리하지 않기 때문에 update 쿼리가 날아가지 않는다.

준영속 상태로 만드는 방법

  1. em.detach(member) : 해당 엔티티를 준영속 상태로 전환
  2. em.clear() : 영속성 컨텍스트를 초기화

영속성 컨텍스트의 이점

1차 캐시 사용


영속성 컨텍스트는 내부에 1차 캐시를 갖고 있는데, 1차 캐시에는 엔티티의 PK가 key, 엔티티 자체가 value로 매핑되어 저장된다. 만약 member2를 조회한다면, 엔티티 매니저는 1차 캐시에서 member2를 찾고, 만약 1차 캐시에 없으면 데이터베이스에서 찾는다. 데이터베이스에서 엔티티를 가져와 1차 캐시에 저장한 후, 1차 캐시에서 엔티티를 반환한다.
만약, 같은 데이터를 100번 조회한다고 하면, 데이터베이스에 select 쿼리를 100번 날리는게 아니라, 처음에 한번 조회하여 1차 캐시에 넣고, 계속 사용할 수 있다는 이점이 있다.

그런데,

엔티티 매니저는 트랜잭션 단위로 만들어지고, 트랜잭션이 끝나면 종료된다. 이 말은, 고객의 요청이 하나 들어와서 비지니스를 수행하고 끝나버리면, 이 영속성 컨텍스트도 사라진다는 것이다. 따라서, 1차 캐시는 애플리케이션 전체가 공유하는 것이 아니기 때문에 아주 찰나의 순간 이득을 보기 위해 사용하는 것이다. 물론, 비지니스 로직이 매우 복잡한 경우에는 도움이 될 수도 있다.

동일성 보장

Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
//
System.out.println(a == b); 	//동일성 비교 -> true

같은 트랜잭션* 내에서 영속 엔티티의 동일성을 보장한다. (당연)

**트랜잭션 : 데이터베이스 접근 동작의 최소 단위. 예를 들면, A(30000원 있음)가 B(30000원 있음)에게 10000원을 송금하는 상황에서, A는 20000원이 되고 B는 40000원이 되어야 한다. 이렇게 이렇게 어떤 깨져서는 안되는 원자성을 지닌 동작의 단위를 트랜잭션이라 한다. 데이터베이스에 커밋이나 롤백이 되어야 트랜잭션이 끝난다.

쓰기 지연



위 예제에서,
멤버A를 persist 하면 JPA가 insert 쿼리를 생성하고 쓰기지연 SQL저장소에 저장한다.
멤버B를 persist 하면 JPA가 insert 쿼리를 생성하고 쓰기지연 SQL 저장소에 저장한다.
그리고 트랜잭션 커밋 하는 시점에 쓰기지연 SQL 저장소에 있던 SQL문이 한번에 flush된다.

Member member1 = new Member(3L, "a");
Member member2 = new Member(4L, "b");
//
em.persist(member1);
em.persist(member2);            //이 순간까지 쓰기지연 SQL 저장소에 저장된다.
//
System.out.println("============================"); 	//이 이후에 DB에 쿼리 전송됨.

META-INF/persistence.xml에

<property name="hibernate.jdbc.batch_size" value="10"/>

batch_size 옵션을 지정함으로써 SQL 쿼리를 최대 몇 개까지 저장하여 한번에 DB에 보낼지 설정할 수 있다.

변경 감지

생각해보면, 영속 엔티티를 수정하면, 자동으로 데이터베이스 데이터도 수정된다. em.update() 같은 코드가 없는데도 말이다.

영속성 컨텍스트 안에는 스냅샷이라는 공간도 있다. 스냅샷에는 엔티티가 처음 데이터베이스에서 1차캐시로 저장될 때, 저장되는데, flush가 일어나면 엔티티 매니저는 1차 캐시 엔티티와 스냅샷을 비교하여, 다를 경우, update 쿼리를 생성하여 쓰기 지연 저장소에 저장하고 데이터베이스에 update 쿼리를 날린다.

flush

쓰기 지연 SQL 저장소에 쌓여있던 SQL 쿼리를 데이터베이스에 반영하는 과정이다. 즉, 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 과정이다. flush 다음에 commit이 일어난다.

Member member = new Member(5L, "c");
em.persist(member);         //여기까지 하면 쿼리가 저장소에 담겨있고
//
em.flush();                 //플러시를 통해 쿼리를 디비에 먼저 반영할 수 있음. 플러시
//
System.out.println("================");

플러시 방법
1. em.flush() : 직접 호출하여 flush
2. transaction.commit() : 자동 호출
3. JPQL 쿼리 실행 : 자동 호출
3에서 flush가 일어나는 이유는 아래와 같은 상황에서 만약, JPQL 쿼리 실행시 flush가 호출되지 않으면, 데이터베이스에 영속성 컨텍스트에 저장된 데이터가 저장되지 않았기 때문에 원하는 결과가 나오지 않을 수 있다.

em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//
//중간에 JPQL 실행
query = em.createQuery("select m from Member m", Member.class);
List<Member> members= query.getResultList();
profile
Record What I Learned

0개의 댓글