일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 테스트 코드
- Not Acceptable
- 스프링 IoC 컨테이너
- Controller
- 스프링 데이터 JPA
- entity
- SpringBootTest
- testresttemplate
- ResponseEntity
- 키움
- jdbc template
- 의존관계 자동 주입
- 컴포넌트 스캔
- 빈 스코프
- 가을야구
- 스프링
- 스프링 컨테이너
- 랜덤 포트
- RunWith
- 405 METHOD_NOT_ALLOWED
- JPA
- restTemplate
- Java Reflection API
- jdbc
- 기본 생성자
- 406 NOT_ACCEPTABLE
- 정적 컨텐츠
- 좋은 객체지향 설계 원칙
- 통합 테스트
- 의존성 주입
- Today
- Total
코드네임 JY
[스프링 JPA] 영속성 컨텍스트 본문
🍟 EntityManager 그리고 트랜잭션
EntityManager를 사용하면 내부적으로 DB Connection을 사용해 데이터베이스를 사용 할 수 있게 된다.
생성 단위는 요청(한 트랜잭션 단위)이 들어올 때마다 새롭게 선언 하며, 하나의 트랜잭션이 완료되면 꼭 close 해주어야 한다!
절대로 쓰레드 간 EntityManager를 공유해서 사용하면 안 된다! 한 트랜잭션 안에서만 사용하고 닫아야 한다!
이런 EntityManager를 만들어내는 공장이 따로 있다! 공장의 이름은 EntityManagerFactory 라고 한다!
EntityManagerFactory emf = Persistence.createEntityManagerFactory(persistenceUnitName);
EntityManager em = emf.createEntityManager();
em.close(); // 트랜잭션 단위로 EntityManager close 해주기
emf.close(); // 전체 코드가 완료되면 EntityManagerFactory close 해주기
위와 같은 방식으로 EntityManager를 생성할 수 있다. EntityManager는 한 트랜잭션 단위로 동작 했다면,
EntityManagerFactory는 코드의 모든 동작이 완료되면 그때 close 해주면 된다!
또한 JPA에서 데이터를 변경하는 모든 작업은 트랜잭션 안에서 진행되어야 한다! 엄청나게 중요한 내용이다! ⭐️⭐️
(단, 간단한 데이터 조회는 트랜잭션이 없이도 동작할 수 있다는 사실..!)
🍕 영속성 컨텍스트의 저장 및 수정
JPA가 내부에서 어떤 방식으로 동작하는지 알고 싶다면 '영속성 컨텍스트' 라는 개념을 알아야 한다!
영속성 컨텍스트는 동작하고 있는 EntityManager 안에서 엔티티를 영구적으로 저장하는 환경 이라고 이해하면 된다.
Member member = new Member();
member.setId(1L);
member.setName("memberA");
em.persist(member); // 영속성 컨텍스트로 저장
위와 같은 방식으로 Member 라는 엔티티 객체를 영속성 컨텍스트로 저장할 수 있다.
여기서 상당히 재미있는 개념이 하나 있는데, '영속' 이라는 단어 자체를 보면.. 무언가를 '영원히 저장' 한다는 느낌이 강하다.
그렇다면 위 코드를 수행하면 데이터베이스에 엔티티가 잘 저장되어 있을까?
그렇지 않다는 것을 확인할 수 있다. '영속성 컨텍스트에 저장'하는 것은 '데이터베이스에 저장'하는 것과는 완전히 다른 개념 이다!
데이터베이스 공부할 때, 데이터를 최종적으로 DB에 반영하는 과정을 'COMMIT' 이라고 공부했다!
영속성 컨텍스트는 DB와는 다른, 엔티티를 다른 매커니즘으로 활용하기 위한 또 하나의 공간 이다. (그 매커니즘 아래에 있음)
그래서 em.persist( ) 를 통해서 영속성 컨텍스트로 저장을 했더라도, DB에 데이터가 반영되는 것이 아니다.
DB에 데이터를 저장하고 싶다면 반드시 COMMIT 과정을 넣어주어야 하고, COMMIT은 트랜잭션을 통해서 사용할 수 있다.
Member member = new Member();
member.setId(1L);
member.setName("memberA");
em.persist(member); // 영속성 컨텍스트로 저장
em.getTransaction().commit(); // 커밋
COMMIT 과정을 추가한 위 코드를 수행한 결과는 다음과 같다!
하이버네이트가 찍어준 로그를 보면 INSERT 쿼리를 통해 DB에 데이터가 잘 들어간 것을 확인할 수 있다!
그렇다면 DB에 저장된 데이터에 대한 수정 은 어떻게 동작할까?
Member findMember = em.find(Member.class, 1L);
findMember.setName("memberZ");
// em.persist(findMember); // 영속성 컨텍스트로 저장
em.getTransaction().commit(); // 커밋
일단 EntityManager에 저장된 값을 찾으려면 find 메서드를 사용하면 된다!
그렇게 찾아온 값을 setName 메서드를 통해 값을 세팅하고, persist 메서드를 사용해서 내용을 저ㅈ..ㅏㅇ 하지 않아도 된다!! 😟😟
이는 아래에서 설명할 영속성 컨텍스트를 사용해서 좋은 이점 중 한 가지 관련이 있는데, 바로 '변경 감지' 라는 특성이다!
예제 코드에서 find 메서드로 영속성 컨텍스트를 가져왔고, setName 메서드로 필드 내용을 변경하였다.
우리가 영속성 컨텍스트를 가져와서 사용한 덕분 에, EntityManager는 다음과 같은 행동을 할 수 있다.
1️⃣ 영속성 컨텍스트에 저장된 엔티티의 스냅샷과 변경된 내용을 비교
2️⃣ 변경이 있으면 감지하고 UPDATE 쿼리 수행
3️⃣ 커밋해서 최종적으로 데이터베이스에 반영
영속성 컨텍스트를 저장할 때, 필드와 함께 해당 엔티티의 스냅샷도 저장된다. 스냅샷은 말 그대로 순간 포착! (세상에 요런일이)
EntityManager 입장에서, 본인이 관리하는 영속성 컨텍스트에 대해서 변경 감지 기능을 수행 할 수 있다.
그래서 기록해놓은 스냅샷과 코드에서 변경된 내용을 서로 비교해서 UPDATE 쿼리로 데이터를 변경 할 수 있는 것이다!
이 과정에서 persist 메서드는 쓰이지 않았다. 그리고 위에서 말하지 않았나! persist는 DB에 데이터를 저장하는 것이 아니라고!
따라서 위에 있는 수정 예제 코드를 수행하면 다음과 같은 결과가 나온다.
find 메서드에서 SELECT 쿼리를 통해 영속성 컨텍스트를 찾아오고, 변경 감지 기능 통해 UPDATE 쿼리를 사용했다.
데이터베이스에서 결과를 확인해보면 수정할 때는 persist 메서드가 없어도 내용이 수정될 수 있다는 것 을 확인할 수 있다!
최종적인 데이터의 반영은 COMMIT이 수행하는 것이고, persist 메서드는 영속성 컨텍스트에 올리는 용도니까!
정리
✅ 어쨌든 DB에 최종적인 데이터를 반영하는 것은 COMMIT으로 수행되어야 함
✅ persist 메서드는 영속성 컨텍스트에 올리는 용도
✅ 변경 감지 기능이 있기 때문에, 영속성 컨텍스트를 가져와서 Setter로만 내용을 바꿔도 데이터를 수정할 수 있음!
🍔 매커니즘의 활용성
바로 데이터베이스에 저장하는 것이 아니라 중간에 영속성 컨텍스트의 매커니즘을 굳이 사용하는 이유가 있겠지요..?
위에서 살짝 맛은 봤다. 영속성 컨텍스트를 사용하면 변경 감지 기능을 사용해서 편리하게 내용을 수정할 수 있는 것처럼!
1️⃣ 1차 캐시
영속성 컨텍스트는 1차 캐시 기능을 통해서 DB에서 엔티티를 꺼내오지 않고도 데이터를 조회할 수 있다.
Member member = new Member();
member.setId(2L);
member.setName("memberB");
em.persist(member);
System.out.println("Find Member ID is 2L");
Member findMember2 = em.find(Member.class, 2L);
System.out.println("Find Member ID is 3L");
Member findMember3 = em.find(Member.class, 3L);
DB에서 데이터를 가져오는 것이 아니라 캐시된 데이터를 가져온다면 DB에 쿼리를 날릴 필요가 없지 않나!
그래서 나온 결과는 다음과 같다.
영속성 컨텍스트에 저장된 데이터를 가져올 때는 1차 캐시를 활용하기 때문에 DB에 쿼리를 날릴 필요가 없다.
하지만 현재 1차 캐시에 없는 데이터를 가져올 때는 DB에 있는 데이터를 일단 꺼내와야 하기 때문에 SELECT 쿼리가 날라간다!
(DB에는 데이터가 있지만 1차 캐시에는 데이터가 없다고 가정)
2️⃣ 동일성 보장
Member a = em.find(Member.class, 2L);
Member b = em.find(Member.class, 2L);
System.out.println(a == b); // True
영속성 컨텍스트가 1차 캐시에 저장되어 있을 때, 똑같은 엔티티를 꺼내면 그 엔티티는 동일성을 보장할 수 있다는 내용이다!
3️⃣ 쓰기 지연
Member memberA = new Member();
memberA.setId(1L);
memberA.setName("memberA");
Member memberB = new Member();
memberB.setId(2L);
memberB.setName("memberB");
em.persist(memberA);
em.persist(memberB);
em.getTransaction().commit();
그동안 계속 반복했던 중요한 내용! persist 메서드는 영속성 컨텍스트에 저장하는 것이지 DB에 데이터를 저장하는 것이 아니다!
그래서 위 예제 코드를 수행하면 INSERT 쿼리는 엔티티에서 persist 메서드를 각각 호출하였을 때 나가는 것이 아니라,
COMMIT 하기 직전에 INSERT 쿼리가 나간다! 이것을 쓰기 지연이라고 한다!
(영속성 컨텍스트가 저장되는 공간과 DB 공간이 다른 것을 알고 있기만 한다면! 이해가 매우 쉽다!)
4️⃣ 변경 감지
위에서 언급했다! ㅎㅎ
'백엔드 공부' 카테고리의 다른 글
[스프링 JPA] 연관관계 매핑 (1) (0) | 2023.02.10 |
---|---|
[스프링 JPA] 엔티티 매핑 (0) | 2023.02.09 |
[오브젝트] 객체지향 설계 (0) | 2023.02.05 |
[DevOps] Github Actions CI/CD (0) | 2023.01.27 |
[DevOps] 빌드와 배포, CI/CD (0) | 2023.01.25 |