-
영속성 컨텍스트의 이해JPA 2024. 7. 8. 00:31
[영속성 컨텍스트]
데이터베이스와의 중간 저장소 역할
트랜잭션이 진행되는 동안 해당 엔티티들을 메모리 상에 관리하고, 데이터베이스와의 동기화를 담당하는 곳
[주요 역할]
1. 엔티티 추적: 영속성 컨텍스트는 영속성 상태에 있는 엔티티들을 추적하여 해당 엔티티가 변경되면 이를 감지하고, 트랜잭션 커밋 시 변경 사항을 데이터베이스에 반영합니다.
2. 1차 캐시: 영속성 컨텍스트는 1차 캐시를 가지고 있어, 이미 조회된 엔티티는 메모리에서 제공하게 됩니다. 이로 인해 데이터베이스와의 불필요한 조회를 줄이고 성능을 개선할 수 있습니다.
3. 동기화: 트랜잭션이 끝날 때, 영속성 컨텍스트는 변경된 엔티티 상태를 데이터베이스에 동기화합니다. 이 과정에서 flush가 일어나며, 영속성 컨텍스트와 데이터베이스 간의 상태를 동기화합니다.
4. 트랜잭션 관리: 영속성 컨텍스트는 트랜잭션 범위 내에서 객체들을 관리하며, 트랜잭션이 완료되면 commit 혹은 rollback에 따라 변경 사항을 반영하거나 취소합니다.
[영속성 컨텍스트 내 4가지 엔티티 상태]
1. Transient (비영속): 영속성 컨텍스트에 존재하지 않는 객체 상태로, 데이터베이스와 연관이 없습니다. 엔티티 객체가 new 상태일 때입니다.
2. Persistent (영속): 영속성 컨텍스트에서 관리되는 객체 상태로, 데이터베이스와 연관이 있으며, 트랜잭션이 커밋되기 전까지 계속 관리됩니다.
3. Detached (분리): 영속성 컨텍스트에서 더 이상 관리되지 않는 객체 상태로, 데이터베이스와의 연결이 끊어졌습니다. 예를 들어, em.clear() 호출 후 상태입니다.
4. Removed (삭제됨): 영속성 컨텍스트에서 제거된 상태로, 트랜잭션이 커밋되면 데이터베이스에서 실제로 삭제됩니다.
플러시(Flush): flush() 메서드는 영속성 컨텍스트에서 관리 중인 엔티티들을 데이터베이스에 동기화합니다. 즉, 변경된 내용을 실제 데이터베이스에 반영합니다. 그러나 트랜잭션을 커밋(commit) 하지 않으면, 실제로 데이터베이스에 반영된 것은 아닙니다.
커밋(Commit): 트랜잭션이 끝날 때, 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하고, 트랜잭션을 완료합니다.
[em.persist()]
엔티티 객체를 영속성 컨텍스트에 저장하는 작업
기존의 엔티티를 갱신하는 데는 사용되지 않으며 오직 새로운 엔티티를 저장하는 데만 사용
em.persist(member) 시에는 다음과 같이 1차 캐시에 저장됨
이때 @Id는 해당 Entity의 PK임
[persist로 영속성 컨텍스트에만 저장하고 transaction.commit()은 안했는데 em.find로 조회가 가능한 이유는?]
JPA에서 entity를 persist할때
@Id @GeneratedValue 어노테이션이 있는 필드는
JPA가 자동으로 ID 값을 생성해서 Entity를 생성하고
영속성 컨텍스트(1차 캐시)에 저장해준다
즉 아직 DB에 저장된 상태는 아니지만(commit이 날라간 상태는 아니지만)
기본키는 모두 생성이 되고
영속성 컨텍스트(1차 캐시)에는 저장된 상태인 것이다
같은 트랜잭션 내에서 em.find가 호출되면
1차 캐시에서 동일한 ID 값을 가진 Entity를 검색한다
그래서 전에 persist 한 entity를 조회할 수 있는 것이다
이런 이유로 persist 한 것에 대한 commit이 날라가지 않아도
em.find를 통해 조회가 바로 가능한 것이다!
[em.save()]
엔티티 객체를 영속성 컨텍스트에 저장하거나 업데이트하는 작업
실제 데이터베이스에는 저장되지 않는다!
데이터를 실제 데이터베이스에 바로 반영하지 않고, 영속성 컨텍스트에 저장한다.
영속성 컨텍스트는 데이터베이스와의 중간 저장소로, 트랜잭션이 완료될때까지 해당 엔티티를 관리한다.
persist나 save 호출 시 영속성 컨텍스트에도 엔티티가 저장되고 1차 캐시에도 저장된다!
따라서 이후 같은 엔티티를 조회하면 1차 캐시에서 즉시 가져올 수 있다.
[em.flush()]
영속성 컨텍스트에 있는 모든 변경사항을 즉시 데이터베이스에 반영하도록 강제하는 작업이다.
하지만 flush 가 commit 은 아니다!!
두 개의 차이점을 알아야한다
flush는 트랜잭션이 진행 중인 상황에서 발생하는 것이다!
[em.clear()]
영속성 컨텍스트에 관리되는 모든 엔티티를 초기화
EntityManager가 관리하는 모든 엔티티를 삭제함
이 메서드를 호출하면, 영속성 컨텍스트의 상태가 초기화되어 현재 관리 중인 엔티티들은 더 이상 관리되지 않게됨.
영속성 컨텍스트도 초기화하고 그에 따른 1차 캐시도 초기화해준다.
[em.getTransaction()]
EntityManager를 통해 직접 트랜잭션을 제어하는 방법이다.
// 트랜잭션을 시작 em.getTransaction().begin() // 트랜잭션을 커밋하여 영속성 컨텍스트에 있는 변경사항을 실제 데이터베이스에 반영 em.getTransaction().commit() // 트랜잭션을 롤백하여, 변경된 사항을 모두 취소 em.getTransaction().rollback()
하지만 @Transactional 어노테이션을 써주면
@Transactional 어노테이션이 있는 메서드가 실행될 때 Spring은 해당 메서드에 대해 트랜잭션을 시작하고, 메서드가 정상적으로 완료되면 커밋, 예외가 발생하면 롤백을 처리해준다.
따라서 @Transactional 어노테이션을 통해 EntityManager를 통해 직접 트랜잭션을 제어할 필요없이 자동으로 제어 통제되도록 할 수 있다.
[영속성 컨텍스트에서 Entity 찾기]
이미 영속성 컨텍스트 안에 있는 entity를 조회하는 경우에는
다음과 같이 DB까지 안가고 1차 캐시에 있는 entity를 바로 꺼낼 수 있음
왜냐하면 persist 할때 1차 캐시에 들어가서 DB를 안뒤져도 되기 때문!
만약 찾는 entity가 1차 캐시에 없는 놈이라면
DB로 SELECT 쿼리문 날려서 가져온 다음
Entity로 1차 캐시에 저장하고 반환함
[쓰기 지연 SQL 저장소와 실제 DB 적용 시점]
영속성 컨텍스트 안에는
1차 캐시와 쓰기지연 SQL 저장소 이렇게 두 개가 있음
memberA를 persist 해주면
일단 1차 캐시 안에 들어가면서
동시에 JPA가 entity를 분석해서 SQL을 써놓고 "쓰기 지연 저장소"에 저장해둠
또 entity가 들어오면 차곡차곡 쌓아둠
그러다가
transaction.commit을 때리면
그제서야 쓰기 지연 SQL 저장소에 있던 놈들이
flush가 되면서
DB에 SQL문을 날리는거임
그래서 transaction.commit()을 했을때 실제로 DB에 적용되는거임!
[Entity 수정 - 변경 감지]
JPA는 commit 시 내부적으로 flush가 호출됨
이때 DB에 쓰기지연 SQL 저장소에 만들어둔 쿼리를 보내기 전에
Entity랑 SnapShot을 비교함
1차 캐시 안에는 사실 @Id / Entity / SnapShot 이 존재함
SnapShot 은 최초로 영속성 컨텍스트에 들어온 상태를 찍어놓은 거임!
지금과 같이 Entity 값을 변경했으면
JPA가 Entity랑 SnapShot을 일일이 비교한 다음
바뀐게 있으면 UPDATE SQL 쿼리를 생성해서 쓰기 지연 SQL 저장소에 저장한 후
DB에 쌓인 쿼리들과 같이 보냄
JPA는 값을 바꾸면 transaction.commit을 하는 시점에서 값을 바꾼다!
'JPA' 카테고리의 다른 글
[JPA] JPA의 이해 (0) 2024.07.28 연관관계 편의 메서드 (0) 2024.07.08 @mappedBy의 이해 (0) 2024.07.08 단방향 양방향 연관관계의 이해 (0) 2024.07.08 ORM과 JPA의 이해 (0) 2024.03.01