OSIV (open Session/Entitymanger In View)
이 글은 JPA에 대한 기본 지식을 가지고 있다고 가정하며 설명했습니다.
OSIV
OSIV 의 전제 조건은 "요청(Request) 전체 동안 영속성 컨텍스트를 열어 둘 것인가?" 라는게 OSIV 의 핵심입니다.
JPA 에서는 데이터를 가져올 때 EntityManger(영속성 컨텍스트) 라는 "문" 을 열어주어야 합니다.
이 문을 언제 닫느냐가 OSIV 설정에 따라 달라지게 됩니다.
OSIV 에서는 설정이 2가지가 있습니다. ON, OFF 입니다.
ON 은 요청이 시작해서 끝날 때까지 문을 계속 열어두는 것이고, OFF 는 서비스(Service, 비즈니스로직) 이 끝나면 바로 문을 닫는 것입니다.
ON 일때
영속성 컨텍스트 범위를 Filter Interceptor, Controller, Service, Repository 까지 전부 열어두는 옵션입니다.
Service 계층 뿐만 아니라 Controller 나 View 에서 지연로딩으로 설정한 데이터들을 꺼내오거나 하는 작업을 할 때 유용합니다.
에를 들어 Service 에서 주문을 조회했는데, Controller 에서 주문한 유저 정보를 꺼내오고 싶다면, 그 순간 DB 에 다시 물어볼 수 있습니다. 이렇게 설정하면 개발할 때 편리합니다.
하지만 ON 으로 설정했을 때 가장 큰 단점은 DB 에 물어보기 전에 3초 이상 걸리는 알고리즘이 존재할 경우 커넥션을 계속 물고있어 다른 사람에게 커넥션을 주지 못해 커넥션 고갈 현상이 발생할 수도 있습니다.
OFF 일때
서비스에서 데이터를 다 꺼내놓고, Controller/View 에서는 완성된 결과만을 넘기면 됩니다.
주문과 주문자 이름이 필요하면 서비스에서 미리 DB 에 다 가져와서 DTO(결과 객체) 에 담고, Controller 에다가 DTO 만 전달하면 됩니다. OFF 일때의 단점은 개발할 때 귀찮습니다. 서비스에서 미리 DTO 변환, fetch join 등 작업을 더 해야하기 때문입니다.
사용하는 기준
ON
빠른 화면 개발, 작은 프로젝트, 관리자용 툴
OFF
대규모 서비스, 많은 사용자가 쓰는 서비스
실제 사용하는 코드
Order(주문) 엔티티가 있고, Order 는 Member(회원) 과 연관관계(@ManyToOne) 를 가지며, 우리는 주문과 회원 이름을 화면에 보여주고 싶다고 가정해봅시다.
OSIV ON 일 경우
서비스에서 그냥 주문만 가져오면 됩니다.
컨트롤러나 뷰에서 지연로딩(LAZY) 가 자동으로 작동해서 회원 이름까지 가져오게 됩니다.
// Repository
public interface OrderRepository extends JpaRepository<Order, Long> {}
// Service
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
@Transactional(readOnly = true)
public Order getOrder(Long id) {
return orderRepository.findById(id)
.orElseThrow(() -> new IllegalArguementException("없는 주문"));
}
}
// Controller
@RestController
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@GetMapping("/orders/{id}")
public String getOrder(@PathVariable Long id) {
Order order = orderService.getOrder(id);
// 서비스가 끝났어도 OSIV ON 설정을 해서 영속성 컨텍스트 접근 가능
String memberName = order.getMember().getName();
return "success";
}
}
이렇게 Controller 에서 영속성 컨텍스트를 통해 DB 에 접근하여 Member 정보를 가져와 편리하게 사용할 수 있습니다.
하지만 이러한 방법은 위에서 말했듯이 3초이상 걸리는 API 가 Member 정보를 가져오기 전에 수행중이라면 커넥션을 계속 반환할 수 없습니다.
OSIV OFF 인 경우
Controller 에서는 DB 에 다시 접근할 수가 없습니다.
서비스에서 필요한 데이터(Member 이름 포함) 를 미리 DTO 에 담아 반환해줘야 합니다.
// DTO
@AllArgsConstructor
public class OrderDto {
private Long orderId;
private String memberName;
}
// Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("select o from Order o join fetch o.member where o.id = :id")
Optional<Order> findOrderWithMemberById(@Param("id") Long id);
}
// Service ( Entity -> DTO )
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
@Transactional(readOnly = true)
public OrderDto getOrder(Long id) {
Order order = orderRepository.findOrderWithMemberId(id)
.orElseThrow(() -> new IllegalArgumentException("없는 주문"));
return new OrderDto(order.getId(), order.getMember().getName());
}
}
// Controller
@Controller
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@GetMapping("/orders/{id}")
public OrderDto getOrder(@PathVariable Long id) {
return orderService.getOrder(id);
// DB 에 접근은 이미 서비스에서 끝남 -> Controller 안전
}
}
이렇게 OSIV 를 OFF 로 설정하면 Service 계층에서 DB 접근이 끝나버리니까 Controller 보안, 설계적으로 바람직한 결과가 나타납니다.
OSIV 를 사용해야 하나?
글을 읽어보면 이해를 하면 할수록 궁금증이 생길 수 있습니다.
그럼 그냥 OSIV 를 설정하지 않고 Service 계층에서 작업을 끝내버리면 되는거 아닌가?
우리가 궁금했던 부분처럼 컨트롤러에 오기 전에 서비스에서 필요한 데이터를 다 가져와서 DTO 로 변환하면, 사실 OSIV ON/OFF 에 크게 영향을 받지는 않습니다. 하지만 초창기 스프링 MVC + JPA 개발자들이 컨트롤러나 뷰에서도 지연로딩을 쓰고 싶은데 해당 구간에서 사용하게 된다면 LazyInitializationException 이 발생하는 문제점이 있었습니다.
그래서 Hibernate 팀에서 요청 전체에서 EntityManger 를 열어두는 OSIV 라는 방식을 제공한 것입니다.
즉, 개발 편의성을 위해 생긴 기능이며, 원칙적인 구조는 아닙니다.
또한 여러 개발자가 알고리즘을 만드는 중에 Controller 에서 영속성 컨텍스트에 접근하는 코드가 발생하는 경우 OFF 로 설정을 하여 사전에 방지할 수 있는 상황도 생길 수 있습니다.
'Spring' 카테고리의 다른 글
WAR 배포 및 분석 (0) | 2025.09.25 |
---|---|
Spring 외부설정, 조회방법 (0) | 2025.09.22 |
JPA 에 대하여 (8) | 2025.08.25 |
Spring 에서 CORS 설정 (3) | 2025.07.09 |
@Aspect - AOP (0) | 2025.05.02 |