Загружаем каталог…
Загружаем каталог…
이전에는 ORM과 JPA의 관계를 살펴보고, Java 객체를 DB 테이블과 연결하는 Entity 에 대해 정리했다. ORM ↓ JPA ↓ Hibernate ↓ Entity ↓ DB 그런데 여기서 한 가지 궁금한 점이 생긴다. Memo memo = new Memo(); 이렇게 생성한 객체를 JPA는 어떻게 관리할까? 그리고 em.persist(memo); 를 호출하면 바로 DB에 INSERT SQL이 실행되는 걸까? JPA는 Entity를 효율적으로 관리하기 위해 영속성 컨텍스트(Persistence Context) 라는 공간을 사용한다. 강의에서도 영속성 컨텍스트를 Entity 객체를 효율적으로 관리하기 위해 만들어진 공간이라고 설명한다. :chatgpt-content-reference{index="0"} 이번에는 EntityManager ↓ 영속성 컨텍스트 ↓ 1차 캐시 ↓ 쓰기 지연 ↓ 변경 감지 의 흐름과 Entity의 상태 변화를 정리해 보려고 한다. 1. 영속성 컨텍스트란? 영속성 컨텍스트는 쉽게 말해 JPA가 Entity 객체를 관리하는 공간 이다. JPA는 우리가 사용하는 Entity 객체를 무조건 바로 DB에 보내는 것이 아니라, 영속성 컨텍스트에 저장하고 관리하면서 DB와 통신한다. :chatgpt-content-reference{index="1"} 전체적인 구조는 다음과 같이 볼 수 있다. Application ↓ EntityManager ↓ Persistence Context ↓ DB 즉, 개발자가 Entity를 직접 DB에 넣고 빼는 것이 아니라 개발자 ↓ EntityManager ↓ 영속성 컨텍스트 ↓ DB 형태로 JPA를 통해 관리하게 된다. 2. EntityManager란? 영속성 컨텍스트에 접근해서 Entity를 관리하려면 EntityManager 가 필요하다. 강의에서는 EntityManager를 Entity를 관리하는 관리자라고 설명하며, 이를 통해 Entity를 저장·조회·수정·삭제할 수 있다고 설명한다. :chatgpt-content-reference{index="2"} 예를 들어: em.persist(memo); Memo memo = em.find(Memo.class, 1L); em.remove(memo); 처럼 사용할 수 있다. 정리하면: EntityManager ↓ 영속성 컨텍스트 접근 ↓ Entity 관리 이다. 3. EntityManager는 어떻게 만들어질까? 일반 JPA에서는 EntityManagerFactory 를 통해 EntityManager 를 생성한다. EntityManagerFactory emf = Persistence.createEntityManagerFactory("memo"); EntityManager em = emf.createEntityManager(); 강의에서도 같은 순서로 EntityManagerFactory 를 만든 후 EntityManager 를 생성한다. :chatgpt-content-reference{index="3"} 즉, Persistence ↓ EntityManagerFactory ↓ EntityManager ↓ Persistence Context 로 이해할 수 있다. EntityManagerFactory 는 이름 그대로 EntityManager 를 만들어 주는 Factory다. 강의에서는 일반적으로 DB 하나당 하나의 EntityManagerFactory 를 생성해 애플리케이션 실행 중 사용한다고 설명한다. :chatgpt-content-reference{index="4"} 4. 영속성 컨텍스트의 핵심 기능 — 1차 캐시 영속성 컨텍스트 내부에는 1차 캐시 라는 저장 공간이 있다. 강의에서는 이를 Map 과 비슷한 구조로 설명한다. Key → @Id 값 Value → Entity 객체 예를 들어: 1차 캐시 Key Value --------------------- 1 Memo 객체 2 Memo 객체 3 Memo 객체 처럼 Entity를 관리한다. :chatgpt-content-reference{index="5"} 즉, Entity의 @Id 값이 식별자 역할을 한다. 5. persist() 를 호출하면 무슨 일이 일어날까? 다음 코드를 보자. Memo memo = new Memo(); memo.setId(1L); memo.setUsername("Robbie"); memo.setContents("Hello"); em.persist(memo); em.persist(memo) 를 호출하면 Entity가 영속성 컨텍스트의 1차 캐시에 저장된다. :chatgpt-content-reference{index="6"} 즉, Memo 객체 ↓ em.persist() ↓ Persistence Context ↓ 1차 캐시 저장 가 된다. 여기서 중요한 점은 persist() 의 핵심 의미는 Entity를 영속성 컨텍스트에서 관리되는 상태로 만드는 것 이라는 것이다. 6. Entity를 조회하면 먼저 1차 캐시를 확인한다 다음 코드를 생각해 보자. Memo memo = em.find(Memo.class, 1L); JPA는 무조건 DB부터 조회하지 않는다. 먼저 영속성 컨텍스트의 1차 캐시에서 id=1 인 Entity가 있는지 확인한다. em.find(Memo.class, 1L) ↓ 1차 캐시 확인 ↓ 있음? 있다면 바로 1차 캐시의 Entity를 반환한다. 1차 캐시 ↓ Entity 반환 없다면 DB에서 조회한다. 1차 캐시 확인 ↓ 없음 ↓ DB SELECT ↓ 조회한 Entity를 1차 캐시에 저장 ↓ Entity 반환 강의에서도 캐시에 조회하려는 ID가 없으면 DB에서 조회한 뒤 캐시에 저장한다고 설명한다. :chatgpt-content-reference{index="7"} 7. 같은 Entity를 두 번 조회하면? 예를 들어 다음 코드를 실행한다고 하자. Memo memo1 = em.find(Memo.class, 1L); Memo memo2 = em.find(Memo.class, 1L); 첫 번째 조회에서는 1차 캐시에 데이터가 없을 수 있다. memo1 조회 1차 캐시 없음 ↓ SELECT ↓ DB ↓ 1차 캐시 저장 두 번째 조회에서는 이미 같은 Entity가 1차 캐시에 있다. memo2 조회 1차 캐시 있음 ↓ DB 조회 X ↓ 캐시의 객체 반환 따라서 같은 영속성 컨텍스트 안에서는 memo1 == memo2 결과가 true 가 된다. 강의에서도 동일한 Entity를 두 번 조회했을 때 같은 객체가 반환되어 객체 동일성이 보장된다고 설명한다. :chatgpt-content-reference{index="8"} 이를 객체 동일성 보장 이라고 한다. 8. 1차 캐시의 장점 강의에서는 1차 캐시의 장점을 크게 두 가지로 설명한다. 1. DB 조회 횟수를 줄일 수 있음 2. 같은 DB Row에 대해 같은 객체가 사용되는 것을 보장 즉, DB 접근 감소 + 객체 동일성 보장 이라는 효과가 있다. :chatgpt-content-reference{index="9"} 다만 여기서 말하는 1차 캐시는 영속성 컨텍스트 내부에서 사용되는 캐시 라는 점을 기억해 두는 것이 중요하다. 9. JPA는 SQL을 바로 DB에 보내지 않을 수도 있다 JPA에는 쓰기 지연 저장소(ActionQueue) 라는 개념도 있다. Entity를 저장하거나 삭제하는 등 DB 변경 작업이 발생할 때 SQL을 모아 두었다가 트랜잭션과 함께 처리할 수 있다. 강의에서는 JPA가 SQL을 쓰기 지연 저장소에 모아 두었다가 트랜잭션이 커밋될 때 DB에 반영한다고 설명한다. :chatgpt-content-reference{index="10"} 개념적으로 보면 다음과 같다. em.persist(memo1) ↓ INSERT SQL 생성 ↓ 쓰기 지연 저장소 em.persist(memo2) ↓ INSERT SQL 생성 ↓ 쓰기 지연 저장소 쓰기 지연 저장소에는 INSERT memo1 INSERT memo2 처럼 SQL이 모인다. 그리고 트랜잭션의 커밋 과정에서 DB로 전달된다. 쓰기 지연 저장소 ↓ flush ↓ DB에 SQL 전달 ↓ commit 강의에서도 commit 전까지 SQL 요청이 없다가 이후 여러 INSERT SQL이 요청되는 과정을 확인한다. :chatgpt-content-reference{index="11"} 10. flush() 란? 여기서 등장하는 중요한 메서드가 em.flush(); 다. flush() 는 영속성 컨텍스트의 변경 내용을 DB에 반영하도록 SQL을 전달하는 과정 이라고 이해할 수 있다. 강의에서는 flush() 가 쓰기 지연 저장소에 있는 SQL을 DB로 요청하는 역할을 한다고 설명한다. :chatgpt-content-reference{index="12"} 흐름은 다음과 같다. Persistence Context 1차 캐시 + 쓰기 지연 저장소 ↓ flush() ↓ DB 11. flush() 와 commit() 은 같은 걸까? 같은 것은 아니다. 단순하게 구분하면: flush → SQL을 DB로 전달 commit → 트랜잭션의 변경 내용을 최종 확정 이라고 이해하면 된다. 강의에서도 flush() 를 직접 호출하면 그 시점에 쓰기 지연 저장소의 SQL이 DB에 요청된다고 확인한다. :chatgpt-content-reference{index="13"} 따라서 flush = DB에 SQL 반영 요청 commit = 트랜잭션 확정 으로 구분할 수 있다. 12. 트랜잭션은 왜 필요할까? JPA에서 데이터 변경 작업은 트랜잭션 안에서 처리된다. 예를 들어 일반 JPA에서는 다음과 같이 작성한다. EntityTransaction tx = em.getTransaction(); tx.begin(); try { em.persist(memo); tx.commit(); } catch (Exception e) { tx.rollback(); } 강의에서는 EntityTransaction 을 이용해 트랜잭션을 시작하고, 정상 처리되면 commit , 오류가 발생하면 rollback 한다고 설명한다. :chatgpt-content-reference{index="14"} 즉, begin ↓ DB 작업 ↓ 정상 ↓ commit 또는 begin ↓ DB 작업 ↓ 오류 ↓ rollback 으로 동작한다. 13. 그런데 UPDATE용 메서드는 어디 있을까? JPA를 처음 보면 다음과 같은 메서드가 있을 것 같다. em.update(memo); 하지만 실제로는 이런 방식으로 수정하지 않는다. JPA에서는 변경 감지(Dirty Checking) 를 이용한다. 강의에서도 em.update() 와 같은 메서드는 존재하지 않는다고 설명하면서 변경 감지 과정을 소개한다. :chatgpt-content-reference{index="15"} 14. 변경 감지(Dirty Checking) Entity가 영속성 컨텍스트에 들어올 때 JPA는 해당 Entity의 최초 상태를 함께 저장한다. 강의에서는 이를 LoadedState 라고 표현한다. :chatgpt-content-reference{index="16"} 예를 들어 처음 조회한 상태가 username = Robbie contents = Hello 라고 하자. JPA는 이 상태를 기억한다. LoadedState username = Robbie contents = Hello 그리고 코드에서 Entity를 변경한다. memo.setUsername("Update"); memo.setContents("변경 감지 확인"); 현재 상태는 Current State username = Update contents = 변경 감지 확인 가 된다. 15. JPA는 최초 상태와 현재 상태를 비교한다 트랜잭션이 커밋되고 flush() 가 일어나는 과정에서 JPA는 LoadedState ↕ 비교 Current State 를 확인한다. 변경 사항이 있다면 변경 발견 ↓ UPDATE SQL 생성 ↓ 쓰기 지연 저장소 ↓ DB 과정이 실행된다. 강의에서도 커밋 후 flush() 과정에서 최초 상태와 현재 상태를 비교하고, 차이가 있으면 UPDATE SQL 을 생성한다고 설명한다. :chatgpt-content-reference{index="17"} 이 과정을 변경 감지(Dirty Checking) 라고 한다. 16. 그래서 save() 를 다시 하지 않아도 수정된다 다음 코드를 보자. Memo memo = em.find(Memo.class, 1L); memo.setContents("수정된 내용"); 여기에는 em.update(memo); 도 없고 em.persist(memo); 를 다시 호출하지도 않는다. 그래도 해당 Entity가 영속 상태 라면 JPA가 변경을 감지할 수 있다. Entity 조회 ↓ 영속 상태 ↓ 필드 변경 ↓ Dirty Checking ↓ UPDATE SQL 강의의 예제에서도 Entity를 조회한 뒤 Setter로 값을 변경하고 commit() 만 수행해 UPDATE 가 발생한다. :chatgpt-content-reference{index="18"} 이 개념이 나중에 Spring Data JPA의 수정 코드에서 매우 중요해진다. 17. Entity에는 상태가 있다 JPA에서는 Entity가 항상 같은 상태인 것은 아니다. 강의에서는 크게 다음 상태들을 다룬다. 비영속 (Transient) 영속 (Managed) 준영속 (Detached) 삭제 (Removed) 각 상태는 JPA가 해당 Entity를 관리하고 있는가 를 기준으로 이해하면 쉽다. 18. 비영속(Transient) 다음과 같이 객체만 생성한 상태다. Memo memo = new Memo(); memo.setId(1L); memo.setUsername("Robbie"); memo.setContents("Hello"); 이 객체는 Java 객체로 존재하지만 아직 영속성 컨텍스트에 들어가지 않았다. 따라서: Java Heap에는 존재 하지만 Persistence Context에는 없음 이다. 강의에서는 new 로 생성했지만 아직 영속성 컨텍스트에 저장되지 않아 JPA의 관리를 받지 않는 상태를 비영속 상태라고 설명한다. :chatgpt-content-reference{index="19"} 즉, new Memo() ↓ Transient 이다. 19. 영속(Managed) 비영속 상태의 객체에 em.persist(memo); 를 호출하면 영속 상태가 된다. Transient ↓ persist() ↓ Managed 이제 해당 Entity는 영속성 컨텍스트에 저장되어 JPA의 관리를 받는다. 강의에서도 persist() 를 통해 비영속 Entity를 영속성 컨텍스트에 저장하면 관리되는 상태가 된다고 설명한다. :chatgpt-content-reference{index="20"} 영속 상태에서는 1차 캐시 변경 감지 쓰기 지연 등 영속성 컨텍스트의 기능을 사용할 수 있다. 20. 비영속 객체에서는 변경 감지가 일어나지 않는다 예를 들어 Memo memo = new Memo(); memo.setContents("A"); memo.setContents("B"); 라고 해도 이 객체가 영속성 컨텍스트에 들어간 적이 없다면 JPA는 관리하지 않는다. 따라서 Dirty Checking도 할 수 없다. 강의에서도 비영속 상태는 JPA가 관리하지 않기 때문에 데이터를 변경해도 변경 감지가 이루어지지 않는다고 설명한다. :chatgpt-content-reference{index="21"} 즉, Dirty Checking 영속 Entity → 가능 비영속 Entity → 불가능 이라고 볼 수 있다. 21. 준영속(Detached) 영속성 컨텍스트에서 관리되던 Entity가 관리 대상에서 분리될 수도 있다. 이 상태를 준영속(Detached) 이라고 한다. Managed ↓ detach() ↓ Detached 예를 들어: em.detach(memo); 를 실행하면 memo 는 영속성 컨텍스트에서 분리된다. 강의에서는 준영속 상태를 영속성 컨텍스트에서 관리되다가 분리된 상태라고 설명한다. :chatgpt-content-reference{index="22"} 22. 준영속 상태에서는 JPA의 관리를 받지 않는다 detach() 가 호출되면 해당 Entity는 1차 캐시에서도 제거된다. 따라서 1차 캐시 X 변경 감지 X 영속성 컨텍스트의 관리 X 가 된다. 강의에서도 준영속 상태로 전환된 Entity는 1차 캐시에서 제거되기 때문에 영속성 컨텍스트의 기능을 사용할 수 없다고 설명한다. :chatgpt-content-reference{index="23"} 즉, Managed → JPA 관리 O Detached → JPA 관리 X 다. 23. 준영속 Entity를 다시 영속 상태로 만들려면? 강의에서는 merge() 도 다룬다. Memo mergedMemo = em.merge(memo); 준영속 객체의 값을 영속 상태의 객체에 병합해서 새로운 영속 Entity를 반환한다. :chatgpt-content-reference{index="24"} 여기서 중요한 점은 merge(memo) 를 호출했다고 해서 기존 memo 객체 자체가 다시 영속 상태가 되는 것은 아니라는 점이다. 개념적으로는 Detached memo ↓ merge() ↓ 값을 병합한 새로운 Managed Entity 반환 으로 이해하면 된다. 24. 삭제(Removed) Entity를 삭제하려면 먼저 JPA가 관리하고 있는 Entity를 가져온 뒤 em.remove(memo); 를 호출한다. Managed ↓ remove() ↓ Removed 강의에서는 remove() 호출 시 Entity가 삭제 상태로 변경되고 이후 트랜잭션 커밋 과정에서 DELETE SQL 이 DB로 요청된다고 설명한다. :chatgpt-content-reference{index="25"} 즉, Memo memo = em.find(Memo.class, 1L); em.remove(memo); 처럼 사용하는 구조다. 25. Entity 상태 전체 흐름 지금까지의 상태 변화를 그림처럼 정리하면 다음과 같다. new ↓ Transient 비영속 ↓ persist() ↓ Managed 영속 ├──────────────→ remove() │ ↓ │ Removed │ 삭제 │ └──────────────→ detach() ↓ Detached 준영속 ↓ merge() ↓ Managed Entity 반환 핵심은 영속 상태인지 아닌지가 JPA의 관리 여부를 결정한다 는 것이다. 26. Spring Boot에서는 트랜잭션을 어떻게 사용할까? 지금까지 일반 JPA 예제에서는 직접 EntityTransaction tx = em.getTransaction(); tx.begin(); ... tx.commit(); 을 작성했다. 하지만 Spring에서는 트랜잭션을 더 편하게 사용할 수 있다. @Transactional public void updateMemo() { } 처럼 @Transactional 을 사용한다. 강의에서는 @Transactional 을 클래스나 메서드에 추가하면 해당 범위의 DB 연산을 하나의 트랜잭션으로 묶어 처리하고, 정상 수행되면 커밋하고 예외가 발생하면 롤백한다고 설명한다. :chatgpt-content-reference{index="26"} 즉, 메서드 시작 ↓ 트랜잭션 시작 ↓ DB 작업 ↓ 정상 종료 ↓ commit 이 자동으로 관리되는 것이다. 27. 수정 코드에서 @Transactional 이 중요한 이유 이제 실제 Spring 코드에서 다음과 같은 구조를 생각해 보자. @Transactional public Long updateMemo( Long id, MemoRequestDto requestDto ) { Memo memo = findMemo(id); memo.update(requestDto); return id; } 여기에는 memoRepository.save(memo); 가 없다. 그런데도 수정이 가능하다. 왜냐하면 트랜잭션 안에서 조회한 Entity가 영속 상태로 관리되고 있다면 Entity 조회 ↓ Managed 상태 ↓ memo.update() ↓ Entity의 값 변경 ↓ Dirty Checking ↓ UPDATE SQL ↓ commit 이 이루어지기 때문이다. 즉, JPA에서는 영속 상태 Entity의 값을 변경하면 변경 감지를 통해 UPDATE가 수행될 수 있다. 이게 처음 보면 가장 신기한 부분 중 하나다. 28. @Trans
То, что RADAR обнаружил и классифицировал для этой возможности. Это опубликованный источником текст, а не подтверждение, что предложение ещё действует.
[내일배움캠프] Spring 7 영속성 컨텍스트 — JPA는 Entity를 어떻게 관리할까. 이전에는 ORM과 JPA의 관계를 살펴보고, Java 객체를 DB 테이블과 연결하는 Entity 에 대해 정리했다. ORM ↓ JPA ↓ Hibernate ↓ Entity ↓ DB 그런데 여기서 한 가지 궁금한 점이 생긴다. Memo memo = new Memo(); 이렇게 생성한 객체를 JPA는 어떻게 관리할까? 그리고 em.persist(memo); 를 호출하면 바로 DB에 INSERT SQL이 실행되는 걸까? JPA는 Entity를 효율적으로 관리하기 위해 영속성 컨텍스트(Persistence Context) 라는 공간을 사용한다. 강의에서도 영속성 컨텍스트를 Entity 객체를 효율적으로 관리하기 위해…
Открыть источник