🍳머리말
JPA에서 자주 등장하는 OSIV(Open Session In View) 개념에 대해 알아보겠습니다. 이 글은 OSIV의 정의, 특징, 장단점, 실무 사용 예제 그리고 최신 트렌드에 대해 설명합니다.
📕 정의
📔 OSIV
Hibernate와 JPA에서 사용되는 개념. 영속성-컨텍스트(Session 또는 EntityManager)의 생명주기를 요청-응답(View Rendering) 전체로 확장하는 전략. View에서 데이터에 접근할 때도 DB 연결을 유지할 수 있도록 설계
📑 영속성 컨텍스트
- JPA에서 엔티티를 관리하는 공간
- EntityManager가 엔티티를 "1차 캐시"처럼 관리
- 데이터베이스에서 가져온 엔티티를 영속성 컨텍스트가 관리하기 때문에 같은 트랜잭션 안에서는 동일한 엔티티를 여러 번 조회하더라도 같은 객체를 반환
📘 예시
EntityManager em = ...;
User user1 = em.find(User.class, 1L); // DB에서 가져옴
User user2 = em.find(User.class, 1L); // 1차 캐시에서 가져옴
// user1 == user2 -> true
📑 생명주기를 요청-응답 전체로 확장
OSIV는 요청부터 응답이 끝날 때까지 EntityManager또는 Hibernate Session을 열어둠
원래(OSIV가 없는 상태)는 어떻게 동작?
- 일반적으로 트랜잭션 범위 내에서만 유지
- 트랜잭션이 끝나면 EntityManager가 닫히고 영속성 컨텍스트도 사라짐
- 이 상태에서 Lazy Loading을 시도하면 LazyInitializationException이 발생
- Lazy Loading이 불가능하므로 controller나 service에서 필요한 데이터를 명시적으로 fetch join해 로딩해야함
📘 예시
<!-- Thymeleaf user-detail.html -->
<p>User Name: ${user.name}</p>
<p>Orders:
<ul>
<!-- orders는 Lazy 로딩 필드 -->
<li th:each="order : ${user.orders}">
<span th:text="${order.name}"></span>
</li>
</ul>
</p>
@GetMapping("/user/{id}")
public String getUser(@PathVariable Long id, Model model) {
// Service 레이어에서 user를 가져오지만 orders는 Lazy 상태
User user = userService.findUserById(id); // orders는 아직 로드되지 않음
model.addAttribute("user", user);
return "user-detail"; // View에서 Lazy 필드 호출 시 문제 발생
}
따라서 fetch join을 통해 해결합니다.
@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :id")
User findUserWithOrders(@Param("id") Long id);
@GetMapping("/user/{id}")
public String getUser(@PathVariable Long id, Model model) {
// orders까지 명시적으로 로드
User user = userService.findUserWithOrders(id);
model.addAttribute("user", user);
return "user-detail"; // Lazy Initialization 문제 없음
}
OSIV가 있는 상태에서는
- OSIV 활성시 트랜잭션이 끝난 후에도 영속성 컨텍스트가 열려있음. 때문에 View에서 데이터에 접근할 때도 DB연결이 유지됨. 그러므로, view(redering)단계에서 엔티티의 Lazy Loading이 가능함
📘 예시
@GetMapping("/user/{id}")
public String getUser(@PathVariable Long id, Model model) {
User user = userService.findUserById(id); // 트랜잭션 내에서 엔티티 로드
model.addAttribute("user", user); // View에서 Lazy 필드 호출 가능
return "user-detail"; // user.getOrders() 호출 시 Lazy Loading 동작
}
📕 주요 특징
📔 Lazy Loading
📑 웹 앱에서 Lazy Loading이 가능
📑 Controller나 service 계층이 아닌 View 계층에서도 Lazy Loading 테이블에 접근 가능.
📘 예시
@Entity
public class User {
@OneToMany(mappedBy = "user")
private List<Order> orders;
}
// Controller
@GetMapping("/user/{id}")
public String getUser(@PathVariable Long id, Model model) {
User user = userService.findUserById(id);
model.addAttribute("user", user); // View에서 user.getOrders() 호출 가능
return "user-detail";
}
View에서 user.getOrders() 호출 시 Lazy Loading 동작. DB에서 필요한 데이터 가져옴
📕 실무 사용 예제와 그 이유
📔 OSIV 사용 이유
- 데이터 조회 간소화 : view에서 lazy loading가능하므로 별도의 fetch join이 필요 없음
- 생산성 향상: service계층에서 모든 데이터를 명시적으로 로드하지 않아도 됨
📘 예시
1. 단순 CRUD 웹 앱
사용자 정보와 관련된 주문 내역을 함께 표시하는 경우. Lazy 로딩이 필요한 경우, OSIV를 활용하면 추가적인 코드 작성 없이 View 단에서 데이터 조회 가능.
@GetMapping("/user/{id}")
public String getUser(@PathVariable Long id, Model model) {
User user = userService.findUserById(id);
model.addAttribute("user", user); // Lazy 필드 View에서 로드
return "user-detail";
}
2. 관리 도구(Admin Tool)
관리자는 데이터의 전체 관계를 확인해야 하므로 Lazy Loading 필드 다수. OSIV이용해 자유로운 로딩
📕 장단점
📔 장점
📑 개발 편의성
view에서 데이터 로드 가능 lazyinitializationException 방지
📑 코드 단순화
Controller와 service에서 데이터 fetch join 안해도 됨
📔 단점
📑 성능 이슈
OSIV로 인해 요청-응답 동안 DB 연결 유지. 요청 많아지면 DB 커넥션 풀이 부족해질 수 있음
📑 의도치 않은 Lazy loading
view에서 예상하지 못해 쿼리가 다수 발생하는 N+1 문제로 이어질 수 있음
📑 트랜잭션 관리 복잡성 증가
트랜잭션이 끝난 후에도 영속성 컨텍스트가 걸려있어 데이터 변경 불가. 변경하려 할 경우 예외 발생 가능
📕 새로운 트렌드
📔 OSIV 비활성화와 DTO 패턴 도입
📑 OSIV 비활성화 도입
Spring boot 2.3부터 기본적으로 비활성화. 이를 통해 요청-응답 이후 DB연결 끊어 문제 완화
📑 DTO 사용
service 계층에서 필요한 데이터만 추출해서 사용하는 방식 사용. Lazy 필드 대신 명시적으로 fetch join 사용. 필요한 데이터만 DTO에 담아 전송
📘 예시
@Query("SELECT new com.example.dto.UserDTO(u.name, o.name) FROM User u JOIN u.orders o WHERE u.id = :id")
UserDTO findUserWithOrders(@Param("id") Long id);
📑 GraphQL 도입
프론트엔드에서 필요한 데이터만 쿼리해서 가져올 수 있음. OSIV없어도 조회 가능
📕참조
- Spring Boot 공식 문서: Spring Data JPA
- Hibernate Reference Documentation: Hibernate ORM
- GraphQL 공식 문서: GraphQL
*더 나은 내용을 위한 지적, 조언은 언제나 환영합니다.
'Java > Spring' 카테고리의 다른 글
(Spring) - 객체 생명주기 (1) | 2024.11.28 |
---|