| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- 단위테스트
- 배열
- vscode
- 문자열
- 스프링부트
- math
- CSS
- js
- Java
- javascript
- SpringBoot
- 정규식
- Eclipse
- IntelliJ
- 자바스크립트
- input
- Array
- html
- 인텔리제이
- Visual Studio Code
- HashMap
- junit5
- string
- 테스트자동화
- ArrayList
- junit
- 자바
- java테스트
- list
- 자바문법
- Today
- Total
어제 오늘 내일
[SpringBoot/JPA] "SQL을 직접 짜지 마세요" 영속성 컨텍스트(Persistence Context)의 비밀 본문
[SpringBoot/JPA] "SQL을 직접 짜지 마세요" 영속성 컨텍스트(Persistence Context)의 비밀
hi.anna 2026. 3. 12. 00:23과거에 MyBatis나 JDBC를 쓸 때는 개발자가 직접 SQL을 한 땀 한 땀 짰습니다. "DB에 저장해!" 하면 INSERT 쿼리를 날리고, "수정해!" 하면 UPDATE 쿼리를 날렸죠.
하지만 JPA(ORM)의 세계는 다릅니다.
우리는 그저 자바 객체(Entity)를 다루기만 하면 됩니다. 그러면 JPA가 알아서 적절한 SQL을 생성해 DB에 날려줍니다.
이 모든 것을 가능하게 하는 JPA의 심장, '영속성 컨텍스트'에 대해 알아볼까요?
1. 영속성 컨텍스트가 뭔가요?
쉽게 말해 "애플리케이션과 데이터베이스 사이에 있는 중간 관리자(캐시)"입니다.
우리가 객체를 저장하거나 조회할 때, 바로 DB로 가는 게 아닙니다. 먼저 이 영속성 컨텍스트라는 곳에 보관됩니다. 그리고 JPA는 이 컨텍스트에 있는 정보를 바탕으로 필요한 시점(트랜잭션 커밋)에 DB와 통신합니다.
논리적인 개념입니다: 눈에 보이지는 않지만,
EntityManager라는 객체를 생성할 때 그 안에 1:1로 만들어집니다.
2. 왜 중간 관리자를 두나요? (4가지 이점)
그냥 바로 DB에 넣으면 편할 텐데, 왜 굳이 중간 단계를 거칠까요? 여기엔 어마어마한 성능 최적화의 비밀이 숨어 있습니다.
① 1차 캐시 (First Level Cache)
영속성 컨텍스트는 내부에 Map 형태의 캐시를 가지고 있습니다.
// 1. 첫 번째 조회: DB에서 가져와서 1차 캐시에 저장
Member member1 = em.find(Member.class, "member1");
// 2. 두 번째 조회: DB 안 가고 캐시에서 바로 줌 (빠름!)
Member member2 = em.find(Member.class, "member1");
- 장점: 똑같은 걸 두 번 조회해도 SQL은 한 번만 나갑니다.
② 동일성 보장 (Identity)
자바 컬렉션에서 꺼낸 객체처럼, JPA가 관리하는 객체는 == 비교가 참이 됩니다.
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); // true (주소값까지 같음!)
③ 트랜잭션을 지원하는 쓰기 지연 (Transactional Write-behind) ★
이게 대박입니다. em.persist(member)를 한다고 바로 INSERT SQL이 날아가지 않습니다.
transaction.begin(); // 트랜잭션 시작
em.persist(memberA);
em.persist(memberB);
// 여기까지는 SQL을 안 보내고 "쓰기 지연 SQL 저장소"에 모아둡니다.
transaction.commit(); // 커밋하는 순간! 모아둔 INSERT 쿼리 2개를 한방에 쏩니다. (Flush)
- 장점: 네트워크 통신 횟수를 줄여 성능을 최적화할 수 있습니다. (버퍼링 기능)
④ 변경 감지 (Dirty Checking) ★★★
JPA의 하이라이트입니다. 수정(Update) 쿼리를 날릴 필요가 없습니다.
// 1. 조회
Member member = em.find(Member.class, "member1");
// 2. 데이터 수정 (자바 객체 값만 바꿈)
member.setName("JPA Master");
// 3. update 코드 필요 없음! (em.update(member) 같은 거 없음)
transaction.commit();
- 원리:
- JPA는 값을 읽어올 때 최초 상태(스냅샷)를 복사해둡니다.
commit시점에 스냅샷과 현재 객체를 비교합니다.- "어? 이름이 바뀌었네?" -> 자동으로 UPDATE 쿼리 생성 후 DB에 반영.
3. 엔티티의 생명주기 (Lifecycle)
객체가 영속성 컨텍스트와 어떤 관계냐에 따라 상태가 나뉩니다.
- 비영속 (New/Transient): 객체를 생성만 한 상태. (JPA와 관계없음)
- 영속 (Managed):
em.persist(member)를 통해 영속성 컨텍스트에 저장된 상태. (이제부터 관리 대상!) - 준영속 (Detached): 관리되다가 쫓겨난 상태.
- 삭제 (Removed): 삭제된 상태.
4. 주의사항: Flush(플러시)란?
Flush는 영속성 컨텍스트의 변경 내용을 DB에 반영(동기화)하는 작업입니다.
- 언제 발생하나요?
transaction.commit()자동 호출- JPQL 쿼리 실행 시 자동 호출
em.flush()직접 호출 (거의 안 씀)
주의: 플러시는 캐시를 비우는 게 아닙니다! 변경 내용을 DB에 보내기만 할 뿐, 1차 캐시는 그대로 유지됩니다.
마치며
JPA를 쓴다는 것은 "DB 중심 설계"에서 "객체 중심 설계"로 패러다임을 전환하는 것입니다.
오늘 배운 1차 캐시와 변경 감지(Dirty Checking) 덕분에 우리는 더 이상 SQL 작성에 시간을 쏟지 않고, 비즈니스 로직에만 집중할 수 있게 되었습니다.
하지만 실무에서 EntityManager를 직접 쓸 일은 거의 없습니다. 다음 포스팅에서는 이것을 더 편하게 감싸서 사용하는 Spring Data JPA와 JpaRepository의 마법에 대해 알아보겠습니다.
도움이 되셨다면 좋아요와 댓글 부탁드립니다! 😊
