[JPA] @PersistenceContext와 EntityManager로 이해하는 영속성 컨텍스트
📖 @PersistenceContext와 EntityManager로 이해하는 JPA 영속성 컨텍스트
Spring Boot에서 JPA를 사용하다 보면 다음과 같은 코드를 마주치는 경우가 있다.
@PersistenceContext
private EntityManager entityManager;
처음 보면 단순히:
EntityManager를 주입받는 어노테이션인가?
정도로 생각하기 쉽다.
실제로 결과만 보면 맞는 말이지만, @PersistenceContext를 제대로 이해하려면 그 뒤에 있는 영속성 컨텍스트(Persistence Context) 와 EntityManager의 관계를 함께 이해해야 한다.
JPA에서 엔티티를 조회하고 수정하고 저장하는 과정은 단순히 데이터베이스에 SQL을 실행하는 구조가 아니다.
대략적으로:
EntityManager
│
▼
영속성 컨텍스트
│
▼
Entity 상태 관리
│
├── 조회
├── 변경 감지
├── 1차 캐시
└── Flush
│
▼
Database
와 같은 구조로 동작한다.
이번 글에서는 다음 내용을 중심으로 정리해본다.
@PersistenceContext란 무엇인가EntityManager는 어떤 역할을 하는가- 영속성 컨텍스트란 무엇인가
@PersistenceContext와EntityManager의 관계- 엔티티의 생명주기
- 1차 캐시
- 동일성 보장
- Dirty Checking
- Flush란 무엇인가
- Transaction과 영속성 컨텍스트의 관계
persist(),find(),remove(),merge()의 차이- Spring Data JPA에서는 왜 직접 사용할 일이 적은가
- 실무에서
EntityManager를 직접 사용하는 경우
🔎 1. @PersistenceContext란 무엇인가?
@PersistenceContext는 JPA의 EntityManager를 주입받기 위해 사용하는 어노테이션이다.
가장 기본적인 사용 형태는 다음과 같다.
@Repository
public class UserRepository {
@PersistenceContext
private EntityManager entityManager;
}
여기서 중요한 것은:
@PersistenceContext
자체가 데이터베이스 연결 객체를 만들어주는 것은 아니라는 점이다.
@PersistenceContext는 JPA가 관리하는 EntityManager를 현재 컴포넌트에 주입해달라는 의미에 가깝다.
즉:
@PersistenceContext
↓
EntityManager 주입
↓
영속성 컨텍스트와 상호작용
↓
Entity 상태 관리
↓
Database 반영
이라는 흐름으로 이해하면 된다.
🧩 2. EntityManager란 무엇인가?
그렇다면 EntityManager는 무엇일까?
EntityManager는 이름 그대로 JPA에서 엔티티를 관리하는 핵심 인터페이스다.
예를 들어 다음과 같은 엔티티가 있다고 하자.
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
}
EntityManager를 사용하면:
entityManager.find(User.class, 1L);
처럼 엔티티를 조회할 수 있다.
또한:
entityManager.persist(user);
로 새로운 엔티티를 영속 상태로 만들거나,
entityManager.remove(user);
로 삭제할 수도 있다.
즉 EntityManager는:
Entity 조회
Entity 저장
Entity 삭제
Entity 상태 관리
영속성 컨텍스트 접근
Flush
등 JPA의 엔티티 관리와 관련된 여러 기능을 담당한다.
🔗 3. @PersistenceContext와 EntityManager의 관계
둘의 관계를 간단하게 표현하면 다음과 같다.
@PersistenceContext
↓
EntityManager 주입
↓
Persistence Context 관리
↓
Entity 상태 관리
따라서:
@PersistenceContext
private EntityManager entityManager;
라는 코드는 단순히 "EntityManager 객체 하나를 생성해서 넣어주세요"라는 의미로만 이해하면 부족하다.
JPA가 관리하는 EntityManager를 통해 영속성 컨텍스트와 상호작용하기 위한 진입점을 제공받는 것이다.
🌱 4. 영속성 컨텍스트란 무엇인가?
@PersistenceContext를 이해하기 위해 가장 중요한 개념이 바로 **영속성 컨텍스트(Persistence Context)**다.
영속성 컨텍스트는 쉽게 표현하면:
JPA가 엔티티를 관리하기 위해 사용하는 논리적인 저장 공간
이라고 볼 수 있다.
예를 들어:
User user = entityManager.find(User.class, 1L);
을 실행했다고 하자.
단순히:
SELECT SQL 실행
↓
Java 객체 생성
↓
반환
으로 끝나는 것이 아니다.
조회된 User 엔티티는 영속성 컨텍스트에 등록되어 JPA가 관리하게 된다.
Database
↓
EntityManager
↓
Persistence Context
↓
User Entity
이후 해당 엔티티가 변경되었는지 JPA가 추적할 수 있게 된다.
🧠 5. 영속성 컨텍스트가 필요한 이유
그렇다면 JPA는 왜 굳이 엔티티를 영속성 컨텍스트에 관리할까?
가장 대표적인 이유는 다음과 같다.
1차 캐시
동일성 보장
Dirty Checking
쓰기 지연
Flush 관리
즉 영속성 컨텍스트는 단순한 캐시가 아니라 JPA가 엔티티의 상태를 관리하기 위한 핵심 공간이다.
📦 6. 1차 캐시
영속성 컨텍스트에는 1차 캐시가 존재한다.
예를 들어:
User user1 = entityManager.find(User.class, 1L);
User user2 = entityManager.find(User.class, 1L);
을 실행했다고 하자.
두 번째 find()에서는 항상 데이터베이스에 다시 접근하는 것은 아니다.
처음 조회한 엔티티가 영속성 컨텍스트에 있다면 해당 엔티티를 다시 사용할 수 있다.
흐름은:
첫 번째 find()
Database 조회
↓
User Entity 생성
↓
Persistence Context 등록
↓
user1 반환
그리고:
두 번째 find()
↓
Persistence Context 확인
↓
이미 존재
↓
기존 Entity 반환
이 된다.
🔎 7. 동일성 보장
1차 캐시와 연결되는 중요한 특징이 동일성 보장이다.
앞서 작성한 코드:
User user1 = entityManager.find(User.class, 1L);
User user2 = entityManager.find(User.class, 1L);
에서 같은 영속성 컨텍스트 안이라면:
user1 == user2
가 true가 된다.
즉 같은 데이터베이스 Row를 나타내는 엔티티라면 같은 영속성 컨텍스트 안에서 동일한 엔티티 인스턴스를 반환한다.
이것이 JPA의:
Repeatable Read 수준의 동일성 보장
을 이해하는 중요한 부분이다.
단순히 SQL 결과를 매번 새로운 객체로 만드는 것과는 다른 방식으로 엔티티를 관리하는 것이다.
🧩 8. 엔티티의 생명주기
JPA에서는 엔티티의 상태를 이해하는 것이 중요하다.
대표적인 상태는 다음과 같다.
비영속 (Transient)
영속 (Persistent)
준영속 (Detached)
삭제 (Removed)
각 상태가 어떤 의미인지 알아보자.
🌱 9. 비영속 상태
비영속 상태는 아직 JPA가 관리하지 않는 일반 Java 객체다.
User user = new User("콩이");
현재 user는 단순한 Java 객체다.
아직:
Persistence Context
│
X
│
User
상태다.
JPA는 이 객체를 관리하지 않는다.
📥 10. 영속 상태
EntityManager를 통해 엔티티를 영속성 컨텍스트에 등록하면 영속 상태가 된다.
User user = new User("콩이");
entityManager.persist(user);
이후:
User
↓ persist()
Persistence Context
관계가 된다.
이제 JPA가 해당 엔티티를 관리하기 시작한다.
따라서 이후 엔티티의 변경사항을 JPA가 추적할 수 있다.
🔎 11. 조회한 엔티티도 영속 상태가 된다
조회 역시 마찬가지다.
User user = entityManager.find(User.class, 1L);
이렇게 조회된 엔티티는 영속성 컨텍스트에서 관리된다.
즉:
find()
↓
Database 조회
↓
Entity 생성
↓
Persistence Context 등록
↓
Persistent 상태
가 된다.
🔄 12. 준영속 상태
영속성 컨텍스트에서 관리되던 엔티티가 더 이상 관리되지 않는 상태를 **준영속(Detached)**이라고 한다.
예:
entityManager.detach(user);
또는:
entityManager.clear();
를 사용하면 영속성 컨텍스트에서 엔티티가 분리될 수 있다.
이 상태에서는:
Entity
X
Persistence Context
가 된다.
따라서 더 이상 해당 엔티티의 변경사항을 JPA가 자동으로 추적하지 않는다.
🗑️ 13. 삭제 상태
엔티티를 삭제하려면:
entityManager.remove(user);
를 호출한다.
그러면 해당 엔티티는 삭제 대상으로 관리된다.
트랜잭션이 Flush되는 과정에서 실제 DELETE SQL이 실행된다.
remove()
↓
삭제 대상으로 등록
↓
Flush
↓
DELETE SQL
↓
Database 반영
🔎 14. EntityManager의 주요 메서드
EntityManager를 직접 사용할 때 자주 등장하는 메서드는 다음과 같다.
| 메서드 | 역할 |
|---|---|
persist() | 새로운 엔티티를 영속 상태로 등록 |
find() | 엔티티 조회 |
remove() | 엔티티 삭제 |
detach() | 엔티티를 영속성 컨텍스트에서 분리 |
clear() | 영속성 컨텍스트 전체 초기화 |
flush() | 변경 내용을 DB에 반영 |
merge() | 준영속 엔티티를 병합 |
각각의 동작은 영속성 컨텍스트와 밀접하게 연결되어 있다.
🛠️ 15. persist()는 바로 INSERT를 실행할까?
다음 코드를 보자.
User user = new User("콩이");
entityManager.persist(user);
여기서 흔히 하는 오해가 있다.
persist()
↓
즉시 INSERT SQL
이라고 생각하는 것이다.
하지만 persist()의 핵심은 엔티티를 영속성 컨텍스트에 등록하는 것이다.
실제 SQL 실행 시점은 Flush 정책과 트랜잭션 상황 등에 따라 달라질 수 있다.
즉:
persist()
↓
Persistence Context 등록
↓
Flush
↓
INSERT SQL
흐름으로 이해하는 것이 좋다.
🧠 16. Dirty Checking이란?
JPA의 대표적인 기능 중 하나가 Dirty Checking이다.
말 그대로:
"관리 중인 엔티티의 상태가 변경되었는지 확인한다."
는 개념이다.
예를 들어:
@Transactional
public void changeName(Long userId) {
User user = entityManager.find(User.class, userId);
user.changeName("두부");
}
여기에는 다음 코드가 없다.
entityManager.save(user);
또는:
entityManager.update(user);
그런데도 데이터베이스가 변경될 수 있다.
이것이 Dirty Checking 때문이다.
🔎 17. Dirty Checking은 어떻게 동작할까?
JPA는 엔티티가 영속성 컨텍스트에 들어올 때 엔티티의 상태를 관리한다.
개념적으로 보면:
조회 당시 상태
name = "콩이"
↓
Entity 변경
name = "두부"
↓
Flush 시점
조회 당시 상태와 비교
↓
변경 감지
↓
UPDATE SQL
이렇게 동작한다.
따라서:
user.changeName("두부");
처럼 엔티티의 상태만 변경해도 트랜잭션 종료 과정에서 변경사항이 데이터베이스에 반영될 수 있다.
🔥 18. Dirty Checking에서 중요한 점
Dirty Checking은 영속 상태의 엔티티를 대상으로 한다.
예:
User user = entityManager.find(User.class, 1L);
user.changeName("두부");
이 경우 user는 영속 상태이므로 변경을 추적할 수 있다.
반면:
User user = ...;
entityManager.detach(user);
user.changeName("두부");
처럼 준영속 상태가 되면 해당 변경사항은 일반적인 Dirty Checking 대상이 아니다.
따라서:
영속성 컨텍스트
+
영속 상태 Entity
+
Transaction
↓
Dirty Checking
의 관계를 함께 이해하는 것이 중요하다.
🚽 19. Flush란 무엇인가?
JPA를 이해하다 보면 flush()라는 개념이 자주 등장한다.
Flush는 쉽게 말하면:
영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 과정
이다.
예를 들어:
user.changeName("두부");
를 실행했다고 하자.
이 순간 바로 DB에 UPDATE가 실행된다고 생각하기보다는:
Java Entity 변경
↓
Persistence Context 변경
↓
Flush
↓
UPDATE SQL
↓
Database
흐름으로 이해하는 것이 좋다.
🔎 20. Flush와 Commit은 같은 것일까?
아니다.
이 둘은 자주 혼동하지만 서로 다른 개념이다.
Flush
→ 변경 내용을 DB에 동기화
Commit
→ Transaction을 최종 확정
즉 Flush가 발생했다고 해서 Transaction이 최종적으로 성공한 것은 아니다.
개념적으로:
Entity 변경
↓
Flush
↓
SQL 실행
↓
Transaction Commit
↓
최종 반영
과 같은 흐름으로 이해할 수 있다.
만약 Flush 이후 Transaction이 Rollback된다면 해당 변경사항은 최종적으로 반영되지 않는다.
⚙️ 21. Flush는 언제 발생할까?
Flush는 여러 상황에서 발생할 수 있다.
대표적으로:
Transaction Commit
JPQL / Query 실행 전
entityManager.flush() 직접 호출
등이 있다.
특히 JPQL을 실행하기 전에 영속성 컨텍스트의 변경 내용을 DB와 맞춰야 하는 상황에서는 Flush가 발생할 수 있다.
예를 들어:
User user = entityManager.find(User.class, 1L);
user.changeName("두부");
List<User> users = entityManager
.createQuery(
"select u from User u",
User.class
)
.getResultList();
이런 상황에서는 변경된 엔티티 상태와 쿼리 결과의 일관성을 맞추기 위해 Flush가 고려된다.
🧠 22. Transaction과 영속성 컨텍스트
JPA를 이해할 때 Transaction도 함께 봐야 한다.
일반적인 Spring Data JPA 환경에서는 다음과 같은 코드가 있다.
@Transactional
public void changeName(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow();
user.changeName("두부");
}
흐름을 단순화하면:
Transaction 시작
↓
Persistence Context 생성/참여
↓
Entity 조회
↓
Entity 변경
↓
Dirty Checking
↓
Flush
↓
UPDATE SQL
↓
Transaction Commit
이 구조를 이해하면 왜 별도의 save() 호출 없이 엔티티 변경사항이 반영되는지 이해할 수 있다.
🔗 23. @PersistenceContext와 Transaction의 관계
다시 처음의 코드로 돌아가 보자.
@PersistenceContext
private EntityManager entityManager;
이 EntityManager는 단순히 애플리케이션 전체에서 하나의 객체를 공유하는 방식으로 생각하면 안 된다.
Spring이 JPA EntityManager를 관리하면서 현재 트랜잭션과 연결된 영속성 컨텍스트를 사용할 수 있도록 구성한다.
따라서 같은 트랜잭션 안에서:
entityManager.find(User.class, 1L);
와 같은 작업을 수행하면 해당 트랜잭션의 영속성 컨텍스트를 통해 엔티티가 관리된다.
이 때문에 EntityManager를 사용할 때는 단순히 객체 하나를 주입받았다고 생각하기보다는:
Spring
↓
EntityManager 관리
↓
Transaction
↓
Persistence Context
↓
Entity 관리
관계를 이해하는 것이 중요하다.
🧩 24. EntityManager는 Thread Safe한가?
여기서 또 하나 중요한 점이 있다.
JPA의 EntityManager는 일반적으로 Thread Safe한 객체가 아니다.
따라서 여러 스레드에서 하나의 EntityManager 인스턴스를 직접 공유하는 방식으로 사용하면 안 된다.
하지만 Spring에서:
@PersistenceContext
private EntityManager entityManager;
처럼 주입받아 사용하는 경우에는 개발자가 직접 EntityManager의 생명주기를 관리하는 것이 아니다.
Spring이 적절한 방식으로 EntityManager를 관리하고 현재 트랜잭션에 연결된 EntityManager를 사용할 수 있도록 처리한다.
따라서 개발자는 일반적으로 다음과 같이 사용한다.
@PersistenceContext
private EntityManager entityManager;
그리고:
@Transactional
public void updateUser(Long userId) {
User user = entityManager.find(User.class, userId);
user.changeName("두부");
}
처럼 비즈니스 로직을 작성한다.
🔎 25. merge()는 save()와 같은 것일까?
EntityManager를 사용하다 보면 merge()도 자주 접하게 된다.
entityManager.merge(user);
하지만 merge()를 단순히:
save()
와 동일한 개념으로 생각하면 안 된다.
merge()의 핵심은 준영속 상태의 엔티티 데이터를 새로운 영속 엔티티에 병합하는 것이다.
중요한 점은 merge()가 전달받은 객체 자체를 다시 영속 상태로 만드는 것이 아니라는 것이다.
개념적으로:
Detached Entity
│
│ merge()
▼
새로운 Managed Entity
│
▼
Persistence Context
구조가 된다.
따라서 merge() 이후에는 반환된 엔티티를 사용하는 것이 중요하다.
User managedUser = entityManager.merge(user);
⚠️ 26. merge()를 무조건 사용할 필요는 없다
Spring Data JPA를 사용하다 보면 merge()를 직접 호출할 일이 생각보다 많지 않다.
특히 일반적인 서비스 로직에서는:
@Transactional
public void updateUser(Long userId, String name) {
User user = userRepository.findById(userId)
.orElseThrow();
user.changeName(name);
}
처럼 현재 트랜잭션의 영속성 컨텍스트에서 엔티티를 조회하고 상태를 변경하는 방식이 더 자연스럽다.
즉:
조회
↓
영속 상태
↓
상태 변경
↓
Dirty Checking
↓
Flush
구조를 활용하는 것이다.
📦 27. Spring Data JPA에서는 @PersistenceContext를 왜 잘 안 볼까?
Spring Boot 프로젝트를 처음 시작하면 다음과 같은 코드를 많이 사용한다.
public interface UserRepository
extends JpaRepository<User, Long> {
}
이 경우 개발자가 직접:
@PersistenceContext
private EntityManager entityManager;
를 작성하지 않아도:
userRepository.findById(id);
userRepository.save(user);
userRepository.delete(user);
같은 기능을 사용할 수 있다.
그 이유는 Spring Data JPA가 내부적으로 EntityManager를 활용해 Repository 기능을 구현하기 때문이다.
즉:
Application
↓
UserRepository
↓
Spring Data JPA
↓
EntityManager
↓
Persistence Context
↓
Database
구조로 연결된다.
따라서 JpaRepository를 사용하는 일반적인 CRUD에서는 EntityManager를 직접 다룰 필요가 많지 않다.
🛠️ 28. 그렇다면 EntityManager를 직접 사용하는 이유는?
Spring Data JPA가 대부분의 CRUD 기능을 제공하지만 EntityManager를 직접 사용하는 경우도 있다.
대표적인 경우는:
복잡한 동적 쿼리
Bulk 처리
Persistence Context 제어
Flush / Clear 직접 제어
JPA 자체 기능 사용
QueryDSL과 함께 세밀한 처리
등이다.
예를 들어 대량 데이터를 처리한 뒤 영속성 컨텍스트를 비우고 싶다면:
entityManager.flush();
entityManager.clear();
를 사용할 수 있다.
🧹 29. flush()와 clear()를 함께 사용하는 경우
대량 데이터를 반복해서 처리한다고 생각해보자.
for (int i = 0; i < 10000; i++) {
User user = new User(...);
entityManager.persist(user);
}
이런 방식으로 많은 엔티티를 하나의 영속성 컨텍스트에서 계속 관리하면 메모리 부담이 커질 수 있다.
이런 상황에서는:
entityManager.flush();
entityManager.clear();
를 적절한 단위로 사용하는 방법을 고려할 수 있다.
예:
1000건 처리
↓
flush()
↓
DB 반영
↓
clear()
↓
Persistence Context 초기화
↓
다음 1000건
다만 실제로는 데이터 규모와 트랜잭션 구조, 배치 처리 방식 등을 함께 고려해야 한다.
🔥 30. JPQL과 EntityManager
EntityManager를 사용하면 JPQL을 직접 실행할 수도 있다.
예:
List<User> users = entityManager
.createQuery(
"select u from User u where u.name = :name",
User.class
)
.setParameter("name", "콩이")
.getResultList();
여기서 중요한 점은 JPQL이 테이블이 아니라 엔티티를 대상으로 작성된다는 것이다.
SQL에서는:
SELECT *
FROM users
WHERE name = ?
처럼 테이블을 기준으로 생각한다.
반면 JPQL에서는:
select u
from User u
where u.name = :name
처럼 엔티티와 필드를 기준으로 작성한다.
JPA는 이를 SQL로 변환해 데이터베이스에 전달한다.
🧠 31. EntityManager와 QueryDSL의 관계
QueryDSL을 사용하는 프로젝트에서도 EntityManager는 내부적으로 중요한 역할을 한다.
QueryDSL JPA에서는 일반적으로:
JPAQueryFactory queryFactory;
를 통해 쿼리를 작성한다.
예:
queryFactory
.selectFrom(user)
.where(user.name.eq("콩이"))
.fetch();
여기서 QueryDSL이 직접 데이터베이스와 통신하는 것이 아니라 JPA의 EntityManager를 기반으로 쿼리를 실행한다.
개념적으로:
QueryDSL
↓
JPAQueryFactory
↓
EntityManager
↓
JPA
↓
Database
라고 볼 수 있다.
따라서 QueryDSL을 사용하고 있다면 EntityManager가 내부적으로 어떤 역할을 하고 있는지 이해해두는 것이 좋다.
🔎 32. @PersistenceContext와 @Autowired의 차이
Spring에서 의존성을 주입하다 보면 다음과 같은 코드도 생각할 수 있다.
@Autowired
private EntityManager entityManager;
그렇다면 왜 JPA에서는:
@PersistenceContext
private EntityManager entityManager;
를 사용하는 것일까?
둘은 목적과 의미가 조금 다르다.
@PersistenceContext는 JPA 표준에서 제공하는 어노테이션으로 Persistence Context와 관련된 EntityManager 주입을 표현한다.
반면 @Autowired는 Spring의 의존성 주입 기능이다.
즉:
@PersistenceContext
→ JPA 표준의 EntityManager 주입 의미
@Autowired
→ Spring의 의존성 주입 의미
로 구분할 수 있다.
실무에서는 Spring이 JPA의 EntityManager를 관리해주기 때문에 둘의 동작이 비슷하게 보일 수 있지만, 의미와 추상화 레벨은 다르다.
🧩 33. @PersistenceContext의 기본 사용 예
가장 전형적인 코드를 하나 만들어보자.
@Repository
public class UserRepository {
@PersistenceContext
private EntityManager entityManager;
public User findById(Long id) {
return entityManager.find(User.class, id);
}
public void save(User user) {
entityManager.persist(user);
}
public void delete(User user) {
entityManager.remove(user);
}
}
그리고 Service:
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
@Transactional
public void changeName(
Long userId,
String name
) {
User user = userRepository.findById(userId);
user.changeName(name);
}
}
여기서 save()를 호출하지 않아도 변경사항이 반영될 수 있다.
왜냐하면:
findById()
↓
영속성 컨텍스트에 Entity 등록
↓
user.changeName()
↓
Dirty Checking
↓
Flush
↓
UPDATE SQL
이 발생하기 때문이다.
⚠️ 34. @Transactional 없이 사용하면 어떻게 될까?
EntityManager를 사용하는 로직에서는 Transaction의 존재가 중요하다.
예를 들어:
public void changeName(Long userId) {
User user = entityManager.find(User.class, userId);
user.changeName("두부");
}
처럼 Transaction 없이 상태를 변경하려고 하면 JPA 작업의 종류와 실행 시점에 따라 문제가 발생할 수 있다.
특히:
entityManager.persist(user);
와 같은 변경 작업은 일반적으로 활성화된 Transaction이 필요하다.
따라서 일반적인 서비스 계층에서는:
@Transactional
을 통해 작업의 경계를 명확하게 설정한다.
🔄 35. 하나의 Transaction 안에서 일어나는 일
다음 코드를 기준으로 생각해보자.
@Transactional
public void updateUser(
Long userId,
String name
) {
User user = entityManager.find(
User.class,
userId
);
user.changeName(name);
}
내부적으로는 대략 다음과 같은 흐름이 만들어진다.
Transaction 시작
│
▼
Persistence Context
│
▼
Entity 조회
│
▼
User 영속 상태
│
▼
changeName()
│
▼
Dirty Checking
│
▼
Flush
│
▼
UPDATE SQL
│
▼
Commit
이 구조가 바로 JPA를 사용하는 가장 중요한 기본 흐름 중 하나다.
🧠 36. @PersistenceContext를 이해할 때 핵심적으로 기억할 것
@PersistenceContext 자체에 너무 많은 의미를 부여할 필요는 없다.
중요한 것은 다음 관계를 이해하는 것이다.
@PersistenceContext
↓
EntityManager
↓
Persistence Context
↓
Entity 상태 관리
↓
Dirty Checking / 1차 캐시 / Flush
↓
Transaction
↓
Database
즉:
@PersistenceContext
private EntityManager entityManager;
를 보면 단순히:
EntityManager를 주입받는구나.
에서 끝내지 않고:
현재 JPA가 관리하는 영속성 컨텍스트와 상호작용하기 위한 EntityManager를 주입받는구나.
정도로 이해하면 좋다.
📝 37. @PersistenceContext와 Spring Data JPA를 함께 정리하면
Spring Data JPA를 사용하는 프로젝트에서는 대부분 다음과 같이 작성한다.
public interface UserRepository
extends JpaRepository<User, Long> {
}
그리고:
userRepository.findById(id);
userRepository.save(user);
userRepository.delete(user);
처럼 사용한다.
이 과정에서도 내부적으로는 JPA의 EntityManager와 Persistence Context가 동작한다.
즉 개발자가 직접 사용하지 않는다고 해서 EntityManager가 사라지는 것은 아니다.
오히려:
Spring Data JPA
→ EntityManager
→ Persistence Context
→ JPA
→ Database
구조를 추상화해서 개발자가 더 편하게 사용할 수 있도록 만든 것이다.
🔎 38. 언제 @PersistenceContext를 직접 사용할까?
일반적인 CRUD에서는:
JpaRepository
만으로 충분한 경우가 많다.
하지만 다음과 같은 상황에서는 EntityManager를 직접 사용하는 것이 유용할 수 있다.
복잡한 JPA 기능을 직접 제어해야 하는 경우
↓
영속성 컨텍스트를 직접 flush / clear해야 하는 경우
↓
JPA 기반의 커스텀 Repository 구현
↓
특정 JPQL을 직접 실행해야 하는 경우
↓
Bulk 작업 후 영속성 컨텍스트를 제어해야 하는 경우
다만 EntityManager를 직접 사용한다고 해서 더 좋은 코드가 되는 것은 아니다.
추상화가 제공하는 편리함을 포기하고 직접 제어하는 만큼, 왜 직접 제어해야 하는지 이유가 명확해야 한다.
🎯 마무리
@PersistenceContext를 처음 접하면:
@PersistenceContext
private EntityManager entityManager;
라는 한 줄만 기억하기 쉽다.
하지만 실제로 중요한 것은 이 한 줄 자체가 아니라 그 뒤에 있는 구조다.
@PersistenceContext
↓
EntityManager
↓
Persistence Context
↓
Entity 상태 관리
↓
Dirty Checking
↓
Flush
↓
Transaction
↓
Database
@PersistenceContext는 JPA가 관리하는 EntityManager를 주입받기 위한 JPA 표준 어노테이션이다.
그리고 EntityManager는 엔티티를 직접 관리하기 위한 인터페이스이며, 실제 엔티티의 상태를 관리하는 중심에는 영속성 컨텍스트가 존재한다.
영속성 컨텍스트를 이해하면 JPA에서 자주 등장하는 개념들도 자연스럽게 연결된다.
find()
↓
1차 캐시
↓
영속 상태
↓
Dirty Checking
↓
Flush
↓
UPDATE
또한:
persist()
↓
영속성 컨텍스트 등록
↓
Flush
↓
INSERT
그리고:
remove()
↓
삭제 대상 등록
↓
Flush
↓
DELETE
와 같은 JPA의 동작 방식도 이해할 수 있다.
특히 Spring Data JPA를 사용하는 환경에서는 EntityManager를 직접 호출하는 일이 많지 않기 때문에 오히려 내부 동작을 놓치기 쉽다.
userRepository.findById(id);
user.changeName("두부");
라는 간단한 코드 뒤에서도:
Repository
↓
EntityManager
↓
Persistence Context
↓
Dirty Checking
↓
Flush
↓
Database
라는 과정이 동작하고 있다.
결국 @PersistenceContext를 이해한다는 것은 단순히 어노테이션 하나를 외우는 것이 아니라,
JPA가 엔티티를 어떻게 관리하고, 언제 데이터베이스와 동기화하는지를 이해하는 것
에 가깝다.
그리고 이 흐름을 이해하고 나면 JPA를 사용할 때 자주 발생하는 다음과 같은 질문에도 답하기 쉬워진다.
왜 save()를 호출하지 않아도 UPDATE가 발생하는가?
왜 같은 Entity를 여러 번 조회해도 같은 객체가 반환되는가?
왜 Transaction이 중요한가?
왜 flush()와 commit은 다른가?
왜 clear()가 필요한 경우가 있는가?
왜 준영속 Entity의 변경은 자동으로 반영되지 않는가?
왜 Spring Data JPA를 사용하는데 EntityManager라는 개념이 등장하는가?
결국 JPA를 제대로 이해하기 위해서는 Repository API만 보는 것보다 그 아래에서 동작하는:
EntityManager
+
Persistence Context
+
Entity Lifecycle
+
Dirty Checking
+
Flush
+
Transaction
의 관계를 함께 이해하는 것이 중요하다.
@PersistenceContext는 그 구조를 들여다보기 위한 좋은 출발점이다.
