김영한 님의 "자바 ORM 표준 JPA 프로그래밍" 책을 정리한 포스팅 입니다.
1. 트랜잭션 범위의 영속성 컨텍스트
기본 전략
구분 | 내용 |
생명 주기 |
트랜잭션과 영속성 컨텍스트는 같은 생존 범위를 가짐
- 트랜잭션이 시작될 때 영속성 컨텍스트 생성 (초기화 상태) - 트랜잭션이 종료될 때 영속성 컨텍스트 종료 |
공유 |
- 같은 트랜잭션 간 같은 영속성 컨텍스트를 공유함
- 다른 트랜잭션 간 다른 영속성 컨텍스트를 가짐 |
예시) 공유
더보기
같은 트랜잭션
@Test
@Transactional
void same_transaction() {
User user1 = entityManager.find(User.class, 1);
user1.setName("test");
searchName();
}
private void searchName() {
User user = entityManager.find(User.class, 1);
assertEquals(user.getName(), "test");
}
다른 트랜잭션
@Transactional
public void methodA() {
Member member1 = em.find(Member.class, 1L);
member1.setName("노아");
}
@Transactional
public void methodB() {
Member member2 = em.find(Member.class, 1L); // DB에서 다시 조회
System.out.println(member2.getName()); // 출력: 원래 DB에 저장된 이름
}
- methodA의 변경은 커밋되지 않았다면 methodB에서는 반영되지 않음.
예시) 생명 주기
더보기
→ [Filter / Interceptor]
→ [Controller]
→ ┌──────────────────────────────────────────────────┐
│ 트랜잭션 범위 (영속성 컨텍스트 생존 범위) │
│ │
│ [Service (@Transactional 시작)] → [Repository] │
└──────────────────────────────────────────────────┘
→ [View (Model 반환)]
- Filter / Interceptor → Controller → View : 준영속 상태
- Service → Repository : 영속 상태
2. 준영속 상태와 지연 로딩
- 준영속 상태는 영속성 컨텍스트의 관리에서 벗어난 엔티티 상태. (ex. detach)
- 서비스 계층 이외는 준영속 상태이므로 다른 계층(뷰)에서 엔티티 접근 시 문제 발생
- 지연 로딩 실패 (LazyInitializationException)
- 변경 감지 실패 (Dirty Checking)
해결 방법
해결 방법 | 설명 | 장점 | 단점 |
JPQL Fetch Join | join fetch로 필요한 연관 엔티티 미리 로딩 → 지연 로딩 실패 해결 |
준영속 상태 발생 ❌
|
성능 문제 (N+1 발생)
뷰 의존 |
FACADE 계층 도입 | 프록시 초기화를 담당하는 계층을 도입 (Presentation Layer ↔️ Service Layer) FACADE 계층에서 트랜잭션 시작 (프록시 초기화를 위해 영속성 컨텍스트 필요) |
프록시 초기화 트랜잭션 관리 |
구조 복잡도 ↑
|
OSIV (Open Session In View) |
트랜잭션 범위를 뷰 렌더링까지 확장 |
자동으로 지연로딩
|
트랜잭션이 길어짐 → 성능 저하 → 리소스 점유 |
예제) JPQL Fetch Join
더보기
SELECT o FROM Order o JOIN FETCH o.customer WHERE o.status = 'SHIPPED'
예제) FACADE
더보기
[Presentation Layer]
│
▼
[Facade Layer] ← 프록시 초기화, 트랜잭션 시작
│
▼
[Service Layer] ← 비즈니스 로직 처리
│
▼
[Repository Layer] ← DB 접근
@Service
public class TeamService {
@Autowired
private TeamRepository teamRepository;
@Transactional(readOnly = true) // 트랜잭션 관리
public List<User> getUsersByTeamId(Integer teamId) {
Team team = teamRepository.findById(teamId).orElseThrow();
return team.getMembers(); // LAZY 로딩된 팀 멤버들
}
}
@Facade
public class TeamFacade {
@Autowired
private TeamService teamService;
@Transactional // Facade에서 트랜잭션 관리
public List<User> getUsersWithTeamName(Integer teamId) {
List<User> users = teamService.getUsersByTeamId(teamId);
// 프록시 초기화: LAZY 로딩된 Team 정보 초기화
users.forEach(user -> {
System.out.println("User: " + user);
user.getTeam().getName(); // team 필드를 강제로 초기화
});
return users;
}
}
@RestController
@RequestMapping("/teams")
public class TeamController {
@Autowired
private TeamFacade teamFacade;
@GetMapping("/{teamId}/users")
public List<User> getUsers(@PathVariable Integer teamId) {
return teamFacade.getUsersWithTeamName(teamId);
}
}
3. OSIV
- 영속성 컨텍스트(Persistence Context)를 뷰(View)까지 열어두는 방식
버전
항목 | 과거 OSIV (요청 당 트랜잭션) |
Spring OSIV (비즈니스 계층 트랜잭션)
|
OSIV 방식 | - 영속성 컨텍스트 생성: 서블릿 필터/인터셉터 - 트랜잭션 시작: 서블릿 필터/인터셉터 |
- 영속성 컨텍스트 생성: 서블릿 필터/인터셉터 - 트랜잭션 시작: 서비스 계층 |
영속성 컨텍스트 상태 | - 시작: 클라이언트 요청 시작 - 종료: 클라이언트 요청 종료 |
- 시작: 클라이언트 요청 시작 (트랜잭션 내 사용됨) - 종료: 클라이언트 요청 종료 |
트랜잭션 관리 | 프레젠테이션 계층에서 시작 | 서비스 계층에서 시작 |
영속성 컨텍스트 플러시 | 클라이언트 요청 종료 시 |
트랜잭션 종료 시
|
영속성 컨텍스트 종료 시점 | 클라이언트 요청 종료 시 | 클라이언트 요청 종료 시 |
트랜잭션 없이 읽기 | ❌ | ✅ (쿼리가 발생하지 않을 경우만 가능) |
주의 사항 | - 프레젠테이션 계층에서 엔티티 변경 ✅ - 수정을 막기 위한 방법 필요 (엔티티 래핑 등) |
- 프레젠테이션 계층에서 엔티티 직접 수정 ❌
- 서비스 계층에서 수정해야 엔티티 변경이 DB에 반영됨 |
셋팅) Spring OSIV
더보기
spring.jpa.open-in-view=true
- 클라이언트 요청 시작 ~ 응답 완료까지 영속성 컨텍스트가 열려 있음
4. 너무 엄격한 계층
- Spring OSIV는 뷰 계층에서 영속성 컨텍스트를 참조할 수 있는 방법을 제공
- OSIV를 사용하면 유연하고 실용적 관점으로 접근하는 좋은 방법임
'Spring > Spring Data JPA' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 16. 트랜잭션과 락, 2차 캐시 (1) | 2025.04.26 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 15. 고급 주제와 성능 최적화 (1) | 2025.04.26 |
[자바 ORM 표준 JPA 프로그래밍] 12. 스프링 데이터 JPA (0) | 2025.04.25 |
[자바 ORM 표준 JPA 프로그래밍] 10-5. 객체지향 쿼리 언어: 객체지향 쿼리 심화 (0) | 2025.04.24 |
[자바 ORM 표준 JPA 프로그래밍] 10-4. 객체지향 쿼리 언어: Native SQL (0) | 2025.04.24 |