영속성
JPA
JPA에서의 영속성은 위의 단어 해석처럼 Entity를 영구적으로 저장해주는 환경을 의미한다.
EntityManagerFactory와 EntityManager
데이터베이스를 하나만 사용하는 어플리케이션은 보통 EntityManagerFactory를 하나만 생성한다. EntityManagerFactory는 여러 EntityManager를 생성하는 객체이다. 두 객체의 차이점은...
* EntityManagerFactory : 생성하는데 비용이 크기 때문에 어플리케이션 전체에서 한 번만 생성해 공유하도록 설계되어 있다.
: 여러 스레드가 동시에 접근해도 안전하다. 따라서 서로 다른 스레드 간에 공유가 가능하다.
* EntityManager : 생성하는데 비용이 거의 들지 않는다.
: 여러 스레드가 동시에 접근하면 동시성 문제가 발생하기 때문에 스레드 간에 절대 공유하지 않는다.
참고로 EntityManager는 데이터베이스 연결이 꼭 필요한 시점까지 (보통 트랜잭션을 시작할 때) 커넥션을 얻지 않는다. EntityManagerFactory와 EntityManager를 생성하는 코드는 아래와 같다.
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("person"); // 파라미터 영속성 단위 이름
EntityManager entityManager = entityManagerFactory.createEntityManager();
영속성 컨텍스트란?
영속성 컨텍스트(persistence context)는 '엔티티를 영구 저장하는 환경'이라는 뜻이다. EntityManager를 이용해 Entity를 저장하거나 조회할 때 EntityManager는 영속성 컨텍스트에 Entity를 보관하고 관리한다. EntityManager객체.persist(Entity객체) 를 실행하면 영속성 컨텍스트가 Entity를 관리한다.
영속성 컨텍스트는 눈에 보이지 않는 논리적인 개념이다. 또한 EntityManager를 하나 생성할 때 하나가 만들어지며, EntityManager를 통해 접근할 수 있고 관리할 수 있다.
다음은 영속성 컨텍스트의 특징
1. 영속성 컨텍스트는 Entity를 식별자 값으로 구분한다.
Entity에서 @Id 어노테이션을 통해 지정한 멤버변수가 영속성 컨텍스트에 식별자 값으로 저장된다.
2. jpa는 보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 Entity를 데이터베이스에 반영한다.(이런 과정을 flush라 한다.)
3. 1차캐시를 이용한다.
영속성 컨텍스트 내부에 존재하는 캐시(Map)를 1차 캐시라 한다. 영속 상태의 Entity는 모두 이곳에 저장되며 키는 @Id로 매핑한 식별자이며 값은 Entity 인스턴스이다. entityManager.find() 메소드를 호출하면 먼저 1차 캐시에서 Entity를 찾고, 만약 찾는 Entity가 1차 캐시에 없으면 데이터베이스에서 조회한 후 1차 캐시에 저장하고 영속상태인 해당 객체를 반환한다.
4. 객체의 동일성을 보장한다.
Person devjones = new Person(1, "devjones"); // id, name
entityManager.persist(devjones);
Person devjones1 = entityManager.find(Person.class, 1);
Person devjones2 = entityManager.find(Person.class, 1);
devjones1 == devjones2 // true
이와 같이 1차캐시에 있는 같은 Entity 인스턴스를 반환하기 때문에 Entity의 동일성을 보장한다.
5. 트랜잭션을 지원하는 쓰기 지연을 수행한다.
EntityManager는 트랜잭션을 commit하기 직전까지 데이터베이스에 Entity를 저장하지 않고 영속성 컨텍스트 내부의 SQL 저장소에 생성 쿼리를 저장해둔다. 이후 commit을 하게 되면 저장해두었던 쿼리를 데이터베이스에 보낸다. 이것을 트랜잭션을 지원하는 쓰기지연이라고 한다.
트랜잭션을 commit하면 EntityManager는 영속성 컨텍스트를 flush()한다. flush() 란 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업으로 등록, 수정, 삭제한 Entity를 데이터베이스에 반영한다. 즉, 쓰기 지연 SQL 저장소에 모인 쿼리를 데이터베이스에 보낸다. 이러한 동기화 작업을 거친 후 실제 데이터베이스 트랜잭션을 commit 한다.
6. 변경을 감지한다.
영속성 컨텍스트에는 이전 flush() 때의 Entity 상태를 복사해서 저장해둔 스냅샷이 존재한다. JPA는 flush() 시점에 스냅샷과 Entity를 비교해 변경된 Entity를 찾는다. 만약 있다면 각각의 객체에 대한 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 저장한 후 한꺼번에 데이터베이스로 보내고 데이터베이스 트랜잭션을 commit한다.
여기서 수정 쿼리는 Entity의 모든 필드를 업데이트한다. 이렇게 하면 수정 쿼리를 애플리케이션 로딩 시점에 미리 생성해두고 재사용할 수 있으며, 데이터베이스에 동일한 쿼리를 보낼 때 데이터베이스가 이전에 한 번 파싱된 쿼리를 재사용하기 때문에 성능상 장점이 된다.
변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용된다. 바꿔말하면, 준영속 상태의 객체는 아무리 수정해도 영속성 컨텍스트가 변경을 감지하지 못 한다.
7. 지연 로딩을 수행한다.
지연 로딩(Lazy Loading)이란 실제 객체 대신 프록시 객체를 로딩해두고 해당 객체를 실제 사용할 때 영속성 컨텍스트를 통해 데이터를 불러오는 방법이다.
================
참고로 EntityManager는 데이터베이스 연결이 꼭 필요한 시점까지 (보통 트랜잭션을 시작할 때) 커넥션을 얻지 않는다.
Entity 생명주기
Entity는 4가지 상태가 존재한다.
1. 비영속(new)
- 영속성 컨텍스트와 전혀 관계가 없는 상태
- 상태 : 객체 생성
Person devjones = new Person("devjones");
2. 영속(managed)
- 영속성 컨텍스트에 저장된 상태
- 상태 : 객체 생성 후 EntityManager를 통해 EntityManager를 통해 영속성 컨텍스트에 저장
entityManager.persist(devjones);
3. 준영속(detached)
- 영속성 컨텍스트에 저장되었다가 분리된 상태
- 상태 : 영속성 컨텍스트가 영속 상태였던 Entity를 관리하지 않음(3가지 방법)
(1)
entityManager.detach(devjones);
(2)
entityManager.closed();
// 영속성 컨텍스트 종료
(3)
entityManager.clear();
// 영속성 컨텍스트 초기화
- 준영속 상태가 되면 1차 캐시부터 쓰기 지연 SQL 저장소까지 해당 엔티티를 관리하기 위한 모든 정보가 제거됨
- 특징
ㄱ. 영속성 컨텍스트가 제공하는 어떠한 기능(1차 캐시 등)도 동작하지 않는다.
ㄴ. 이미 한 번 영속 상태였기 때문에 반드시 식별자 값을 가지고 있다.
ㄷ. 지연 로딩 시 문제가 발생한다.
4. 삭제(removed)
- 삭제된 상태
- 상태 : 영속성 컨텍스트와 데이터베이스에서 Entity 삭제
entityManager.remove(devjones);
준영속 상태를 영속 상태로(merge)
준영속 상태인 Entity를 다시 영속상태로 변경하려면 병합(merge)을 이용한다.
merge() 메소드를 수행하면 이전 Entity인 devjones가 영속 상태로 변경되는 것이 아니라 새로운 영속 상태의 Entity를 반환한다. 따라서 devjones와 mergedDevjones는 같은 객체가 아니다.
파라미터로 넘어온 devjones를 식별자 값인 1로 1차 캐시에서 Entity를 조회한다. 만약 1차캐시에 Entity가 없으면 데이터베이스에서 Entity를 조회하고 1차 캐시에 저장한다. 만약 데이터베이스에도 없다면 새로운 Entity를 생성해서 병합한다.
조회한 영속상태인 Entity(위의 코드에서 mergedDevjones)에 devjones 엔티티의 값을 채워넣는다. 그리고 난 후 mergedDevjones를 반환한다.
이렇게 되면 devjones객체는 merge()후 에도 준영속 상태로 남아 있다.
flush()
flush()는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.
1. 변경감지가 동작해 영속성 컨텍스트에 있는 모든 Entity를 스냅샷과 비교한 후 수정된 Entity를 찾는다. 수정된 Entity는 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 저장한다. (등록, 수정, 삭제 쿼리)
2. 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송한다.
flush()를 호출하는 방법 3가지
1. 직접 호출
EntityManager의 flush()를 직접 호출해 강제로 플러시하는 방법이다. 거의 사용x
2. 트랜잭션 commit 시 플러시 자동호출
JPA는 트랜잭션을 commit할 때 자동으로 flush()를 호출한다.
3. JPQL 쿼리 실행 시 플러시 자동 호출
JPQL 같은 객체지향 쿼리를 호출할 때도 쿼리 실행 직전에 플러시가 자동으로 실행된다.
출처:
hyeooona825.tistory.com/87