일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- jwttoken
- 프로그래머스 이중우선순위큐
- jwt
- 디자인패턴
- 개발자 취업
- @FeignClient
- TiL
- 프로그래머스
- 인프콘 2024
- 구글 OAuth login
- 파이썬
- 취업리부트코스
- 전략패턴 #StrategyPattern #디자인패턴
- DesignPattern
- 커스텀 헤더
- 디자인 패턴
- 99클럽
- spring batch 5.0
- 빈 충돌
- infcon 2024
- 항해99
- Python
- 단기개발자코스
- 1주일회고
- Spring multimodule
- 코딩테스트 준비
- KPT회고
- 빈 조회 2개 이상
- JavaScript
- 개발자부트캠프추천
- Today
- Total
m1ndy5's coding blog
JPA의 동작원리 본문
JPA에서는 CRUD 작업을 수행하기 위해 @Entity로 표시된 엔티티 클래스가 JPA 컨텍스트에 등록되면, 생명주기를 갖게 되고 JPA에 의해 관리된다.
생명주기가 살아있는 엔티티는 DB 테이블과 매핑되어 관리되는데, 개발자가 엔티티에 데이터를 추가/수정/삭제하면 그에 맞는 쿼리문이 자동으로 생성된다.
이와 같이 JPA에 의해 생명주기가 관리되는 엔티티를 두고 영속성(Persistence)이 있다고 표현한다.
영속성 관리
EntityManager
EntityManagerFactory는 여러 스레드에서 동시에 접근해도 안전하지만, 생성하는 비용이 크다.
따라서 EntityManagerFactory에서 요청이 올 때마다 생성 비용이 거의 없는 EntityManager를 생성한다.
EntityManager은 JPA에서 데이터베이스와의 상호 작용을 관리하는 핵심 인터페이스이다.
EntityManager은 일반적으로 애플리케이션의 각 트랜잭션에 대해 생성되며, 트랜잭션이 종료되면 닫혀야 한다.
이 때 만들어진 EntityManager은 내부적으로 Database Connection을 사용해서 DB를 사용한다.(데이터베이스 연결이 꼭 필요한 시점에만 커넥션을 얻는다.)
Entity의 생명주기는 EntityManager을 통해 관리되고 이는 트랜잭션을 시작하면 비영속 상태에서 영속 상태로 전환되며, 트랜잭션이 커밋되면 영속 상태의 변경사항이 데이터베이스에 반영된다. 트랜잭션 종료 후에는 영속 상태의 엔티티가 자동으로 준영속 상태로 전환된다.
1. 트랜잭션이 커밋되면, 영속성 컨텍스트의 모든 변경 내용이 데이터베이스에 반영된다. 변경 내용은 SQL 쿼리로 변환되어 데이터베이스에 전송되고, 데이터베이스에서는 해당 쿼리를 실행하여 영속성 컨텍스트에 있는 엔티티의 상태를 데이터베이스에 반영한다.
// 트랜잭션 커밋
em.getTransaction().commit();
2. Flush는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업이다. 트랜잭션 커밋 이전에도 명시적으로 Flush를 호출할 수 있다. Flush를 호출하면 JPA는 영속성 컨텍스트의 변경 내용을 데이터베이스에 즉시 반영하려고 시도한다.
// Flush 수동 호출
em.flush();
Flush는 Dirty Checking 기법을 사용하여 영속성 컨텍스트에 있는 엔티티의 상태 변화를 감지하고, 변경된 엔티티를 데이터베이스에 반영한다.
일반적으로 JPA는 트랜잭션 커밋 전에 자동으로 Flush를 수행하는데 쿼리 실행 시에도 Flush가 발생할 수 있다. (쿼리를 실행하기 전에 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하기 위해)
em.getTransaction().begin(); // 트랜잭션 시작
// 엔터티 상태 변경
em.flush(); // Flush 수동 호출
// 변경 내용 확인 및 문제 해결
em.getTransaction().commit(); // 트랜잭션 커밋
궁금한 점
- commit이 되기 전에 flush를 하면 뭐가 좋을까?
1. 쿼리 결과의 정확성 : flush를 수행하면 영속성 컨텍스트의 변경 내용이 데이터베이스에 반영되므로, JPQL 또는 네이티브 쿼리를 실행하기 전에 최신의 데이터를 사용할 수 있다.
2. 캐시 갱신 : 영속성 컨텍스트 내의 캐시된 엔티티 정보가 갱신되어 최신의 상태를 유지할 수 있다. -> 성능 향상
하지만 flush를 자주 사용하면 성능 저하의 원인이 될 수 있으므로 주의해야 한다.
명시적인 flush 호출은 특정 상황에서만 사용되어야하며, 세밀한 제어가 필요한 경우에만 사용하는 것이 좋다.
- 트랜잭션이 없이도 EntityManager가 생성될 수 있나?
트랜잭션이 없는 환경에서도 EntityManager를 사용하여 엔티티 조회가 가능하다!
이 때는 트랜잭션 범위를 명시적으로 정의하지 않고 각각의 엔티티 조회 작업이 독립적으로 실행된다.
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("examplePU");
EntityManager entityManager = entityManagerFactory.createEntityManager();
// 트랜잭션 없이 엔터티 조회
MyEntity entity = entityManager.find(MyEntity.class, 1L);
// 엔터티에 대한 작업 수행
// ...
entityManager.close();
entityManagerFactory.close();
위 예제는 트랜잭션을 명시적으로 시작하거나 커밋하지 않은 예시이다.
일반적으로 트랜잭션이 없는 간단한 조회 작업에서 사용된다.
주의할 점은 트랜잭션이 없는 상태에서 엔티티를 수정하거나 데이터베이스에 변경을 가하면, 변경 내용은 즉시 데이터베이스에 반영되어 일관성이 깨질 수 있다는 것이다.
JPA에서 영속성 컨텍스트를 관리하려면 항상 트랜잭션을 사용하는 것이 권장되는 이유는 트랜잭션 내에서만 영속성 컨텍스트가 활성화되며 엔티티를 등록하고 관리할 수 있기 때문이다. (트랜잭션 범위 외에는 엔티티 관리 불가)
- 트랜잭션 안에서 flush를 했는데 트랜잭션이 실패해서 롤백되면 flush를 통해 데이터베이스에 반영된 정보도 같이 롤백될까?
같이 롤백된다!
여기서 궁금했던 점은 데이터베이스에 이미 반영이 된 것인데 어떤 방식으로 롤백이 가능한지 였다.
가능했던 이유는 JPA의 트랜잭션과 데이터베이스의 트랜잭션이 일치하며, 동일한 범위 내에서 시작되고 종료되기 때문이었다.
MySQL로 예시를 들어보자면
START TRANSACTION;
-- 트랜잭션 내에서 다양한 SQL 명령문 실행
COMMIT; -- 또는 ROLLBACK;
이런식으로 함께 트랜잭션이 시작되는 것이다.
그렇기 때문에 JPA 트랜잭션이 롤백된다면 데이터베이스에서도 롤백이 일어나 게 되는 것이다.
하지만 또 궁금한게 생겼다,,,
Entity Manager은 데이터베이스 연결이 꼭 필요한 시점에만 커넥션을 얻는데 데이터베이스에 JPA 트랜잭션의 시작과 데이터베이스의 트랜잭션의 시작이 같을 수 있나? 라는 생각이 들었다.
결론부터 말하면 시작 시점이 다를 수 있다!
JPA에서 트랜잭션이 시작될 때 START TRANSACTION과 같은 SQL문이 아직 데이터베이스에 전송되지 않을 수 있다. 이는 JPA의 내부 동작 및 최적화에 따라 다를 수 있다.
하지만 이는 데이터베이스 연결 시점 때문이 아니고 START TRANSACTION과 같은 트랜잭션 제어 명령은 대부분 내부에서 데이터베이스 드라이버와 JPA 구현체에 의해 자동으로 처리된다고 한다!
- 만약 flush를 통해 데이터베이스에 반영을 했는데 다른 트랜잭션에서 flush된 정보로 트랜잭션을 시작해 버렸다고 가정하자. 근데 기존 트랜잭션이 실패해서 롤백되어 flush한 부분도 롤백이 되어버리는 문제는 어떻게 방지해야할까?
1. 트랜잭션 내에서 변경을 수행할 때, 트랜잭션을 완료하기 전에 다른 트랜잭션에서 해당 변경을 볼 수 없도록 트랜잭션 경계 설정 -> 트랜잭션의 격리 수준을 조절
2. 다른 트랜잭션이 영속성 컨텍스트의 변경을 바로 확인하지 못하도록 영속성 컨텍스트를 분리
3. flush 후에도 영속성 컨텍스트의 변경을 즉시 데이터베이스에 반영하지 않도록 캐시 설정 변경
결론
EntityManager은 일반적으로 애플리케이션의 각 트랜잭션에 대해 생성되며, 트랜잭션이 종료되면 닫혀야 한다.
EntityManager와 DB의 실질적인 연결은 데이터베이스와 연결이 필요한 시점에 발생한다.
EntityManager는 트랜잭션이 없이도 생성될 수 있지만 영속성컨텍스트의 장점을 취하려면 트랜잭션 안에서 실행되어야 한다.
flush를 통해 트랜잭션이 commit되지 않아도 데이터베이스에 반영할 수 있다. -> 하지만 잦은 사용은 성능 저하의 원인이 될 수 있다.
flush를 통해 데이터베이스에 반영됐더라도 기존 트랜잭션이 실패하면 롤백된다.
flush를 사용할 때는 트랜잭션의 격리 수준과 영속성 컨텍스트의 관리 방법을 조절하여 일관성을 유지하는 것이 매우 중요하다!!
'CS Study' 카테고리의 다른 글
Garbage Collector (1) | 2024.03.07 |
---|---|
Java의 메모리 영역 (0) | 2024.03.07 |
JVM, JRE, JDK & 자바 프로그램의 실행 과정 (0) | 2024.03.07 |
영속성 컨텍스트 (0) | 2024.03.06 |
JPA 도입 시 고려해 볼 사항 (0) | 2024.03.06 |