JPA에서 가장 중요한 2가지가 있다. 

1. 객체와 관계형 데이터베이스 매핑하기 (Object Relational Mapping)

2. 영속성 컨텍스트

영속성 컨텍스트에 대해서 공부한 내용을 정리해보자.

 

 

 

본 정리 내용은 "김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편" 을 듣고 정리한 내용이며, 설명에 필요한 사진 중 중요한 사진이라고 생각한 사진을 강의자료에서 가져왔음을 출처로서 밝힙니다.

 

 

 

영속성 컨텍스트란?

- 번역을 하자면, "엔티티를 영구 저장하는 환경" 이라는 뜻이다.
- 영속성 컨텍스트는 논리적인 개념으로 눈에 보이지 않는다. 엔티티 매니저(Entity Manager)를 통해서 영속성 컨텍스트에 접근한다.
- 엔티티 매니저와 영속성 컨텍스트가 1:1 생성된다.

 

 

 

 

 

엔티티의 생명주기

 

1. 비영속(new/transient)

- 영속성 컨텍스트와의 전혀 관계과 없는 새로운 상태를 뜻한다.

- 그냥 객체를 생성해서 세팅만 한 상태, 이거는 JPA와 전혀 관계가 없는 상태이다.

 

 

 

 

2. 영속(managed) 

- 영속성 컨텍스트에 관리되는 상태를 뜻한다. 

 

- 객체를 생성해서 엔티티매니저를 얻어와서 em.persist(memebr); 하면 객체를 영속 컨텍스트에 member가 들어가면서 영속 상태가 된다. 

- 중요한점은 em.persist(memebr); 한다고 DB에 쿼리가 날라가는 것이 아니다. ts.commit(); 하는 순간 날라간다.

 

 

 

3. 준영속(detached) 

- 영속성 컨텍스트에 저장되었다가 분리된 상태이다.

 

 

 

 

 

 

4. 삭제(removed) 

- 객체가 삭제되는 상태이다.

 

 

 

 

 

 

 

 

영속성 컨텍스트의 이점

 

1. 1차 캐시

 

 

- em.persist(memebr); 하여 영속 상태가 되었다고 가정하자. 영속성 컨텍스트(EntityManager)에는 1차 캐시가 있다. 1차 캐시에는 @Id로 우리가 매핑한 pk값이 key값으로, member가 value값으로 저장이 된다. (Map형태로 저장이 된다.)

 

 

 

 

 

- 이후 조회를 한다고 가정하여 em.find(Member.class, "member1"); 하면 JPA는 DB를 먼저 찾는게 아니라 우선 영속성 컨텍스트에서 1차캐시를 뒤져서 캐시값을 조회하여 일치하면 바로 Entity값을 꺼내온다.

- 그런데 만약 em.find(Member.class, "member2"); 하여 1차 캐시에 없는 것을 조회하면, 1차적으로 1차 캐시에서 찾고 없으면 DB를 조회한다. 그 후 DB에서 조회한 member2를 1차 캐시에 다시 저장한다. 그 이 후 member2를 반환한다.

 

- EntityManager는 트랜젝션단위로 생성하고 지우기 때문에 1차 캐시로 인해 엄청난 이득을 보는것은 아니고, 찰나의 순간에만 이득이다.

 

 

 

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

- 1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공한다. (마치 자바 컬렉션에서 같은 레퍼런스를 바라보는 객체를 == 비교하면 같은 것처럼) 

 

- 출력의 결과는 true가 나온다. 이유는 1차 캐시를 사용하기 때문이다. em.find(Member.class, "member100");을 하면 먼저 1차 캐시를 뒤지지만 1차 캐시에는 해당 엔티티가 없으니 DB를 조회한다. DB에서 가져온 객체를 1차 캐시에 저장한 후 return 한다. (member1)

다시한번 em.find();를 했을 때에도 1차 캐시를 검색하는데 아까 위에서 member1에서 가져온 객체와 같은 객체가 캐시에 담겨있다. 그러므로 member2는 1차 캐시에서 꺼내온다. 같은 객체를 == 비교하면 당연히 참조값이 같기 때문에 출력결과는 true가 나온다. 

여기에서 알 수 있었던 점은 em.persist(); 해야만 무조건 영속성 컨텍스트에 저장되는 것이 아니라, 영속성 컨텍스트에 없는 객체라면 em.find();했을 때에도 DB에서 가져온 객체가 1차 캐시에 들어가서 영속상태가 될 수 있다.

 

 

 

3. 엔티티 등록 - 트랜잭션을 지원하는 쓰기 지연 (Buffering)

 

 

- em.persist(memebrA); 할 때까지는 Insert sql을 데이터베이스에 보내지 않는다. transaction.commit(); 하여 커밋하는 순간 데이터베이스에 쿼리를 날린다. 

 

- 영속 컨텍스트 안에는 "쓰기 지연 SQL저장소" 라는게 있다. persist(memberA);하면 memberA가 1차 캐시에 저장이 됨과 동시에 INSERT SQL을 생성하여 쓰기 지연 SQL저장소에 쌓는다. 그 후에 또 한번 persist(memberB); 하면 1차캐시에 저장이 되면서 SQL을 쓰기 지연 SQL저장소에 쌓는다.
transaction.commit();을 하는 시점에 쓰기 지연 SQL저장소에 있던 SQL들이 날아간다. (JPA에서는 flush()라고 한다. 플러시는 아래에서 설명)

 

 

 

4. 엔티티 수정 - 변경감지

 

- 데이터를 변경하는 상황에서 Member member = em.find(Member.class, "member100"); 하여 영속 엔티티를 조회하고, member.setName("hello"); 와 같이 이름을 변경하였다. update쿼리를 날리려면 em.update(member); 와 같은 코드를 사용하거나 em.persist(member); 하여 영속성 컨텍스트에 영속을 다시 해주어야 할까? 

 

 


아니다. 우리가 자바 컬렉션 이용할 때 get하여 객체를 얻고 변경 후에 다시 add하여 집어 넣어주나? JPA에서도 그렇게 하지 않아도 된다. 어떻게 이런일이 가능할 수 있을까? 

 

 

     4.1. Dirtiy Checking

- 이유는 바로 Dirty Checking때문이다.

 

 

- transaction.commit();을 하면 flush();가 호출이 되고, 엔티티와 스냅샷을 비교한다.

스냅샷은 1차 캐시안에 있는데 값을 최초로 읽어온 시점, 영속 컨텍스트에 값을 집어 넣든 DB에서 가져오든 최초 시점의 상태를 스냅샷으로 떠놓는 것이다. 비교하여 변경을 감지하고, 변경이 있으면 UPDATE SQL을 쓰기지연 SQL저장소에 쌓는다. 그 후 쿼리를 날려서 DB에 변경값을 반영(commit)한다.

 

- 간단하게 변경감지 메커니즘은 다음과 같이 정리할 수 있다.

플러시 발생 -> 변경감지(Dirty Checking) -> 수정된 엔티티 쓰기 지연 SQL저장소에 등록 -> 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송(등록, 수정, 삭제쿼리)

 

 

 

 

 

플러시 (flush)

- 플러시를 하면 영속성 컨텍스트의 변경내용을 데이터베이스에 반영한다.
- flush하면 1차 캐시나 영속성 컨텍스트가 지워질까? 아니다. 쓰기 지연 SQL 저장소에 있는 쿼리들을 DB에 반영하는 과정일 뿐이다. (영속성 컨텍스트의 변경내용을 데이터베이스에 동기화하는 것이다.)

 

 

- 영속성 컨텍스트를 플러시 하는 방법

1. em.flush() - 직접호출

 

- em.flush(); 직전까지의 상황은 member객체에 값을 set해주고 em.persist(member);를 하여 영속상태로 만들어주었다. 그러면 영속성 컨텍스트 내부에서는 1차 캐시에 member를 저장하고, INSERT SQL을 만들어서 쓰기 지연 SQL 저장소에 쿼리를 쌓고 있는 상태이다. 

ts.commit();하면 DB로 쿼리가 날라가는 상황인데, 커밋명령을 하기 전에 em.flush(); 를 직접 호출하여 쿼리를 바로 DB에 날릴 수 있는 것이다.

 

 

2. 트랙잭션 커밋 - 자동 호출

 

3. JPQL 쿼리 실행 - 자동 호출

 

 

- 이 상황에서 memberA, memberB, memberC가 조회가 될까? 당연히.. 아직 flush가 된 상황이 아니라 안된다. 그래서 JPQL쿼리를 실행하면 JPA는 자동으로 flush를 해주고 쿼리를 날린다. 

 

 

 

 

 

 

준영속상태

- 영속상태의 엔티티가 영속성 컨텍스트에서 분리(detach) 한 상태이다.


- 준영속 상태로 만드는 방법

1. em.detach(entity) - 특정 엔티티만 준영속상태로 전환

 

- em.find(); 해서 1차 캐시를 먼저 검색하지만 없다. DB에서 10L을 pk값으로 가지는 객체를 가져온다. 그 후에 em.detach(member);를 해주어서 준영속상태로 만들었다. 이렇게 하면 JPA가 관리하지 않는(영속성 컨텍스트에서 분리된) 엔티티가 되기 때문에 1차 캐시에서도 삭제된다.

 


2. em.clear() - 모든 엔티티를 준영속상태로 전환

- 영속성 컨텍스트에 있는 모든 엔티티를 준영속 상태로 전환한다.

 

3. em.close() - 영속성 컨텍스트를 종료

- 영속성 컨텍스를 종료한다.

 

 

+ Recent posts