강의 출처:https://www.inflearn.com/course/ORM-JPA-Basic#curriculum
해당 강의는 Inflearn에 등록된 김영한님의 Spring Boot 강의입니다.
이번장에는 JPA의 영속성관리 및 JPA 내부 구조에 대해서 설명하겠다.
JPA에서 가장 중요한 두가지 개념을 알아야 두어야 한다. 하나는 전 시간에 ORM에 대한 개념이다.
그 다음은 영속성 컨텍스트이다.
1. 영속성 컨텍스트
엔티티를 영구 저장하는 환경
이라는 뜻이다.영속성 컨텍스트
는 논리적인 개념(가상)으로 눈에 보이지 않는다.엔티티 매니저
(Entity Manager)를 통해서 영속성 컨텍스트에 접근한다.
엔티티를 영구저장 하는 환경에 영속성은 영구 저장하는 느낌인데 컨텍스트는 환경이라는 느낌으로 보일것이다.
여기서 컨텍스트라는 단어는 운영체제에서도 주요 개념으로 사용된다. 과학, IT 분야에서는 무언가의 상태를 저장하고 관리하는 공간 혹은 환경이라는 느낌이라고 생각해두면 된다.다시 JPA로 돌아가서 설명하면, 애플리케이션과 데이터베이스 사이에서 엔티티(Entity)의 생명 주기를 관리하는 가상의 데이터베이스 테이블 같은 것이라고 생각할 수 있다.
여기서 JPA의 영속성 콘텍스트는 앞서 설명을 토대로 엔티티의 생명주기를 상태로 저장하고 관리한다는 의미이다.
2. 엔티티의 생명주기
왼쪽과 오른쪽 이미지를 둘다 자세히 한번씩 읽어보시길 바랍니다.
코드로 살펴보는엔티티의 생명주기(이전 시간의 try{ }문 안의 등록,조회 등등)
1. 비영속 상태 및 영속 상태
/* 회원 등록 (EntityManager.persist(entity);)*/
Member member = new Member(); //비영속
member.setId(3L); //비영속
member.setName("HelloA"); //비영속
em.persist(member);//영속 ->JPA와 관련
이 코드는 지난 시간 회원 등록 하는 코드였다. JPA는 트랜잭션안에서 객체를 persist하면 영속성 컨텍스트에 엔티티로 저장한다. 이제 부터는 자바의 객체가 엔티티로 변경된 시점이다.
다만 아직 이 엔티티가 아직 DB에 INSERT 되어 있진 않다.
2. 준영속 상태
- 영속성 컨텍스트에 저장되었다가 분리된 상태이다. 즉 영속성 컨텍스트가 제공하는 기능을 못한다.
다음은 준영속 상태로 만드는 방법이다.
코드로 보는 준영속 상태
Member member=new Member();
member.setId(101L);
member.setName("HelloJPA");
//객체를 저장한 상태,영속상태. 즉, 1차 캐시,버퍼에 저장(데베 반영x)
em.persist(member);
em.detach(member);//entity를 영속성 컨테스트에서 분리시킴.
Member findMember1= em.find(Member.class,101L);
Member findMember2= em.find(Member.class,101L);
//영속 엔티티의 동일성 보장, 마치 자바 저장소같은 방식으로 동작.
//데이터베이스 차원이 아닌, 애플리케이션 차원에서 동작.
System.out.println(findMember1==findMember2); //true
위의 코드를 보면 조회하는 로직이 들어간다.
다음과 같이 조회할때 영속성 콘텍스트에서 먼저 해당 엔티티가 있으면 그 엔티티를 조회 한다.
저번시간에 AOP 정리 중 성능에 대해서 확인하다 2번째 조회가 첫번째 조회보다 훨씬 빨랐던 이유를 설명하겠다.
첫번째 조회는 객체를 한번 영속성 컨텍스트에 집어 넣으면 (persist) 1차 캐시에 한번 더 그 객체를 엔티티로 집어 넣고 상태를 관리한다. 그 다음에 db에 집어 넣고, db에서 조회한다.
두번째 조회는 왜 첫번째 조회보다 훨씬 빠른지 다들 이해가 갈것이다. 이는 캐시라는 단어를 보자마자 떠올렸을텐데 JPA의 영속성 컨텍스트는 엔티티의 상태를 관리 하기위해 우선 1차 캐시에 집어 넣는다. 그러면 두번째 조회때는 이 캐시에서 조회를 하므로 훨씬 더 빠른 상태로 조회가 가능하게 된다.
다음은 1차 캐시에 조회할 엔티티가 없으면 db에서 찾는 모습을 나타내는 이미지이다.
3. 삭제
/* 삭제*/
Member findMember= em.find(Member.class,1L); //id가 1인 멤버 찾기
em.remove(findMember);//해당멤버 삭제
삭제는 그냥 영속성 콘텍스트에서 삭제한 상태이다.
3. 영속성 컨텍스트의 이점
영속성 컨텍스트의 이점은 총 6가지이다. 여기서 지연로딩은 추후 프록시파트에서 정리 하겠다.
1. 엔티티의 생명주기 관리
영속성 컨텍스트는 엔티티의 생명주기를 관리한다. 엔티티의 상태에는 비영속(new/transient), 영속(managed), 준영속(detached), 삭제(removed) 상태가 있다. 엔티티 매니저를 통해 엔티티가 영속성 컨텍스트에 저장될 때, 해당 엔티티는 영속 상태가 되며, 이 상태의 엔티티는 영속성 컨텍스트에 의해 관리된다.
2. 1차 캐시
영속성 컨텍스트 내에는 1차 캐시가 존재한다. 엔티티 매니저를 통해 엔티티를 조회하면, 먼저 1차 캐시에서 엔티티를 검색한다. 만약 1차 캐시에 엔티티가 존재한다면, 데이터베이스에 접근하지 않고 캐시된 엔티티를 반환한다. 이는 동일한 트랜잭션 내에서의 반복적인 데이터베이스 접근을 줄여 성능을 향상시킨다.
3. 동일성 보장
영속성 컨텍스트는 같은 엔티티에 대해 항상 같은 인스턴스를 반환함으로써 엔티티의 동일성을 보장한다. 이는 애플리케이션 내에서 안정적인 엔티티 관리를 가능하게 한다.
Member member=new Member();
member.setId(101L);
member.setName("HelloJPA");
//객체를 저장한 상태,영속상태. 즉, 1차 캐시,버퍼에 저장(데베 반영x)
em.persist(member);
em.detach(member);//entity를 영속성 컨테스트에서 분리시킴.
Member findMember1= em.find(Member.class,101L);
Member findMember2= em.find(Member.class,101L);
//영속 엔티티의 동일성 보장, 마치 자바 저장소같은 방식으로 동작.
//데이터베이스 차원이 아닌, 애플리케이션 차원에서 동작.
System.out.println(findMember1==findMember2); //true
코드 결과
왼쪽 이미지와 같이 영속성 컨텍스트는 같은 엔티티에 대해서는 같은 값으로 취급해준다.
자바 객체는 생성시에 서로 다른 값으로 불러오지고, 서로 setting값도 다르게 한다. 심지어 객체가 저장되어있는 메모리 주소도 다르다.
하지만 영속성 컨테스트에 저장된 엔티티를 불러와서 객체로써 쓰면 두 객체는 동일성을 보장해준다.
예를 들어보자, 같은 트랜잭션 내에서 두개발자가 서로 다른 로직을 구현한다고 하자.
내가 회원A 엔티티(id가 1L이라고 가정)에 대한 값을 세팅하고 조회 하는 로직을 작성 한다고 했을 때,
다른 개발자가 다른 객체로 회원 A에 대한 엔티티의 값을 setting한다고 했을때, 나는 회원 A의 값을 불러오면 다른 개발자가 setting 한 값을 불러오게 될것이다. 이는 다른 개발자가 의도한대로 값을 setting 한 값을 불러 올 수 있게됐다. 이러한 룰을 알고 있으면 해당 엔티티를 불러올때 같은 객체이니 불러오더라도 비즈니스 로직을 따로 구현할 수 있게 된다.
(즉, 나의 로직이 영향을 끼칠 수 있다. 전 개발자의 로직에 손을 안대도 된다.)
이러한 동작 방식은 애플리케이션에서 데이터를 다룰 때 일관성과 안정성을 보장한다. 특히, 복잡한 비즈니스 로직이나 여러 개발자가 협업하는 프로젝트에서 이러한 특성은 매우 중요하다. 영속성 컨텍스트를 통해, 개발자는 엔티티의 생명주기를 명확하게 관리할 수 있으며, 애플리케이션의 복잡도를 줄이고 코드의 안정성을 높일 수 있게 된다.
4. 트랜잭션 지원과 지연 쓰기(Lazy Write)
영속성 컨텍스트는 트랜잭션을 지원하며, 엔티티의 변경 사항은 트랜잭션이 커밋되는 시점에 데이터베이스에 반영된다. 이를 '지연 쓰기'라고 하며, 엔티티의 변경이나 SQL 명령의 실행을 최적화하는 데 도움이 된다.
/* em.persist(entity);->2개에 저장, */
// 1. 쓰기 지연 SQL 저장소에 트랙잰션 활동 SQL 저장
// 2. 1차캐시에 해당 트랙잭션 enitity 저장
Member member1=new Member(150L,"A");
Member member2=new Member(160L,"B");
em.persist(member1);
em.persist(member2);
System.out.println("===================");
//trasaction.commit();->
// 1. 쓰기지연 저장소SQL의 SQL들을 flush하여 데베에 넣고,
// 2. 데베에 해당 내용들 commit
5. 변경 감지(Dirty Checking)
영속성 컨텍스트는 엔티티의 상태 변경을 감지하는 '변경 감지' 기능을 가지고 있다. 이는 굉장히 중요한 기능 중 하나이다. 트랜잭션이 커밋되는 시점에, 영속성 컨텍스트에 있는 엔티티 중 변경된 엔티티를 찾아 해당 변경 사항을 데이터베이스에 자동으로 반영한다.
/*수정 (id=1인 멤버 이름 수정)*/
Member findMember= em.find(Member.class,1L); //id가 1인 멤버 찾기
findMember.setName("HelloJPA");
//em.persist(findMember);
// ㄴ ★★★이렇게 영속성 컨테스에 넣으면 안됨!!★★★
persist를 한번더 안하는 이유: "변경감지" 하여 콘테스트에 entity를 수정하고,데베에서 찾아옴.
persist안해도 자동으로 JPA가 알아서 update 쿼리를 날림.
이를 Dirty Checking이라고 한다. 이는 snapshot이라는 다른 열로 확인한다.
snapshot은 최초로 commit한 Transation상태이다.
따라서 JPA는 entity를 최초로 등록할때만 em.persist한다.
4. 플러시(flush) 와 커밋(commit)
저번 정리에서 JPA의 모든 데이터 변경은 트랜잭션 내에서 실행됐다고 했다. 다음은 플러시에 대한 설명이다.
- 플러시: 영속성 컨텍스트의 변경내용을 데이터베이스에 반영
- 플러시 발생 시점:
- 변경 감지
- 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 (등록, 수정, 삭제 쿼리)
- 영속성 콘테스트 플러시하는 법
- em.flush() - 직접 호출
- 트랜잭션 커밋 - 플러시 자동 호출
- JPQL 쿼리 실행 - 플러시 자동 호출
플러시를 하면 데이터를 데이터 베이스에 반영하게되는데, 이때 트랜잭션내의 영속성 컨텍스트는 데이터 변경시 어차피 Dirty Checking(변경 감지)가 알아서 바꿔준다. 다만 플러시 발생은 Commit을 해야 발생해야한다.
그러면 만약 커밋 전에 데이터가 변경해서 미리 데이터 베이스에 업로드 해서 동기화 후 다른 비즈니스 로직을 꾸민다고 할때 em.flush(); 라고 하면 된다.
이번 장에는 JPA의 영속성 관리에 대한 설명을 예로 들어 자세히 설명했다.
'Spring Boot > Spring Boot JPA-기본편 강의 정리(김영한)' 카테고리의 다른 글
[SpringBoot-JPA 기본편] 자바 ORM 표준 JPA 프로그래밍 - 기본편: 7. 고급 매핑 (1) | 2024.02.25 |
---|---|
[SpringBoot-JPA 기본편] 자바 ORM 표준 JPA 프로그래밍 - 기본편: 6. 다양한 연관관계 매핑 (0) | 2024.02.25 |
[SpringBoot-JPA 기본편] 자바 ORM 표준 JPA 프로그래밍 - 기본편: 5. 연관관계 매핑 기초 (0) | 2024.02.25 |
[SpringBoot-JPA 기본편] 자바 ORM 표준 JPA 프로그래밍 - 기본편: 2. JPA 시작 (1) | 2024.02.23 |
[SpringBoot-JPA 기본편] 자바 ORM 표준 JPA 프로그래밍 - 기본편: 1. JPA 소개 (0) | 2024.02.23 |