-
OSIV (Open Session In View)
💡 OSIV는 영속성 컨텍스트를 뷰까지 열어둔다는 의미
영속성 컨텍스트가 살아있으면 엔티티는 영속상태로 유지가 된다.
따라서, 뷰에서도 지연로딩을 사용할 수 있다.
스프링 컨테이너의 기본 전략은 트랜잭션 범위의 영속성 컨텍스트 전략이다.
따라서, 서비스 계층에서 조회한 엔티티는 서비스와 리포지토리 계층에서는 영속성 컨텍스트에 관리되지만,
컨트롤러라 뷰 같은 프레젠테이션 계층에서는 준영속 상태가 된다.
- 프레젠테이션 계층에서는 영속성 컨텍스트에서 제공하는 기능을 사용하지 못한다
- 지연로딩 기능을 사용하지 못한다.
@Controller public class UserController { @PostMapping("/api/v1/users") public void signUp(@RequestBody UserDto userDto){ User user = userService.join(userDto.toEntity); //현재 user는 준영속 상태 Team team = user.getTeam(); //지연 로딩 시 예외 발생 team.getName(); } }
뷰를 렌더링 할 때 조회한 엔티티와 연관된 엔티티도 함께 사용해야 한다.
하지만, 연관된 엔티티를 지연로딩으로 설정하여 프록시 객체를 조회했다고 가정하면
- 프록시 객체는 초기화 되지 않은 상태로 프레젠테이션 계층에 반환된 상태이다.
- 이후 실제 데이터를 불러오려고 초기화를 시도할 경우 준영속 상태이기 때문에 지연로딩이 발생하지 않아 예외가 발생한다.
OEIV
- OSIV는 Hibernate 용어이다.
- JPA는 OEIV(Open EntityManager In View)라 한다
- 관례상 OSIV라고 부른다.
과거의 OSIV
가장 단순한 구현 방법
- 서블릿 필터 또는 스프링 인터셉터에서 트랜잭션을 시작하고 요청이 끝날 때 트랜잭션도 끝나는 것
⇒ 요청 당 트랜잭션방식의 OSIV라 한다.
- 요청이 들어오면 서블릿 필터나 스프링 인터셉터에서 영속성 컨텍스트를 만들면서 트랜잭션을 시작
- Controller, Service, Repository 층에서 모두 영속성 컨텍스트가 유지된다.
- 조회된 엔티티가 뷰까지 영속상태를 유지해 지연로딩이 가능하다
- 뷰에서도 지연로딩이 가능해 FACADE 계층 없이도 뷰에 독립적인 서비스 계층을 유지할 수 있다.
- 요청이 끝날 때 트랜잭션과 영속성 컨텍스트를 함께 종료한다.
문제점
컨트롤러나 뷰 같은 프레젠테이션 계층이 엔티티를 변경할 수 있다.
- 뷰를 렌더링 한 이후 트랜잭션을 커밋한다
- 트랜잭션 커밋시 영속성 컨텍스트를 flush하고 dirty checking 기능을 통해 변경된 엔티티 값을 DB에 반영한다
- 데이터가 변경되는 문제가 발생한다.
- ex) 고객 정보를 보여주는 과정에서 보안상 어떤 내용을 변경해서 보여줌
- 변경감지에 의해 아예 내용이 바뀌어서 DB에 저장된다.
해결방법
프레젠테이션 계층에서 엔티티를 수정하지 못하게 막으면 된다.
- 엔티티를 읽기 전용 인터페이스로 제공
- 엔티티를 읽기 전용 메서드만 가진 Wrapper 클래스로 매핑
- DTO만 반환
이런 문제들은 요청 당 트랜잭션 방식을 사용하는 OSIV에서 발생하는 문제점이다
- 요즘에는 사용하지 않음
- 이러한 문제점을 보안해 비즈니스 계층에서만 트랜잭션을 유지하는 방식의 OSIV를 사용
- 스프링이 제공하는 OSIV
스프링이 제공하는 OSIV
비즈니스 계층 트랜잭션
스프링에서는 다양한 OSIV를 제공
- 하이버네이트 OSIV 서블릿 필터
- 하이버네이트 OSIV 스프링 인터셉터
- JPA OEIV 서블릿 필터
- JPA OEIV 스프링 인터셉터
OSIV를 사용하긴 하지만, 트랜잭션은 비즈니스 계층에서만 사용한다!
동작 원리
- 클라이언트 요청시 서블릿 필터 또는 스프링 인터셉터에서 영속성 컨텍스트를 생성
- 이때, 트랜잭션은 시작하지 않는다 ❌
- 서비스 계층에서 @Transactional 으로 트랜잭션 시작할 때, 미리 생성해둔 영속성 컨텍스트를 찾아와 트랜잭션을 시작
- 서비스 계층이 끝나면 트랜잭션을 커밋하고 영속성 컨텍스트를 flush 한다.
- 트랜잭션은 끝내지만 영속성 컨텍스트는 종료하지 않는다 ❌
- 컨트롤러와 뷰까지 영속성 컨텍스트가 유지되므로 조회한 엔티티는 영속상태를 유지한다.
- 서블릿 필터나 스프링 인터셉터로 요청이 돌아오면 영속성 컨텍스트를 종료한다.
- 플러시를 호출하지 않고 바로 종료한다.
⇒ 영속성 컨텍스트는 프레젠테이션 계층까지 살려두면서, 변경은 불가능하게 했다.
트랜잭션 없이 읽기 (Nontransaction reads)
- 변경이 아닌 단순 조회인 경우는 트랜잭션 없이도 가능
- 이것을 트랜잭션 없이 읽기 라고 한다.
- 프록시를 초기화하는 지연 로딩도 결국 조회기능이므로 트랜잭션 없이 읽기가 가능하다.
주의 사항 👀
프레젠테이션 계층에서 엔티티를 수정한 직후에 트랜잭션을 시작하는 서비스 계층을 호출하는 경우 문제가 발생
@Controller class UserController { public String view(Long id){ User user = userService.getUser(id); user.setName("XXX"); userService.transactionLogic();// 트랜잭션을 시작하는 로직 return "xxxxx" } }
- userService의 getUser를 통해 찾아온 User를 영속성 컨텍스트에 저장
- 찾아온 user의 이름을 XXX로 바꾸었습니다.
- transactionLogic() 메소드를 호출하여 트랜잭션이 있는 비즈니스 로직을 실행하였습니다.
- 트랜잭션 AOP가 동작하면서 영속성 컨텍스트에 트랜잭션을 시작합니다. 그리고 transactionLogic() 메소드를 실행합니다.
- transactionLogic()메소드가 끝나면 트랜잭션 AOP는 트랜잭션을 커밋하고 영속성 컨텍스트를 플러시합니다.
- 이때 변경 감지가 동작하면서 유저 엔티티의 수정 사항을 데이터베이스에 반영합니다.
컨트롤러에서 엔티티를 수정한 후 뷰를 호출한 것이 아니라 트랜잭션이 동작하는 비즈니스 로직을 호출해 문제가 발생
⇒ 스프링 OSIV는 같은 영속성 컨텍스트를 여러 트랜잭션이 공유할 수 있어 문제가 발생
스프링 OSIV 특징
- 클라이언트 요청이 들어올 때 영속성 컨텍스트를 생성해 요청이 끝날 때까지 같은 영속성 컨텍스트를 유지한다.
- 한 번 조회한 엔티티는 요청이 끝날 때 까지 영속 상태를 유지
- 엔티티 수정은 트랜잭션이 있는 계층에서만 동작한다.
- 트랜잭션이 없는 프레젠테이션 계층은 지연로딩을 포함해 조회만 할 수 있다.
스프링 OSIV의 단점
- OSIV를 사용하면 같은 영속성 컨텍스트를 여러 트랜잭션이 공유할 수 있다.
- 트랜잭션 롤백을 조심해야 한다.
- 프리젠테이션 계층에서 엔티티를 수정하고 난 후 비즈니스 로직을 수행하면 엔티티가 수정될 수 있다
- 너무 오랜시간 데이터베이스 커넥션 리소스를 사용한다.
- 실시간 트래픽이 중요한 애플리케이션은 커넥션이 모자를 수 있다 ⇒ 장애 발생
참고 자료
https://loosie.tistory.com/796
https://agileryuhaeul.tistory.com/entry/OSIV란
https://www.baeldung.com/spring-open-session-in-view
'🌱 spring' 카테고리의 다른 글
Spring Scheduler (0) 2022.10.24 Filter, Interceptor (0) 2022.10.11 JPA - N + 1 문제 (0) 2022.10.11 영속성 컨텍스트 (0) 2022.10.11 JPA와 Hibernate, Spring data JPA (0) 2022.10.10