영속성 컨텍스트
간단하게 요약해서 작성하자면.
가장 잘 정리된 사진인거같아서 퍼온다.


보통 JPA를 사용하면 EntityManger에 대해서 모르고 자동으로 사용하는 사람들이 많다.
하지만 EntityManger를 알아야 JPA를 더 효율적으로 사용할 수 있다.
위의 흐름을 보면
- JPA를 사용하면서 java.class로 Entity를 만들고
- Entity Manager Factory를 통해 Entity Manager 생성
- EntityManger를 통해서 Entity를 관리
- Persistance Context에서 관리
- Persistence Unit은 DB연결에 대한 설정으로 .XML로 사용해야 했지만, 요즘은 .프로퍼티(.property), .야믈(yaml) 파일에 datasource, user, password 등을 관리한다.
Entity Manager은 Persistance Context에 Entity를 영속화 시켜 관리한다.
관리를 하는 방법은 MAP형태로 1차 캐시에 저장하여 관리를 한다.
여기서 나오는 JPA에 이점이 있다.
- 1차캐시
- 쓰기지연
- 변경감지
1.은 위와 같이 MAP형태로 Entity를 영속성 컨텍스트 즉 Persistance Context 1차 캐시에 저장하여, 영속화 되어있는 Entity를 다시 DB에서 조회하지 않고, 영속성 컨텍스트에서 바로 가져와서 사용할 수 있다.
2. 쓰기지연은 한 트랜잭션안에 있는 여러가지 쿼리를 바로 flush하지않고, query들을 모아놨다가 한번에 내보내는 것을 말한다. 하지만 중간에 flush 되는 경우가 있는데. 경우의 예시는
- 트랜잭션이 끝났을 경우
- 강제로 flush 했을경우
- JPQL을 사용했을 경우 가있다. -> 이 경우에 데이터의 정확성이 떨어지는 경우가 있는데 하단에서 설명하겠다.
3. 변경감지
1차 캐시에 MAP형태로 저장한 것을 스냅샷을 통해서 남겨두는데. flush 할 시기에 스냅샷의 값과 Entity의 값이 다르다면 Update query를 만들어서 보내주게 된다.
Persistance Context에 상태에는
- 영속화
- 비영속화
- 준영속화
- 삭제
가 있는데 시나리오를 통해서 간략하게 설명하자면,
- 아무런 메서드 실행이 없을때는 비 영속화 상태였다가
- JPA를 통해 조회, 저장 할경우 영속화가 되고,
- 트랜잭션이 끝나면 준영속화가 된다.
- 삭제의 경우 메서드 안에서 delete의 쿼리가 있을 경우 이를 삭제 상태로 부르고 delete query 가 나가기 전에 캐시에서 조회는 되지만, 값은 없어서 조회하지 않는 것이 좋다.
3. JPQL을 사용했을 경우 가있다. -> 이 경우에 데이터의 정확성이 떨어지는 경우가 있는데 하단에서 설명하겠다.
이 부분에 대해서 설명해 보자면,
@SpringBootTest
@Transactional
public class CrewServiceTest {
@Autowired
private CrewService crewService;
@Autowired
private CrewRepository crewRepository;
@Test
public void increaseAgeOfEveryone() {
// given
Crew 마루 = crewRepository.save(new Crew("마루", 24));
Crew 임 = crewRepository.save(new Crew("임", 20));
Crew 페퍼 = crewRepository.save(new Crew("페퍼", 26));
Crew 록바 = crewRepository.save(new Crew("록바", 26));
// when
crewService.increaseAgeForAllCrews();
// then
assertAll(
() -> assertThat(마루.getAge()).isEqualTo(25),
() -> assertThat(임.getAge()).isEqualTo(21),
() -> assertThat(페퍼.getAge()).isEqualTo(27),
() -> assertThat(록바.getAge()).isEqualTo(27)
);
}
}
@Repository
public interface CrewRepository extends JpaRepository<Crew, Long> {
@Modifying
@Query("UPDATE Crew c SET c.age = c.age + 1")
void increaseAllAge();
}
이 경우이다. 얼핏보면 성공할 것 같지만 실패하는 이유가 있다.

위에 save method로 1차 캐시에 등록을 하지만 update 메서드는 jpql로 작성되어있어서 DB에는 영향을 미치지만,
캐시에는 적용이 안되어 정합성이 안맞는 경우가 있는 것이다.
더하여 jpql이 발생하면 기존에있는 query들고 flush되게 되어있는데 이것은 jpql이 일어난 entity에 관련 된 query만 flsuh되고 관련이 없는 쿼리는 남아있다.
약간 주제에 어긋난 이야기지만 남겨놓으면 좋을 것 같아서 남긴다.
새로운 엔티티인지 판단하는 이유와 작동 방식 요약
1. 새로운 엔티티인지 판단하는 이유
• JPA에서 save() 메서드는 새로운 엔티티인지 여부에 따라 다음 작업을 수행:
• 새로운 엔티티 → persist() 호출: 데이터베이스에 새 데이터를 삽입.
• 기존 엔티티 → merge() 호출: 데이터베이스의 기존 데이터를 수정.
• 새로운 엔티티를 제대로 판단하지 못하면 불필요한 데이터베이스 조회나 잘못된 동작이 발생할 수 있음.
2. JPA가 새로운 엔티티인지 판단하는 기준
• JPA는 **@Id**와 @Version 어노테이션 필드를 기준으로 판단:
• @Version 사용된 경우:
• 버전 필드가 primitive 타입이면 AbstractEntityInformation의 기본 로직 실행.
• 버전 필드가 wrapper 타입이면 값이 null인지 확인.
• @Version 없을 경우:
• @Id 필드가 primitive 타입이면 0인지 확인.
• @Id 필드가 wrapper 타입이면 값이 null인지 확인.
3. ID 직접 할당 시 동작
• ID를 직접 할당하면 기본적으로 새로운 엔티티로 간주되지 않음 → merge() 호출.
• 해결 방법: Persistable 인터페이스를 구현하여 isNew() 메서드를 커스터마이징.
이유는 즉 새로운 객체인데도 새로운 객체가 아닌 것으로 판단하면 DB에서 조회하기 때문이다.

'Spring > JPA' 카테고리의 다른 글
태태개발일지 - 비동기처리 (0) | 2024.12.11 |
---|---|
태태개발일지 - 트랜잭션? (0) | 2024.12.03 |
태태개발일지 - JPA PK Key (0) | 2024.12.03 |
태태개발일지 - 양방향 연관관계 (1) | 2024.11.22 |
태태개발일지 - JPA 슈퍼타입 (1) | 2024.11.20 |