프로젝트/Recipository

[Dev] 23.02.17. 중복된 조회의 원인과 그 해결 (feat. OSIV)

규글 2023. 2. 17. 05:01

 현재 일부 게시글 data를 조회하는 query 가 동작할 때, 두 번씩 동작하는 것을 테스트 과정에서 몇 번 보게 되었다. 이에 대한 원인을 분석하고, 수정하는 방안을 모색해보려고 한다. 게시글을 모두 작성하고 난 뒤 가장 앞에 작성하고 있지만, 결과적으로 필자는 OSIV Filter 를 활용하지 않기로 했다.

 

 

원인 분석

 사실 필자는 '영속성' 에 대해서 아직도 명확하게 알고 있지 않다. 그나마 알고 있는 점은 영속성이라는 것이 하나의 Transaction(트랜잭션) 안에서만 유지되는 것이 일반적이라는 것이다. 그래서 추정한 것이 실제로 어딘가에서 해당 조회를 하고 있다는 생각인데, 그 주인공이 바로 Interceptor였다.

 필자는 이전에 게시글을 수정하거나 삭제하는 요청에 대해서 로그인 한 사용자가 작성자와 동일한지 확인하기 위한 interceptor를 만들어서 적용했다. 그 과정에서 게시글이나 댓글에 대한 작성자를 조회하도록 했는데, 이때문에 게시글을 수정하는 페이지로의 이동이나 게시글을 수정하거나 삭제하는 요청, 댓글을 삭제하는 요청에 대해서 관련 게시글이나 댓글 data를 조회하는 요청이 중복되고 있던 것이다.

 

 현상을 해결하기 위해서 검색하던 중 알게 된 키워드는 'OSIV' 이다. 이 OSIV 라는 것이 무엇인지 알아보고, 현재 상태를 어떻게 해결할 것인지 고민해보려고 한다.

 

Open Session In View (OSIV)

 꽤나 여러 블로그들을 체크했지만, 같은 이미지를 사용해서 같은 내용을 기록한 곳들이 많았다. '자바 ORM 표준 JPA 프로그래밍 (김영한 저)' 를 참고했다고 언급하고 있다. [각주:1] [각주:2] 해당 블로그에 작성된 OSIV에 대해 간단히 정리해보겠다.

 

 Open Session In View (OSIV) 는 글자 그대로 'View에 Session을 여는 것', 'Persistence Context (영속성 컨텍스트)view 까지 열어둔다'는 의미이다. Jpa hibernate 에서는 OEIV (Open EntityManager In View) 라고 하지만 관례상 OSIV 라고 부른다고 한다. 왜 session이라 하는지 궁금했는데, 한 블로그에서 친절히 설명하고 있었다.[각주:3]

 

 Jpa에서 활용하고 있는 'Hibernate' 라는 친구는 도메인 객체들이 data repository와 영속성 메커니즘에 대해 알지 않아도 되게끔 하는 Transparent Persistence (투명한 영속성)을 제공하는 Noninstrusive (비침투적인)한 framework이다. 그래서 영속성과 관련된 모든 관심사는 도메인 객체로부터 분리되어 '관리자 객체'에 의해 투명하게 처리되는데, 이때 관리자 객체라고 부르는 것이 Hibernate의 Session 객체이며, 이 Session 객체에 영속성 컨텍스트가 포함되는 것이다.

 이 영속성 컨텍스트를 이해하기 위해서는 Transaction을 먼저 이해해야 한다. Transaction은 가장 작은 업무 처리의 단위라고 할 수 있으며, 영속성 컨텍스트와는 1:1 관계가 있다. 하나의 transaction 내에서 수정된 Entity의 모든 상태는 영속성 컨텍스트에 저장되어 transaction이 종료될 때 flush 하여 data repository에 동기화하도록 한다. 따라서 이 hibernate session을 하나의 transaction 내에 생성, 조회, 수정, 삭제되는 Entity의 상태를 보관하는 일종의 cache로 간주해도 되며, 실제로 이를 'First Level Cache (1차 캐시)' 라고 부르기도 한다.

 

이미지 출처 : 자바 ORM 표준 JPA 프로그래밍 (김영한 저)

 다시 OSIV로 돌아가보자. OSIV의 핵심은 View 에서도 Entity에 대한 lazy loading이 가능하도록 하는 것이라 한다. 그래서 과거에는 Transaction per Request (요청 당 트랜잭션) 방식의 OSIV를 구현했다. 이는 client로부터 request를 받았을 때 Filter Interceptor부터 transaction을 시작하고, 그것을 response 까지 유지하도록 하는 것이다. 그러면 View 까지 transaction의 범위가 되고, 그 범위와 동일하게 영속성 컨텍스트를 유지할 수 있게 된다. 이때 발생할 수 있는 문제는 ControllerView와 같은 Presentation layer에서 Entity를 변경할 수 있게 된다는 것이다. 이에 대한 해결책으로 다음의 3가지를 언급하고 있다. 

 

  • 읽기 전용 method만을 가진 interface를 만들어 제공
  • Entity를 한 번 wrapping 하여 entity에 대한 읽기 전용 method만을 가지게 하여 제공
  • Dto를 만들어서 제공

 

 세 가지 방식 모두 코드량이 증가하는 단점이 있고, 특히 DTO의 경우는 OSIV를 활용하는 의미가 사라진다고 한다. 이런 문제점을 보완하여 Business layer 에서만 transaction을 유지하는 방식의 OSIV를 사용하는데, 이를 spring framework가 제공해주고 있다. Spring framework는 다양한 OSIV class를 제공한다.

  • hibernate servlet filter : OpenSessionInViewFilter
  • hibernate spring interceptor : OpenSessionInViewInterceptor
  • jpa oeiv servlet filter : OpenEntityManagerInViewFilter
  • jpa oeiv spring interceptor : OpenEntityManagerInViewInterceptor

 

이미지 출처 : 자바 ORM 표준 JPA 프로그래밍 (김영한 저)

 Client로부터의 request에 대해 FilterInterceptor에서 영속성 컨텍스트를 생성하는 것은 동일하지만, transaction을 시작하지 않는다. Transaction은 service layer에서 @Transactional 시작하고, 미리 생성한 영속성 컨텍스트를 가져와서 service logic 수행하게 된다.

 Transaction 단위가 끝나면 commit 하고 영속성 컨텍스트flush하는데, 이 때 transaction은 종료된 것이나 영속성 컨텍스트는 그대로 Presentation layer까지 유지된다. 영속성 컨텍스트가 유지되므로 ControllerView 까지 조회한 Entity의 영속 상태 유지 가능해지며, Servlet FilterSpring Interceptorrequest가 다시 돌아오면 flush 없이 영속성 컨텍스트를 종료하는 방식이라고 한다. 따라서 Presentation layer에서는 Entity를 수정할 수 없다. 과거의 OSIV는 Controller에서도 수정이 가능했으나, Spring framework가 제공하는 것은 이를 보완했다고 할 수 있겠다.

 

javax.persistence.TransactionRequiredException: no transaction is in progress

 Entity 변경 없이 단순 조회까지는 가능(Lazy loading도 가능)하지만, 영속성 컨텍스트를 통한 모든 변경은 transaction 내에서만 가능하므로 transaction 단위 밖에서 flush 한 경우에는 TransactionRequiredException이 발생한다고 한다.

 

 하지만 여전히 존재하는 문제점이라고 한다면 Service layer로 넘어가기 전에 Presentation layer에서 먼저 Entity를 수정하게 되면, 의도한 수정 사항이 아닌데 반영되는 문제가 생길 수 있다는 점이다. 하지만 단순히 service logic 이후 변경하도록 한다면 이미 flush 된 상황이기 때문에 변경 사항이 update되지 않도록 할 수 있다.

 이외에도 같은 영속성 컨텍스트를 여러 transaction에서 공유할 수 있으니 이 부분에 주의해야 하며, 만약 성능 최적화를 위해 작업한다면 Presentation layer에서 lazy loading에 의한 select query가 수행될 수 있으니 확인해야할 부분이 늘어날 수 있다는 것이다.

 

 OSIV와 FACADE pattern, DTO를 비교하는 항목도 있었는데, OSIV 대신 어느 방법을 사용하더라도 준영속 상태가 되기 전에 proxy를 초기화해야하며 비교적 코드량이 증가한다는 점을 지적하고 있다. OSIV 가 가진 장점을 언급하는 부분인 것 같다. 하지만 복잡한 data로 구성되는 화면의 경우 JPQL을 활용해서 DTOreturn 하도록 하는 것이 효과적이라고 말한다.

 

 잠깐 그런데 proxy를 초기화해야한다는 것은 어떤 말이지? 역시 필자가 제대로 강의를 모두 듣지 않았기 때문에 발생하는 문제이다. 그래서 간단히 어떤 의미인지 알아보기 위해 한 블로그를 찾게 되었다.[각주:4]

 

 하나의 Entity를 조회할 때 그와 연관된 Entity를 바로 조회하는 것이 아니라 필요할 때만 조회하기 위해서 lazy loading 방식을 사용하는데, 이때 Proxy 객체를 통해서 구현하고 있다고 한다. Proxy는 사전적으로 '대리(인), 대용' 을 의미하는데, 특정 객체의 존재를 대신하는 '가짜 객체'의 개념이라고 한다.

 Hibernate에서는 특정 Entity와 연관 관계를 형성하고 있는 field의 자리에 실제 객체가 아닌 proxy 객체의 참조값을 넣어서 null이 되지 않도록 한다. 이때 proxy 객체가 실제 Entity처럼 동작할 수 있는 이유는 proxy 객체가 실제 Entity를 extends 했기 때문이며, proxy 객체는 실제 Entity 의 참조값을 가지고 있기 때문이다. 하지만 최초 lazy loading 시점에는 그 참조값이 없고, 실제 Entity에 대한 조회가 이루어질 때 select query 로 실제 Entity data를 조회하여 그 객체의 참조값을 저장하는 방식이라고 한다. 이때 실제 Entity를 조회하는 것을 'Proxy Initialization (프록시 초기화)' 라고 말한다.

 

 위 이미지는 방금 내용을 기반으로 필자가 그려본 것이다. 양방향 연관 관계를 구성하고 있는 A와 B가 있을 때, 최초 A에 대한 조회 시 A의 B에 대한 field에는 B를 extends한 B Proxy의 참조값이 있는 것이고, B Proxy의 B에 대한 참조값은 비어있는 상태일 것이다. 그리고 A에서 B field에 대한 조회가 이루어질 때, select query로 DB에서 B에 대한 조회가 이루어지면 B의 참조값이 B Proxy에 할당된다고 생각하면 되며, 이 과정을 초기화라고 하는 것 같다.

 

 따라서 OSIV 대신 FACADE나 DTO 어느 방식을 활용하더라도 준영속 상태가 되기 전에 proxy를 초기화 해야한다는 것은 실제 data에 대한 조회가 이루어져야 함을 의미한다고 할 수 있겠다.

 

 외에도 JVM(Java Virtual Machine)을 벗어난 원격 상황에서는 사용 불가능하다고 언급하는데 이는 어찌보면 당연한 부분이다. Server에서 JSON data를 생성하는 것은 response를 생성하는 과정에서 진행되는 것이니 lazy loading 이 가능하지만, 이미 response를 마친 client에서 lazy loading을 하는 것은 불가능하다. 따라서 한 번에 모두 return 해주어야 하는 것이며, 이에 따라 방금 언급한 JPQL을 활용한 return이 효과적이라고 말한 부분과도 어느 정도 맥이 통한다고 생각한다.

 

 API 측면에서 봤을 때도 Entity 는 자주 변경될 수 있으므로 JSON의 대상 객체로 사용하게 되면 그 api 도 변경될 수 있는데, 따라서 타 팀이나 타 회사와의 업무와 관련되는 등의 외부에 노출하는 API의 경우에는 한 번 약속했을 때 변동하기 어렵다고 말하면서 DTO로 변환하여 return 하는 것이 좋다고 언급한다. 반면 프로젝트와 직접적으로 관련된 내부 API 는 Entity를 변경한다고 하더라도 수정이 상대적으로 쉽기 때문에 실용적인 관점에서 Entity를 직접 사용하기도 한다고 언급한다.

 

 

다시 현재 상황

 OSIV의 존재를 알고 활용해보고자 한 것은 Interceptor에서 현재 로그인한 사용자와 접근하고자 하는 게시글의 작성자를 비교하기 위해 조회한 게시글 정보에 대한 영속성 컨텍스트를 service layer에서도 유지하기 위함이었다. 그런데 놀랍게도 OSIV에 대해 알아보는 과정에서 이미 필자가 사용하는 중이라는 것을 알게 되었다. [각주:5] [각주:6] (후자에서 전자를 참조하고 있다.)

 

 위 이미지는 DispatcherServlet class 내에 있는 handler 에 대해 break point를 걸고 확인한 debugging 항목이다. 'hander' 에는 interceptorList 라는 변수를 확인할 수 있는데, 여기에서 OpenEntityManagerInViewInterceptor (OEIV)를 확인할 수 있었다. 이미 기본 setting으로 활용하고 있었던 것이다.

 

spring.jpa.open-in-view: false

 application.properties 에 위 항목이 default로 true인 상태인 것인데, 이를 false로 바꿔주면 따로 동작하지 않는다.

 

 위 이미지에서 볼 수 있는 interceptorList의 index 순서가 곧 적용되는 순서라고 할 수는 없지만, 실제로 동작하는 순서는 필자가 게시글 수정과 삭제, 댓글 삭제에 대한 요청 시 작성자 비교를 위해 만들었던 AuthInterceptor 가 먼저이고 이후에 OpenEntityManagerInViewInterceptor 가 나중이다.

 

 이런 이유로 AuthInterceptor에서 조회한 data에 대해서는 영속성이 유지되지 않아서 Interceptor를 통과할 때 한 번, 실제로 service logic이 동작할 때 또 다시 한 번 게시글에 대한 data를 조회하게 되는 것이라고 말할 수 있다.

 

 이와 관련된 사항으로 현재는 필요성이 없어서 AuthInterceptor의 method에서 @Transactional annotation을 없애주었는데, 과거에 lazy loading으로 인해서 AuthInterceptor를 통과하면서 LazyInitializationException 이 발생했던 이유도 OpenEntityManagerInViewInterceptor 보다 이전에 수행되는 Interceptor 이기 때문이라고 추론할 수 있다.

 

 

OSIV에 대한 필요성

 OSIV를 사용하는 것에 대해서는 꽤나 부정적인 시선이 많은 것 같다. 그 중에 하나의 관점을 언급한 블로그가 있었다.[각주:7]

 

 해당 블로그에서는 Connection Pool 의 관점에서 OSIV를 사용하는 것에 부정적인 의견을 제시하고 있다. Connection Pool이란 DB와 요청을 주고받을 수 있는 일종의 창구라고 생각하면 좋을 것 같다. 은행에서도 창구가 제한되어 있어서, 한 사람이 오랜 시간이 걸리는 업무를 보는 경우 순서가 돌아가는 것이 더딘 경험을 한 적이 있을 것이다. OSIV는 요청이 마무리될 때까지 이 Connection Pool을 유지하게 되는데, 이런 측면에서 본다면 DB의 data를 조회한 뒤 시간이 오래 걸리는 logic, 혹은 시간이 오래 걸리는 외부의 무언가를 활용했을 때 해당 service method에 대한 요청이 계속 들어온다면 모든 Connection Pool을 소진한 상태가 될 수도 있다.

 간단한 service라면 생산성 측면에서 사용해도 무리 없을 것이라고 말하지만, 복잡한 service이거나 외부 호출이 잦거나 하는 등의 경우에는 OSIV를 비활성화 할 것을 제시하고 있다.

 

 그렇다면 필자는 어떻게 해야할까? 현재 필자는 transaction 밖에서 Entity를 활용하고 있지 않다. 따라서 필자에게 OSIV를 꼭 사용해야할 필요성이 존재하는 것은 아니다. 아주 간단한 service일 뿐이고, 외부의 무언가를 활용하고 있지도 않는다. 따라서 query가 하나 더 수행된다고 해서 크게 문제가 될 일은 아니지만, 중복되는 query가 동작하는 것을 보고 싶지 않다면 OpenEntityManagerInViewFilter 를 활용하면 될 것이라고 생각한다.

 

 

작업

MvcConfiguration.java

    @Bean
    public FilterRegistrationBean<OpenEntityManagerInViewFilter> registerOpenEntityManagerInViewFilterBean() {
        FilterRegistrationBean<OpenEntityManagerInViewFilter> registrationBean = new FilterRegistrationBean<>();
        OpenEntityManagerInViewFilter oeivFilter = new OpenEntityManagerInViewFilter();
        registrationBean.setFilter(oeivFilter);
        registrationBean.addUrlPatterns("/user/contents/*", "/user/comments/*");
//        registrationBean.setUrlPatterns(Arrays.asList("/user/contents/*", "/user/comments/*"));

        return registrationBean;
    }

 기존에는 AuthInterceptor 를 추가하기 위한 method만 존재하고 있던 MvcConfiguration에 @Bean 으로 OpenEntityManagerInViewFilter Bean을 등록해주었다. 여기에서 FilterRegistrationBean의 addUrlPatterns method나 setUrlPatterns method 모두 여러 url pattern을 설정할 수 있으나, Collection type을 전달하는가 아닌가의 차이가 있다.

 

 필자는 이 부분에서 문제가 반복적으로 발생했는데, 그것은 wild card 인 * 때문이었다. 어떤 url pattern을 작성하면서 /** 로 wild card *을 두 번 사용하면 /** 해당 경로 이하의 모든 항목을 표기한다고 알고 있었기 때문에 그렇게 작성해주었다. 하지만 이곳에서는 그렇게 작성하면 안되고 wild card 하나만을 작성해도 되며, 해당 경로 이하의 어떤 내용이든 상관없는 것을 확인했다. 안타깝지만 이유는 모르겠다.

 

AuthInterceptor.java

        // 게시글인 경우의 key와 댓글인 경우의 key를 분기
        if(pathVariables.containsKey("contentId")){
            id = Long.parseLong((String)pathVariables.get("contentId"));

            // DB의 data와 비교해서 아닌 경우 banned page로 이동하게 하고 false를 return
            // 같은 경우는 true를 return하고 정상적으로 동작이 수행되도록 함
            Recipe recipe = recipeRepository.getRecipeByContentId(id);
            beMatched = spUser.getUsername().equals(recipe.getMember().getEmail());

            // Controller 에서 활용할 수 있도록 HttpServletRequest 객체에 담아 보냄
            request.setAttribute("recipe", recipe);

        } else if(pathVariables.containsKey("commentId")){
            id = Long.parseLong((String)pathVariables.get("commentId"));

            Comment comment = commentRepository.getCommentByCommentId(id);
            beMatched = spUser.getUsername().equals(comment.getMember().getEmail());

            // Controller 에서 활용할 수 있도록 HttpServletRequest 객체에 담아 보냄
            request.setAttribute("comment", comment);
        }

 OpenEntityManagerInViewFilter를 설정하게 되면 AuthInterceptor 에서부터 Entity의 영속성이 유지될 수 있으므로 조회한 

Entity를 HttpServletRequest 객체에 담아 Controller로 넘겨주는 것으로 Controller와 이후 Service까지 전달할 수 있게 된다.

 

PageController.java

    @GetMapping("/user/contents/update-form/{contentId}")
    @PreAuthorize("hasAuthority('ROLE_USER')")
    public ModelAndView updateForm(@PathVariable Long contentId, HttpServletRequest request){

        ModelAndView mView = new ModelAndView();

        Recipe recipe = (Recipe)request.getAttribute("recipe");

        mView.addObject("recipe", recipeService.getRecipeOnly(contentId, recipe));
        mView.setViewName("pages/content_updateform");

        return mView;
    }

 Controller 에서는 Interceptor 에서 넘긴 Recipe Entity를 받아 활용할 수 있는데, 필자는 단순히 Service 로 넘겨주는 것 이외에는 다른 작업을 하지 않았다.

 

RecipeServiceImpl.java

    @Transactional
    @Override
    public RecipeDto getRecipeOnly(Long contentId, Recipe recipe) {
        // repository로부터 게시글 data를 가져옴
//        Recipe recipe = recipeRepository.getRecipeByContentId(contentId);
        RecipeDto recipeDto = recipe.toDtoWithAll();

        return recipeDto;
    }

 그렇게 service 로 넘어가서 동작하는 것은 해당 Entity를 Dto로 만들어서 response 하는 것이다. 기존에 이 service method 에서 Recipe Entity를 조회하는 부분이 필요 없어졌고, 전달받는 Long type 객체도 사실 필요가 없어진 셈이다. 조금 더 생각해보면 사실 이 service method 자체가 필요 없어진 셈이라고도 할 수 있는데, Controller 에서 Recipe Entity를 받은 그때 Dto로 변환하여 client 로 response 하도록 하는 것이 OSIV를 활용한다고 할 수 있는 것이지 않을까 생각한다.

 

 

 게시글을 작성하고 OSIV를 활용해보고자 위 항목처럼 작성했으나, 사실 필자는 Controller 에 Entity가 있는 것이 마음에 들지 않는다. 그렇다고 필자가 Controller에 Entity를 아예 사용하지 않는가 하면 또 그것도 아니긴 하다. 필자는 사용자 정보에 대한 요청 controller에서 UserDetails 객체를 Member Entity로 변환하여 사용하고 있다. 하지만 이렇게 작업해보고 다시 확인해보니 Controller에서 Entity를 활용하고 있는 것이 눈으로 보는 것이 뭔가 찝찝하고 답답한 마음이다. 따라서 필자는 우선 위 항목들에 대한 작업을 모두 없던 것으로 돌렸다.

 두 번이나 같은 query가 동작하는 것이 이상했던 것이지만, 단순하게 생각하면 Interceptor에서 구분하는 작성자 정보만을 조회하도록 하면, Interceptor에서는 작성자 정보만을 조회하는 것이고 게시글은 Service layer에서 조회하는 흐름으로 전혀 이상하지 않다.

 

AuthInterceptor.java

        // 게시글인 경우의 key와 댓글인 경우의 key를 분기
        if(pathVariables.containsKey("contentId")){
            id = Long.parseLong((String)pathVariables.get("contentId"));

            // DB의 data와 비교해서 아닌 경우 banned page로 이동하게 하고 false를 return
            // 같은 경우는 true를 return하고 정상적으로 동작이 수행되도록 함
            String writer = recipeRepository.getWriterForRecipe(id);
            beMatched = spUser.getName().equals(writer);

        } else if(pathVariables.containsKey("commentId")){
            id = Long.parseLong((String)pathVariables.get("commentId"));

            String writer = commentRepository.getWriterForComment(id);
            beMatched = spUser.getName().equals(writer);
        }

 게시글을 수정하기 위한 페이지로의 이동, 게시글을 수정하고 삭제하는 요청, 댓글을 삭제하는 요청에 대해 해당 게시글이나 댓글의 작성자와 사용자의 닉네임을 비교하기 위한 AuthInterceptor 에서 기존에는 게시글인 Recipe와 댓글인 Comment 전체를 조회했다. 하지만 단순히 이름만을 비교하려고 한다면 조회하는 것이 객체일 필요가 없이 바로 닉네임 값을 조회하도록 하면 되고, 동작하는 query의 수는 동일하나 같은 query가 동작하지는 않게 된다.

 

RecipeRepository.java / CommentRepository.java

    @Query(value = "select name from recipe r " +
            "join member m on r.user_id = m.user_id " +
            "where r.content_id = :id", nativeQuery = true)
    String getWriterForRecipe(@Param("id") Long contentId);
 
(RecipeRepository) 
------------------------------------------------------------
(CommentRepository)

    @Query(value = "select name from comment c " +
            "join member m on c.user_id = m.user_id " +
            "where c.comment_id = :id", nativeQuery = true)
    String getWriterForComment(@Param("id") Long commentId);

 그리고 닉네임을 그대로 조회하는 query에 대한 Repository method를 작성해서 적용해주었다.

 

 

 이렇게 AuthInterceptor에서의 조회와 Service logic에서의 조회로 동일한 query가 두 번 반복되는 현상에 대해 분석하고, 이를 해결하기 위해 OSIV 라는 친구에 대해 알아보았다. 결과적으로는 알아본 OSIV Filter를 활용하지 않고 동작하는 query만 변경한 셈이지만, OSIV 라는 친구가 어떤 것인지 새롭게 알았으니 그것에 의의를 둔다.

 

 

참고

spring.jpa.properties.hibernate.enable_lazy_load_no_trans = true

 application.properties 에 위와 같이 설정하는 것에 대해 해결책이나 안티 패턴이라고 말하는 글을 보았다.[각주:8] 이 설정은 OSIV와 비슷하기는 하나, 영속성 컨텍스트가 종료되어도 새로운 Database Connection을 획득하여 lazy loading 을 가능하게 해준다고 한다.[각주:9] 그런데 그런 lazy loading이 있을 때마다 각각 새로운 Connection을 획득한다고 말하며 성능상 좋지 않다고 한다. OSIV도 장단점을 살펴서 신중하게 사용해야 하는 것인데, 위 설정은 성능까지 좋지 않아서 더 권장하지 않는다고 말하고 있다.

 

Reference