1. 영속성 컨텍스트 Persistence Context
: 데이터를 영속화하는데 사용하는 컨테이너, JPA 컨테이너 안에서 돌아가는 Entity를 관리하는 컨텍스트
주체가 되는 클래스 EntityManager 를 사용
-> Spring Boot JPA dependency를 통해 별도의 Persistence 파일 사용없이 Persistence 설정들을 처리해줌
EntityManager
: 영속성 컨텍스트 내에서 Entity들을 관리, JpaSimpleRepository에서 직접적으로 EntityManager를 사용하지 않도록 한번더 감싸준 것
@SpringBootTest
public class EntityManagerTest {
@Autowired
private EntityManager entityManager;
@Test
void entityManagerTest(){
System.out.println(entityManager.createQuery("select u from User u").getResultList());
}
}
Hibernate:
select
user0_.id as id1_7_,
user0_.created_at as created_2_7_,
user0_.updated_at as updated_3_7_,
user0_.email as email4_7_,
user0_.gender as gender5_7_,
user0_.name as name6_7_,
user0_.use_yn as use_yn7_7_
from
tb_user user0_
Hibernate:
select
userhistor0_.user_id as user_id6_8_0_,
userhistor0_.id as id1_8_0_,
userhistor0_.id as id1_8_1_,
userhistor0_.created_at as created_2_8_1_,
userhistor0_.updated_at as updated_3_8_1_,
userhistor0_.email as email4_8_1_,
userhistor0_.name as name5_8_1_,
userhistor0_.user_id as user_id6_8_1_
from
user_history userhistor0_
where
userhistor0_.user_id=?
Hibernate:
select
userhistor0_.user_id as user_id6_8_0_,
userhistor0_.id as id1_8_0_,
userhistor0_.id as id1_8_1_,
userhistor0_.created_at as created_2_8_1_,
userhistor0_.updated_at as updated_3_8_1_,
userhistor0_.email as email4_8_1_,
userhistor0_.name as name5_8_1_,
userhistor0_.user_id as user_id6_8_1_
from
user_history userhistor0_
where
userhistor0_.user_id=?
Hibernate:
select
userhistor0_.user_id as user_id6_8_0_,
userhistor0_.id as id1_8_0_,
userhistor0_.id as id1_8_1_,
userhistor0_.created_at as created_2_8_1_,
userhistor0_.updated_at as updated_3_8_1_,
userhistor0_.email as email4_8_1_,
userhistor0_.name as name5_8_1_,
userhistor0_.user_id as user_id6_8_1_
from
user_history userhistor0_
where
userhistor0_.user_id=?
Hibernate:
select
userhistor0_.user_id as user_id6_8_0_,
userhistor0_.id as id1_8_0_,
userhistor0_.id as id1_8_1_,
userhistor0_.created_at as created_2_8_1_,
userhistor0_.updated_at as updated_3_8_1_,
userhistor0_.email as email4_8_1_,
userhistor0_.name as name5_8_1_,
userhistor0_.user_id as user_id6_8_1_
from
user_history userhistor0_
where
userhistor0_.user_id=?
Hibernate:
select
userhistor0_.user_id as user_id6_8_0_,
userhistor0_.id as id1_8_0_,
userhistor0_.id as id1_8_1_,
userhistor0_.created_at as created_2_8_1_,
userhistor0_.updated_at as updated_3_8_1_,
userhistor0_.email as email4_8_1_,
userhistor0_.name as name5_8_1_,
userhistor0_.user_id as user_id6_8_1_
from
user_history userhistor0_
where
userhistor0_.user_id=?
[User(super=BaseEntity(createdAt=2023-05-07T01:42:21, updatedAt=2023-05-07T01:42:21), id=1, name=martin, email=martin@fastcampus.com, useYn=true, gender=MALE), User(super=BaseEntity(createdAt=2023-05-07T01:42:21, updatedAt=2023-05-07T01:42:21), id=2, name=dennis, email=dennis@fastcampus.com, useYn=true, gender=MALE), User(super=BaseEntity(createdAt=2023-05-07T01:42:21, updatedAt=2023-05-07T01:42:21), id=3, name=sophia, email=sophia@slowcampus.com, useYn=true, gender=FEMALE), User(super=BaseEntity(createdAt=2023-05-07T01:42:21, updatedAt=2023-05-07T01:42:21), id=4, name=james, email=james@slowcampus.com, useYn=true, gender=FEMALE), User(super=BaseEntity(createdAt=2023-05-07T01:42:21, updatedAt=2023-05-07T01:42:21), id=5, name=hope, email=martin@another.com, useYn=true, gender=MALE)]
2. Entity Cache
: 영속성 컨텍스트의 특징으로 1차 Cache, Save 메소드와 같이 DB 변경하는 메서드를 실행하였을 때 바로 DB가 없데이트 되지 않고 영속성 컨텍스트 내부에 있는 캐시를 거쳐서 DB가 업데이트된다. 해당 캐시를 1차 Cache라고 부른다.
1차 캐시는 영속 상태의 엔티티를 저장하며 Map의 형태로 만들어진다. Map에는 key는 id값, value는 entity값이 들어간다.
순서
: key값이 id라서 id로 조회를 하게 되면 영속성 컨텍스트에 있는 1차 Cache에 entity가 있는지 확인을 해보고 값이 있다면 DB 조회없이 return한다. 만약 값이 없으면 쿼리문으로 조회를 하고 1차 Cache에 저장 후 return한다.
@SpringBootTest
@Transactional
public class EntityManagerTest {
@Autowired
private EntityManager entityManager;
@Autowired
private UserRepository userRepository;
@Test
void cacheFindTest3(){
System.out.println(userRepository.findById(1L).get());
System.out.println(userRepository.findById(1L).get());
System.out.println(userRepository.findById(1L).get());
}
}
-> 위의 코드와 같이 id값을 반복적으로 찾을 때 @Transcational이 붙는다면 JPA는 데이터를 조회하며 cache에 저장 한 후 다음 조회 할때 조회하지 않고 cache에 있는 값을 사용한다.
2023-05-07 02:25:36.513 INFO 44379 --- [ Test worker] o.s.t.c.transaction.TransactionContext : Began transaction (1) for test context [DefaultTestContext@3f363cf5 testClass = EntityManagerTest, testInstance = com.test.jpa.jpaProj.service.EntityManagerTest@38d9c9c4, testMethod = cacheFindTest3@EntityManagerTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@3829ac1 testClass = EntityManagerTest, locations = '{}', classes = '{class com.test.jpa.jpaProj.JpaProjApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@338c99c8, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@7582ff54, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@78641d23, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@2f67b837, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@60856961, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@38102d01], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@54ec8ab3]; rollback [true]
Hibernate:
select
user0_.id as id1_7_0_,
user0_.created_at as created_2_7_0_,
user0_.updated_at as updated_3_7_0_,
user0_.email as email4_7_0_,
user0_.gender as gender5_7_0_,
user0_.name as name6_7_0_,
user0_.use_yn as use_yn7_7_0_,
userhistor1_.user_id as user_id6_8_1_,
userhistor1_.id as id1_8_1_,
userhistor1_.id as id1_8_2_,
userhistor1_.created_at as created_2_8_2_,
userhistor1_.updated_at as updated_3_8_2_,
userhistor1_.email as email4_8_2_,
userhistor1_.name as name5_8_2_,
userhistor1_.user_id as user_id6_8_2_
from
tb_user user0_
left outer join
user_history userhistor1_
on user0_.id=userhistor1_.user_id
where
user0_.id=?
User(super=BaseEntity(createdAt=2023-05-07T02:25:36, updatedAt=2023-05-07T02:25:36), id=1, name=martin, email=martin@fastcampus.com, useYn=true, gender=MALE)
User(super=BaseEntity(createdAt=2023-05-07T02:25:36, updatedAt=2023-05-07T02:25:36), id=1, name=martin, email=martin@fastcampus.com, useYn=true, gender=MALE)
User(super=BaseEntity(createdAt=2023-05-07T02:25:36, updatedAt=2023-05-07T02:25:36), id=1, name=martin, email=martin@fastcampus.com, useYn=true, gender=MALE)
2023-05-07 02:25:36.899 INFO 44379 --- [ Test worker] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test: [DefaultTestContext@3f363cf5 testClass = EntityManagerTest, testInstance = com.test.jpa.jpaProj.service.EntityManagerTest@38d9c9c4, testMethod = cacheFindTest3@EntityManagerTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@3829ac1 testClass = EntityManagerTest, locations = '{}', classes = '{class com.test.jpa.jpaProj.JpaProjApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@338c99c8, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@7582ff54, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@78641d23, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@2f67b837, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@60856961, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@38102d01], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]
3. Entity의 생명주기
1. 비영속 (new/transient)상태
: 엔티티 객체를 생성하였지만 아직 영속성 컨텍스트에 저장하지 않은 상태
@Transactional
public void put(){
// 비영속 상태 new
User user = new User();
user.setName("newUser");
user.setEmail("newUser@gmail.com");
}
2. 영속 (managed)상태
: 영속성 컨텍스트에 저장된 상태, 영속 상태가 되었다고 바로 DB에 값이 저장되지 않고 트랜잭션의 커밋시점에 영속성 컨텍스트에 있는 정보들을 DB에 쿼리로 날리게 된다.
@Transactional
public void put2(){
User user = new User();
user.setName("newUser");
user.setEmail("newUser@gmail.com");
// 영속 상태
entityManager.persist(user);
user.setName("newUserPersist");
}
3. 준영속 (deteched)상태
: 영속성 컨텍스트에 저장되었다가 분리된 상태
@Transactional
public void put3(){
User user = new User();
user.setName("newUser");
user.setEmail("newUser@gmail.com");
entityManager.persist(user);
// 준영속 상태
entityManager.detach(user);
user.setName("newUserPersist");
}
4. 삭제 (remove)
: 영속성 컨텍스트와 DB에서 해당 엔티티를 삭제한 상태
@Transactional
public void put4(){
User user = new User();
user.setName("newUser");
user.setEmail("newUser@gmail.com");
entityManager.persist(user);
User user1 = userRepository.findById(1L).get();
// 삭제 상태
entityManager.remove(user1);
}
4. 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
1. 엔티티 값을 변경하면 DB에 바로 업데이트 하지 않는다.
2. 트랜잭션 내부에서 영속 상태의 엔티티의 값을 변경하면 Insert Query 들은 DB에 바로 보내지않고 쿼리 저장소에 쿼리문들을 생성하여 쌓아둔다.
3.쿼리 저장소에 쌓여있는 쿼리들은 entityManager의 flush()나 트랜잭션의 commit을 통해 보내지게 된다.
4. flush()는 1차 캐시를 지우지 않고 쿼리를 DB에 날려서 DB와의 싱크를 맞추는 역할을 한다. 쿼리를 보내고 난 후에 commit을 실행
5. 변경 감지 (Dirty Checking)
1. 엔티티의 수정이 일어나도 개발자는 영속성 컨텍스트에 따로 알려주지 않아도 영속성 컨텍스트가 알아서 변경 사항을 체크해준다.
2. 1차 캐시에 엔티티를 저장할 때 스냅샷 필드도 따로 저장하여 commit이나 flush를 할 때 엔티티와 스냅샷을 비교하여 변경사항이 있으면 알아서 Update Query을 만들어서 DB에 전송한다.
Reference
FastCampus - 한번에 끝내는 Java/Spring 웹 개발 마스터 초격차 패키지 Online https://fastcampus.co.kr/dev_online_javaend
한 번에 끝내는 Java/Spring 웹 개발 마스터 초격차 패키지 Online. | 패스트캠퍼스
Java/Spring 웹 개발, 핵심 25가지 스킬부터 공부하세요. 대기업 출신 7인의 강사진이 모여 만든 Java/Spring 웹 개발 완전체 커리큘럼! 핵심 스킬 25가지 강의부터 250개의 예제, 7개의 프로젝트까지! 비
fastcampus.co.kr
https://ultrakain.gitbooks.io/jpa/content/chapter3/chapter3.3.html
3.3 엔티티의 생명주기 · jpa
ultrakain.gitbooks.io
[Spring JPA] 영속성 컨텍스트(Persistence Context)
영속성 컨텍스트란? 엔티티를 영구 저장하는 환경이라는 뜻으로 어플리케이션과 DB사이에서 객체를 보관하는 가상의 DB같은 역할을 한다.
velog.io
'Spring > 1-3. JPA' 카테고리의 다른 글
(8. Spring Data JPA) 영속성 전이 Cascade (0) | 2023.05.11 |
---|---|
(7. Spring Data JPA) Transaction Manager (0) | 2023.05.10 |
(5. Spring Data JPA) Entity Listener 활용 (1) | 2022.10.18 |
(4. Spring Data JPA) Entity의 기본속성(@Entity) (0) | 2022.10.17 |
(3. Spring Data JPA)QueryMethod 쿼리메소드 정의 및 실습 (0) | 2022.10.15 |