ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • OSIV 🧐
    🌱 spring 2022. 10. 11. 02:24

    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라 한다.

    1. 요청이 들어오면 서블릿 필터나 스프링 인터셉터에서 영속성 컨텍스트를 만들면서 트랜잭션을 시작
    2. Controller, Service, Repository 층에서 모두 영속성 컨텍스트가 유지된다.
    3. 조회된 엔티티가 뷰까지 영속상태를 유지해 지연로딩이 가능하다
      • 뷰에서도 지연로딩이 가능해 FACADE 계층 없이도 뷰에 독립적인 서비스 계층을 유지할 수 있다.
    4. 요청이 끝날 때 트랜잭션과 영속성 컨텍스트를 함께 종료한다.

    문제점

    컨트롤러나 뷰 같은 프레젠테이션 계층이 엔티티를 변경할 수 있다.

    • 뷰를 렌더링 한 이후 트랜잭션을 커밋한다
    • 트랜잭션 커밋시 영속성 컨텍스트를 flush하고 dirty checking 기능을 통해 변경된 엔티티 값을 DB에 반영한다
    • 데이터가 변경되는 문제가 발생한다.
    • ex) 고객 정보를 보여주는 과정에서 보안상 어떤 내용을 변경해서 보여줌
      • 변경감지에 의해 아예 내용이 바뀌어서 DB에 저장된다.

    해결방법

    프레젠테이션 계층에서 엔티티를 수정하지 못하게 막으면 된다.

    1. 엔티티를 읽기 전용 인터페이스로 제공
    2. 엔티티를 읽기 전용 메서드만 가진 Wrapper 클래스로 매핑
    3. DTO만 반환

    이런 문제들은 요청 당 트랜잭션 방식을 사용하는 OSIV에서 발생하는 문제점이다

    • 요즘에는 사용하지 않음
    • 이러한 문제점을 보안해 비즈니스 계층에서만 트랜잭션을 유지하는 방식의 OSIV를 사용
      • 스프링이 제공하는 OSIV

    스프링이 제공하는 OSIV


    비즈니스 계층 트랜잭션

     

    스프링에서는 다양한 OSIV를 제공

    • 하이버네이트 OSIV 서블릿 필터
    • 하이버네이트 OSIV 스프링 인터셉터
    • JPA OEIV 서블릿 필터
    • JPA OEIV 스프링 인터셉터

    OSIV를 사용하긴 하지만, 트랜잭션은 비즈니스 계층에서만 사용한다!

    동작 원리

    1. 클라이언트 요청시 서블릿 필터 또는 스프링 인터셉터에서 영속성 컨텍스트를 생성
      • 이때, 트랜잭션은 시작하지 않는다
    2. 서비스 계층에서 @Transactional 으로 트랜잭션 시작할 때, 미리 생성해둔 영속성 컨텍스트를 찾아와 트랜잭션을 시작
    3. 서비스 계층이 끝나면 트랜잭션을 커밋하고 영속성 컨텍스트를 flush 한다.
      • 트랜잭션은 끝내지만 영속성 컨텍스트는 종료하지 않는다
    4. 컨트롤러와 뷰까지 영속성 컨텍스트가 유지되므로 조회한 엔티티는 영속상태를 유지한다.
    5. 서블릿 필터나 스프링 인터셉터로 요청이 돌아오면 영속성 컨텍스트를 종료한다.
      • 플러시를 호출하지 않고 바로 종료한다.

    ⇒ 영속성 컨텍스트는 프레젠테이션 계층까지 살려두면서, 변경은 불가능하게 했다.

    트랜잭션 없이 읽기 (Nontransaction reads)

    • 변경이 아닌 단순 조회인 경우는 트랜잭션 없이도 가능
    • 이것을 트랜잭션 없이 읽기 라고 한다.
    • 프록시를 초기화하는 지연 로딩도 결국 조회기능이므로 트랜잭션 없이 읽기가 가능하다.

    주의 사항 👀

    프레젠테이션 계층에서 엔티티를 수정한 직후에 트랜잭션을 시작하는 서비스 계층을 호출하는 경우 문제가 발생

    @Controller
    class UserController {
        public String view(Long id){
        
          User user = userService.getUser(id);
          user.setName("XXX");
            
          userService.transactionLogic();// 트랜잭션을 시작하는 로직
          return "xxxxx"
        }
    }
    1. userService의 getUser를 통해 찾아온 User를 영속성 컨텍스트에 저장
    2. 찾아온 user의 이름을 XXX로 바꾸었습니다.
    3. transactionLogic() 메소드를 호출하여 트랜잭션이 있는 비즈니스 로직을 실행하였습니다.
    4. 트랜잭션 AOP가 동작하면서 영속성 컨텍스트에 트랜잭션을 시작합니다. 그리고 transactionLogic() 메소드를 실행합니다.
    5. transactionLogic()메소드가 끝나면 트랜잭션 AOP는 트랜잭션을 커밋하고 영속성 컨텍스트를 플러시합니다.
      • 이때 변경 감지가 동작하면서 유저 엔티티의 수정 사항을 데이터베이스에 반영합니다.

    컨트롤러에서 엔티티를 수정한 후 뷰를 호출한 것이 아니라 트랜잭션이 동작하는 비즈니스 로직을 호출해 문제가 발생

    스프링 OSIV는 같은 영속성 컨텍스트를 여러 트랜잭션이 공유할 수 있어 문제가 발생

    스프링 OSIV 특징

    1. 클라이언트 요청이 들어올 때 영속성 컨텍스트를 생성해 요청이 끝날 때까지 같은 영속성 컨텍스트를 유지한다.
      • 한 번 조회한 엔티티는 요청이 끝날 때 까지 영속 상태를 유지
    2. 엔티티 수정은 트랜잭션이 있는 계층에서만 동작한다.
      • 트랜잭션이 없는 프레젠테이션 계층은 지연로딩을 포함해 조회만 할 수 있다.

    스프링 OSIV의 단점

    • OSIV를 사용하면 같은 영속성 컨텍스트를 여러 트랜잭션이 공유할 수 있다.
      • 트랜잭션 롤백을 조심해야 한다.
    • 프리젠테이션 계층에서 엔티티를 수정하고 난 후 비즈니스 로직을 수행하면 엔티티가 수정될 수 있다
    • 너무 오랜시간 데이터베이스 커넥션 리소스를 사용한다.
      • 실시간 트래픽이 중요한 애플리케이션은 커넥션이 모자를 수 있다 ⇒ 장애 발생

    참고 자료

    https://loosie.tistory.com/796

    https://agileryuhaeul.tistory.com/entry/OSIV란

    https://www.baeldung.com/spring-open-session-in-view

    https://ttl-blog.tistory.com/183

    https://aeliketodo.tistory.com/91

    '🌱 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

    댓글

Designed by Tistory.