트랜잭셔널이 자동으로 걸리는지 검증
lazyloading이랑 영속성 컨텍스트랑 어떻게 연관되는지
Persistence Context
Persistence Context의 핵심 기능
기능 | 설명 |
1차 캐시 (First-Level Cache) | 같은 엔티티를 여러 번 조회해도 DB 쿼리를 다시 날리지 않음 |
엔티티 동일성 보장 | 같은 영속성 컨텍스트 안에서는 동일한 ID의 엔티티는 항상 같은 객체 (== 비교 true) |
변경 감지 (Dirty Checking) | 트랜잭션 커밋 시, 엔티티의 변경 사항을 감지해 자동으로 update 쿼리를 생성 |
쓰기 지연 (Write-Behind) | persist/save한 insert 쿼리를 모아뒀다가 트랜잭션 종료 시 한꺼번에 실행 |
지연 로딩 (Lazy Loading) | 프록시 객체로 관계 엔티티를 미리 가져오지 않고, 실제 사용할 때 조회 |
- 개념
- JPA가 데이터를 DB로부터 불러오거나 저장할 때 사용하는 1차 캐시. 이 안에 들어간 엔티티들은 영속 상태(Persistent State) 라고 불리고, JPA가 자동으로 변경 사항을 추적합니다.
- 트랜잭션이 시작될때 영속성 컨텍스트를 생성하였다가 트랜잭션이 끝나면 추적한 변경사항을 일괄 DB에 반영합니다.
Persistence Context 안의 구조
- 자료구조
- Persistence Context는 Map<EntityKey(EntityType, PrimaryKeyValue), EntityInstance> 형태의 구조
- 여기서 EntityKey는 Entity의 Class Type과 @Id의 조합 값으로 구성됩니다
- EntityInstance는 엔티티 객체의 참조값입니다.
- Persistence Context는 Map<EntityKey(EntityType, PrimaryKeyValue), EntityInstance> 형태의 구조
Persistence Context 안의 동작
- 영속성 컨텍스트 등록하여 데이터 캐쉬
- 엔티티 신규 생성 후 저장 (persist())이나 엔티티 호출 (find())이 실행되면 엔티티는 영속성 컨텍스트에 캐시된다
- JPA는 DB에 엔티티를 찾아보기전에 영속성 컨텍스트에 엔티티가 존재하는지 확인한다. 존재한다면 DB 쿼리를 실행하지않는다
- 스냅샷(snapshot) 관리
- 엔티티가 영속성 컨텍스트에 들어와 영속 상태가 되는 순간 엔티티의 초기 상태의 값을 복사해 저장해둠
- flush() 시점에 스냅샷 값과 엔티티 값을 비교하여 변경사항을 일괄 update 실행함
class PersistenceContext {
Map<EntityKey, Object> entitiesByKey; // 1차 캐시: ID로 엔티티 객체 추적
Map<Object, EntityEntry> entityEntries; // 스냅샷 메타 정보
}
- 액션플래그 생성을 통한 쓰기지연
- entityManager.persist(), entityManager.remove() , entityManager.merge()같은 메서드를 호출했을 때, JPA는 즉시 SQL을 실행하지 않고, 내부에 "INSERT/UPDATE/DELETE 수행 예정"이라는 플래그(액션)를 따로 저장함
- 쓰기 지연 SQL 목록
List<InsertAction> insertQueue
List<UpdateAction> updateQueue
List<DeleteAction> deleteQueue
- flush는 변경 감지된 내용과 쓰기 지연된 SQL을 모아서 DB에 실제 일괄 반영하는 것
- 1. 스냅샷 비교하여 변경감지 (Dirty Checking) → 변경사항이 발견되면 SQL 업데이트 액션플래그를 생성
- 2. INSERT 쓰기 큐 내 액션플래그 처리 → INSERT INTO user (name) VALUES ('변상화')
- 3. Update 큐 내 액션플래그 처리 → UPDATE user SET name = '변상화'
- 4. DELETE 큐 내 액션플래그 처리 → Delete
- 1. 스냅샷 비교하여 변경감지 (Dirty Checking) → 변경사항이 발견되면 SQL 업데이트 액션플래그를 생성
JPA 구조로 인한 성능최적화
지연 로딩 (Lazy Loading) 지원 기반
- 어플리케이션 레벨에서 **작업을 “보류하고 관리”**할 수 있습니다.
어플리케이션 레벨에서 예외발생시 rollback 처리하여 자원 절약
- DB에서 예외처리할 시에 발생하는 Undo log 쓰기, 락 획득, MVCC 처리 등 부하 발생을 없앰
지연쓰기로 인해 여러번 분할하여 실행할 쿼리를 한번에 전송함
- JDBC 호출, DB parsing, 네트워크 왕복 비용이 줄어들음
JPA를 사용한다면 @Transactional은 무조건 사용해야한다.
성능 최적화 | 여러 엔티티 조작 후 한번에 flush 가능 |
JPA 구조로 인한 Best Practice
- JPA에서 양방향 연관관계를 선언했으면, 객체 양쪽의 상태를 일치시켜줘야한다
public void addUserAuth(UserAuth userAuth) {
this.userAuthList.add(userAuth);
userAuth.setUser(this);
}
@Test
void test_권한부여_로직() {
User user = new User();
UserAuth userAuth = new UserAuth(user, auth);
// user.getUserAuthList().add(userAuth); 누락!
// setUser만 호출했다고 가정
// 대상 서비스 로직
myService.assignUserAuth(user);
assertEquals(1, user.getUserAuthList().size()); // 실패!
}
- 생명주기
- 일반적으로 Spring에서는 @Transactional 이 붙은 메서드 실행 시 생성되고, 해당 트랜잭션이 끝나면 사라집니다.
- Persistence Context는 EntityManager에 종속되어 있고, EntityManager는 트랜잭션 범위에서 살아있습니다.
JPA는 SQL을 자동 생성하고, 트랜잭션 단위 일관성을 유지할 수 있습니다.
Persistence Context가 없다면 생기는 문제
- 동일한 엔티티를 여러 번 조회 → 쿼리 계속 발생 → 성능 저하
- 변경 감지 불가 → 수동으로 update 쿼리 작성해야 함
- 영속 상태 관리 어려움 → 엔티티 생명주기 추적 복잡
EntityManager
Common Methods
persist(entity) | Inserts a new entity (makes it managed). |
find(entityClass, id) | Retrieves an entity by its ID (uses first-level cache). |
merge(entity) | Reattaches a detached entity to the persistence context. |
remove(entity) | Marks an entity for deletion. |
detach(entity) | Removes an entity from the persistence context (stops tracking). |
flush() | Forces synchronization with the database (useful before batch operations). |
clear() | Removes all entities from the persistence context. |
The persistence context is bound to a transaction in most cases. It ends when:
- The transaction commits (in a service method annotated with @Transactional).
- You explicitly call entityManager.clear() or entityManager.close().
- The request in a Spring Web application completes (when using Spring's default transaction scope).
Important Concepts for Performance Optimization
Lazy vs. Eager Loading
Managing Large Transactions
When handling large datasets, avoid keeping too many entities in the persistence context.
🔹 Best Practice: Batch Processing using flush() and clear()
Handling Detached Entities in REST APIs
In REST APIs, entities become detached because the persistence context ends after each request.
Common Mistakes & How to Avoid Them
Loading too many entities into memory | Use pagination (Pageable in Spring Data JPA). |
Updating detached entities without merging | Use merge() before updating. |
Using @Transactional in private methods | Spring AOP doesn’t work on private methods. Use public. |
Using find() repeatedly for the same entity | Store the entity in a variable; JPA caches it. |
Fetching unnecessary data | Use @Query or JOIN FETCH to optimize queries. |
Ensure You Maintain the Bidirectional Relationship
JPA를 잘못사용하는 케이스
- “@Transactional 없는데도 save가 되는 것 같다” → 구현체(Hibernate)나 Spring 설정 특수 케이스로 인한 자동 flush + auto-commit 덕분일 수 있음.
- 이건 표준 JPA 스펙이 보장하는 동작이 아님.
- 실무에선 반드시 @Transactional을 명시하여 동일한 동작을 보장하는 게 안전합니다.
요약하면, 지금 당장은 트랜잭션이 없는데도 성공한 것처럼 보여도, 설정이 바뀌거나 다른 환경으로 옮기면 “아예 저장 안 되는” 문제가 터질 수 있으니 주의하세요!
- 쓰기 지연(Write-Behind) 상태일 때, flush 전에 객체 값을 바꾸면 바뀐 값이 저장이 됨
- JPA는 flush()가 실행될때까지 쓰기지연상태이며 dirtychecking 후 쓰기가 생성되므로 변경된 값이 저장됨
User user = userRepository.save(new User("홍길동", 30)); // INSERT SQL 안 나감 — 쓰기 지연 큐에 등록
user.setAge(40); // save 이후 값 변경 (변경 감지 대상)
em.flush(); // 이 시점에 INSERT 쿼리 날림 → age = 40이 반영됨
- record를 변경하고자 PK값을 바꾸면 영속성 컨텍스트 구조상 새로운 값으로 삽입됨
- 영속성 컨텍스트에 엔티티는 PK와 Object 맵으로 저장하고 있기때문에 PK를 변경하면 새로운 key-value가 저장되어서 insert로 처리될 수 있음
@Transactional
public void updateUserInfo(UserDto.UserInfoUpdateReqDto userReqDto) {
User user = userRepository.findWithUserAuthsById()
user.updateUserInfo();
// UserAuth 업데이트
UserAuth userAuth = user.getUserAuthList();
userAuth.changeKey(user.getId(), userReqDto.getAuthId());
// user, userauth 업데이트
User updatedUser = userRepository.save(user);
UserAuth updatedUserAuth = userAuthRepository.save(userAuth);
return;
}
'개발기술 > ORM' 카테고리의 다른 글
JPA 쿼리 메소드 정의 ; JPA Method, JPQL, QueryDSL, 네이티브SQL (0) | 2025.03.05 |
---|---|
JPA 기타기능 (Pageable, auditing, data.sql 기능) (0) | 2025.02.28 |
JPA 활용의 필요성과 활용법 (0) | 2025.02.27 |
Spring JPA Hibernate 트랜잭션 (0) | 2024.12.22 |
Spring JPA Entity 설정 (2) | 2024.10.28 |