스프링부트를 처음 접했을 때, 영속성 컨텍스트라는 말을 들으면서 진짜 알 듯 말 듯 하다 싶은 생각을 했다. 이런 개념들은 보통 설명을 들을 때보다 프로젝트를 하면서 '아 이게 이런 뜻이구나!' 하는 깨달음을 얻는 경우가 많은데, 이번에도 프로젝트를 통해 영속성 컨텍스트가 이런 느낌이구나 배울 수 있었다.
영속성 컨텍스트는 엔티티를 조회하고 난 후에 그 값을 엔티티 매니저가 들고 있는 것이다. 그러니까 DB에 쿼리를 보내서 조회한 다음에 까먹지 않고 기억해 두는 것이라고 생각하면 된다. 따라서 엔티티 매니저가 이 데이터를 기억하고 있으니까 같은 것을 조회할 때, 쿼리를 다시 보내지 않고 그 값을 확인해 볼 수 있다.
em.persist(user);
즉, 이런 코드가 repository에 존재한다고 치면, 이 때 user에 대한 데이터를 저장하면서 동시에 엔티티 매니저가 들고 있는 것이다. (저장을 하면 저장된 곳을 다시 조회하면 되니 데이터를 들고 있을 필요가 없지만, 그렇게 하면 다시 조회한다는 점에서 효율성이 떨어진다)
영속의 상태는 다음의 네 가지로 나뉜다.
- 비영속 : 영속성 컨텍스트와 무관한 상태
- 준영속 : 영속성 컨텍스트에 저장되었다가 분리된 상태
- 영속 : 영속성 컨텍스트에 저장된 상태
- 삭제 : 삭제된 상태
비영속
엔티티 객체는 만들었지만 아직 영속성 컨텍스트에 저장하지 않은 상태이다.
User user1 = new User();
영속
아까 본 사례와 같다.
em.persist(user1);
준영속
영속성 컨텍스트에 저장되었다가 분리된 상태
영속성 컨텍스트가 영속 상태였던 엔티티를 더 이상 관리하지 않는다.
em.detach(user1); # 분리
em.clear(); # 아예 영속성 컨텍스트 안의 내용을 전부 지워버림. -> 기존 내용들은 준영속 상태가 된다.
em.close(); # 영속성 컨텍스트 종료해버림. -> 기존 내용들은 준영속 상태가 된다.
준영속 상태의 데이터들은 영속성 컨텍스트가 제공하는 기능들(1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩 등)을 사용할 수 없다. 이런 준영속 상태의 데이터들을 다시 영속 상태로 변경하려면 merge를 사용하면 된다.
User user1 = new User(1, "yeonju");
em.persist(user1);
em.detach(user1);
user1.setUsername("yeonju2");
User user_edited = em.merge(user1);
user1.getUsername(); // yeonju
user_edited.getUsername(); // yeonju2
em.contains(user1); // False
em.contains(user_edited); // True
삭제
엔티티를 영속성 컨텍스트와 DB에서 삭제한다.
em.remove(user1);
영속성 컨텍스트의 특징
Entity를 식별자 값으로 구분
영속성 컨텍스트는 식별자 값으로 서로를 구분한다. 데이터베이스로 따지면 pk에 해당하는 그런 식별자를 말한다. 따라서 언제나 영속성 컨텍스트(그리고 그 대상이 되는 엔티티)에는 식별자값이 존재해야 한다. @Id 어노테이션을 사용하면 바로 식별자 필드를 지정할 수가 있다.
영속성 컨텍스트와 DB 저장
JPA에서는 트랜잭션을 commit 하는 순간에 영속성 컨텍스트에 저장되어 있던 엔티티들을 데이터베이스에 반영한다. (flush)
영속성 컨텍스트가 엔티티를 관리하면 다음과 같은 이점을 얻을 수 있다.
- 1차 캐시
- 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연
- 변경 감지
- 지연 로딩
1차 캐시
영속성 컨텍스트 내부에는 캐시가 있는데, 이를 1차 캐시라고 한다. 그래서 다음과 같은 코드를 실행시키면 조회 흐름을 이렇게 이끌 수 있다.
User user = em.find(User.class, id);
<조회 흐름>
1. 1차 캐시에서 엔티티를 찾는다.
2. 있으면 1차 캐시에서 엔티티 내부를 조회
3. 없으면 데이터베이스에서 조회
4. 데이터를 엔티티로 생성해서 1차 캐시에 저장 (엔티티는 영속 상태가 된다)
5. 조회한 엔티티 반환
영속 엔티티의 동일성 보장
영속성 컨텍스트는 엔티티의 동일성을 보장한다. 즉, 자바에서 .equals()를 쓰면 동일하다고 하는 것은 물론, ==을 사용해도 동일하다고 표현된다. (값만 동일한 것 두 객체가 아닌, 완전히 동일한 하나의 객체라는 것이다.)
트랜잭션을 지원하는 쓰기 지연
엔티티 매니저는 트랜잭션을 커밋하기 직전까지 내부 쿼리 저장소에 INSERT SQL을 저장해둔다. 트랜잭션이 커밋되면 한꺼번에 그동안 쌓였던 SQL들이 실행되는 것이다. 이를 트랜잭션을 지원하는 쓰기 지원이라고 한다.
변경 감지
JPA로 데이터를 수정할 때는 단순히 엔티티를 조회해서 데이터를 변경하면 된다. 조회한 후에 별도의 저장하는 코드는 필요없고 커밋만 해주도록 하면 된다. 트랜잭션을 커밋하면 엔티티 매니저 내부에서 먼저 플러시가 호출된다. 엔티티와 스냅샷을 비교하여 다른 점을 찾는다. 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 저장한다. 이후 쓰기 지연 SQL을 플러시하고 DB 트랜잭션을 커밋하게 된다.
※ flush 란?
플러시는 영속성 컨텍스트의 변경 내용을 DB에 저장한다. 즉, 변경 내용을 동기화한다고 보면 된다. 트랜잭션 커밋 시 혹은 JPQL 쿼리 실행 시 자동 호출이 되지만, em.flush()로 직접 호출해줄 수도 있다.
'TIL > Java | Spring Boot' 카테고리의 다른 글
[Spring boot] Jenkins를 이용한 spring boot 자동 배포 CI (0) | 2022.04.29 |
---|---|
[Java] 스터디 1주차_객체지향프로그래밍1 (0) | 2022.04.10 |
[Spring Boot] IoC, DI, 그리고 컨테이너 (0) | 2022.01.21 |
[Spring Boot] AOP (0) | 2022.01.20 |
[Spring Boot] JdbcTemplate, JPA, 스프링 데이터 JPA (0) | 2022.01.20 |
댓글