Persistence Context특징Cache1차 캐쉬2차 캐쉬(= 공유 캐시)EntityManagerFactoryEntity ManagerContainer-Managed EntityManagerApplication-Managed EntityManagerEntity life cycle비영속상태(new, transient)영속상태(managed)준영속상태(detached) — detached 객체를 persist해버리면 에러 발생[참고]삭제(removed)repository.save() — persist 🆚 mergeCustomer Entity를 통한 영속성컨텍스트 이해EntityManager와 EntityManagerFactory 주입받아 이용하기저장Lazy Writerhibernate의 batch size 옵션조회1차 캐시를 이용한 조회DB를 이용한 조회수정(Dirty Checking)삭제
중요한점
- 영속성 컨텍스트는 트랜잭션을 시작할때 생성되고 트랜잭션이 종료될때 없어짐
- 하나의 트랜잭션 안에서 findById로 조회할 때, 해당 값을 보관하고 있음
- findByEmail 등 다른 것으로 조회 시, 조회마다 쿼리 발생하지만 findById로 조회하여 cache처리 되어 있으면 쿼리가 첫번째에만 발생하고 그 후에는 생기지 않음
Persistence Context
- 보통 메모리에 존재하는 데이터는 서비스가 종료되면 사라지는데, 그러한 데이터를 사라지지 않고 지속적으로 처리하는 방법은 파일 혹은 db.
- 영속성 컨텍스트는 JPA 컨테이너 안에서 동작을 하는 entity의 맥락을 관리하는 주체
- 엔티티를 영구 저장하는 환경
- JPA를 이용하는데 가장 중요한 요소임
- 영속성 컨텍스트에 실질적으로 가장 많은 역할을 하는 것이 EntityManager
- JPA를 J2EE나 스프링 프레임워크 같은 컨테이너 위에서 실행하면 트랜잭션을 시작할 때
영속성 컨텍스트를 생성
하고 트랜잭션을 종료할 때영속성 컨텍스트도 종료
함 (OSIV 를 사용하면 요청의 시작부터 끝까지 같은 영속성 컨텍스트를 유지)

- EntityManager가 persist( ) 를 호출하게 되면 entity가 Persistence Context에서 관리되게 됨
특징
- 영속성 컨텍스트와 식별자 값
- 영속성 컨텍스트 안에서 관리되는 엔티티는 식별자 값을 반드시 가져야한다.
- 그 이유는 key-value로 엔티티를 관리하기 때문이다.
- 1차 캐시에서도 보면 @Id와 Entity이 key-value 형태로 entity를 찾게 됨
- 영속성 컨텍스트와 데이터 베이스 저장
- JPA는 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 DB에 반영한다. (FLUSH)
- 플러시(flush)는 영속성 컨텍스트의 변경 내용을 DB에 동기화하는 작업인데, 이때 등록, 수정, 삭제한 엔티티를 DB에 반영한다.
- 영속성 컨텍스트가 엔티티를 관리함으로 얻는 이점
- 1차 캐시
- 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연(Lazy Writing)
- 변경 감지(dirty checking)
- 지연 로딩
Cache

- 하나의 트랜잭션 안에서 findById로 조회시, 해당 값 보관하고 있음
- 1차 캐쉬는 맵의 형태로 만들어짐 (key는 id 값, value 는 해당 entity)
- findByEmail 등 다른 것으로 조회 시, 조회마다 쿼리 발생하지만 findById로 조회하여 cache처리 되어 있으면 쿼리가 첫번째에만 발생하고 그 후에는 생기지 않음
- DB에 반영되는 시점
- flush() 를 호출하는 시점
- Transaction이 끝나서 해당 쿼리가 커밋되는 시점
- 복잡한 조회조건의 JPQL query가 실행되는 시점
- dirty checking : Transaction 안에서 entity의 변경이 일어나면 캐시에 저장되어 있는 snapshot과 비교하여 entity의 변경 내용을 자동으로 데이터베이스에 반영하는 JPA의 특징임
1차 캐쉬
- 영속성 컨텍스트 내부에 엔티티를 보관하는 저장소.
1차 캐시는 트랜잭션이 시작하고 종료할 때까지만 유효함(Persistence context flush 혹은 clear 하기 전까지는 1차 캐시가 비워지지 않음)
- Entity Manager로 조회하거나 변경하는 모든 엔티티는 1차 캐시에 저장됨
- 트랜잭션을 Commit하거나 flush 하게되면 1차 캐시에 있는 entity 변경 사항들 db반영함
2차 캐쉬(= 공유 캐시)
- 2차 캐시는 애플리케이션 범위의 캐시이고 애플리케이션을 종료할 때까지 캐시가 유지됨
- 2차 캐시는 데이터베이스 기본 키를 기준으로 캐시하지만 영속성 컨텍스트가 다르면 객체 동일성(a==b)을 보장하지 않는다.
EntityManagerFactory
- Entity를 관리하는 EntityManager를 생산하는 공장
- Thread Safe함
- 하나만 만들어짐. 꽤나 무거운 객체이기 때문에
Entity Manager

- EntityManager는 Entity를 저장하고 수정하고 삭제하고 조회하는 (CRUD) 등 Entity와 관련된 모든 일을 처리함
- Thread Safe하지 않음. 여러 Thread에서 동시에 접근할 경우 동시성 이슈가 발생함
- EntityManager Factory가 각각의 request별 Thread안에 각각 만들어주게 됨. 그리고 db와의 작업을 위해서 connection을 획득함
- JPA의 구현체들은 EntityManagerFactory를 생성할 때 커넥션풀을 만듦
- Repository를 이때까지 만들어서 사용했지만 SimpleJpaRepository안에 EntityManager를 통하여 쿼리를 만들고 데이터를 불러옴. custom하게 하고 싶으면 EntityManager를 가지고 변형해야함
- Hibernate에서는 EntityManager를 session이라고 함
- 기본적으로 EntityManager에는 두가지 종류가 있다
- Container Managed EntityManager
- Application Managed EntityManager
Container-Managed EntityManager
- Spring에서는 EntityManager를 Proxy를 통해 감싸고 EntityManager를 사용할 때 Proxy를 통해 EntityManager를 생성함(Autowire로 EntityManager를 받으면 SharedEntityManagerCreator가 proxy를 통해 생성을 해줌 → 여러 스레드에서 동시 접근해도 Thread Safety를 보장해줌)
- 스프링 컨테이너는 트랜잭션 범위의 영속성 컨텍스트 전략을 기본으로 사용함
- 트랜잭션이 시작할 때(tx.begin( )) 영속성 컨텍스트를 생성하고 트랜잭션이 끝날 때(tx.commit( )) 영속성 컨텍스트를 끝낸다.(by 컨테이너)
- 즉 @Transactional이 붙은 하나의 메서드를 시작하면서 영속성 컨텍스트 생성하고, 그 메서드가 끝나면 영속성 컨텍스트가 끝나는 것임!. 그 해당 메서드에 여러 request가 동시에 들어온다고 하더라도 별개의 트랜잭션이 생성되는 것이니 엔티티 매니저도 별개, 영속성 컨텍스트도 별개인 것!
- 트랜잭션이 같으면 같은 영속성 컨텍스트를 사용함(여러 EntityManager를 사용해도 한 트랜잭션으로 묶이면 영속성 컨텍스트를 공유함)
- 하나의 트랜잭션으로 묶인 메서드에 여러 request가 들어온다고 했을 때 하나의 트랜잭션으로 묶이니 영속성 컨텍스트는 공유한다는 것임
- 트랜잭션이 다르면 다른 영속성 컨텍스트를 사용함
- 같은 EntityManager를 사용해도 트랜젝션에 따라 접근하는 영속성 컨텍스트가 다르다
- 따라서 같은 EntityManager를 호출해도 접근하는 영속성 컨텍스트가 다르므로 멀티스레드에 안전함!
Application-Managed EntityManager
- 우리가 직접 생성했으니, 닫는 것도 우리의 책임임
Entity life cycle

비영속상태(new, transient)
- 영속성 context가 해당 entity 객체를 관리하지 않는 상태
- @Transient 를 필드에 붙이면 database에 만들지 않음
- 그냥 단순한 자바객체. 영속화되지 않고 garbage collector에 의해 사라짐
영속상태(managed)
entityManager.persist(user);
로 객체를 persistence context에서 관리되도록 했을 때, 영속상태로 전환
- 영속상태인 경우
- 변경 감지(dirty check)
- 조회에 대한 1차 캐쉬(id로)
- 쓰기 지연
준영속상태(detached) — detached 객체를 persist해버리면 에러 발생[참고]
삭제(removed)
- delete 쿼리임
em.remove(customer)
— 영속성 컨텍스트에서 분리하고, DB에서도 삭제함
repository.save() — persist 🆚 merge
- save를 호출했을 시, isNew를 통해 persist와 merge로 분기를 하게 됨
- isNew는 primitiveType이면 0인지 확인하고, referenceType이면 null인지 확인함
- setId를 호출 시
- id값이 있으니 merge를 호출 → parameter와 return instance가 같지않음
- id값이 없으면 persist를 호출 → parameter instance와 return instance가 같음
- merge호출 전에 detach 하고 나서 하면 반환되는 값 다름
- Entity 새거 = em.merge(옛날거)
- 옛날거 = 준영속 여전히
- 그냥 반환된 객체를 이용하기! 헷갈리니까
Customer Entity를 통한 영속성컨텍스트 이해
- 데이터를 조작하기 위해서는 Transaction begin, commit 안에서 하기. CUD
EntityManager와 EntityManagerFactory 주입받아 이용하기
[ StackOverflow ] PersistenceUnit vs PersistenceContext
@PersistenceContext
: EntityManager를 주입해줌
@PersistenceUnit
: EntityManagerFactory를 주입
@PersistenceContext
를 이용하여 위처럼 사용하면 예외 발생대신
@PersistenceUnit
을 통해 EntityManagerFactory를 주입받고 걔를 이용해 entityManager를 생성하여 사용하면 됨저장

- Entity를 Persistence Context에서 관리하기 위해서 항상 Transaction을 시작하고 Transaction을 끝내야 함
- 그리고 Entity가 Persistence Context에서 관리되기 시작하면
- 쓰기 지연 저장소에 쿼리 저장(Insert into ... 쿼리)
- 1차 캐시에 key-value형태로 해당 Entity저장
- Transaction commit할 시,
- em.flush()
- 쓰기 지연 저장소에 있던 INSERT 쿼리를 DB에 적용
Lazy Writer
- query가 생성될 때마다 바로 DB에 보내지 않고, 어느정도 query를 쌓은 뒤 한번에 보내는 것을 쓰기 지연이라 함
- 한번에 많은 query를 보내 DB connection 시간을 줄일 수 있고 한 transaction이 table에 접근하는 시간을 줄일 수 있는 장점이 있다.
- CUD 쿼리를 쌓아뒀다가 한번에 보냄. DB에 데이터를 업데이트 하는 쿼리들
hibernate의 batch size 옵션
위 옵션을 추가하면 value 만큼 쿼리를 쌓은 다음에 DB로 한번에 query들을 보내 commit 한다.
조회
1차 캐시를 이용한 조회

DB를 이용한 조회

수정(Dirty Checking)

- 1차 캐쉬에서 스냅샷을 관리함
- flush( ) 됐을 때, 1차 캐시의 스냅샷과 비교하여 변경사항이 있는지 확인함
- 변경사항이 있으면 update Query를 수행
- 변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용이 됨
- em.clear() 로 영속성 컨텍스트에서 지워버리면 dirty checking 당연히 수행안됨. 비교할 대상(영속성 컨텍스트에 존재하는 snapshot)이 없으니까. 그리고 entity 또한도 detach 되어버리고