과정을 즐기자

OSIV 설정을 False로 변경하기위한 리팩토링 과정 본문

Spring Data

OSIV 설정을 False로 변경하기위한 리팩토링 과정

320Hwany 2023. 3. 12. 18:18

OSIV란 Open-Session-In-View 의 약자로 영속성 컨텍스트를 뷰까지 열어두는 기능을 말합니다.

스프링부트를 사용하면 기본적으로 OSIV 설정이 true입니다. 

설정을 어떻게 하느냐에 따라서 각 계층에서 영속 상태인지 아닌지가 달라지기 때문에 주의하여 사용해야 합니다. 

이번 글에서는 OSIV 설정을 false로 변경하면서 코드를 어떠한 방식으로 리팩토링 하였는지에 대해서

작성해보려고 합니다.

 

OSIV true 

OSIV 설정이 true이면 영속상태를 뷰까지 열어두기 때문에 코드를 작성하기 편리합니다. 

계층이 달라지더라도 동일성이 보장되기 때문에 같은 엔티티 인스턴스를 반환할 수 있습니다.   

Controller에서도 지연로딩으로 데이터를 가져올 수 있습니다.  

 

이렇게 편리한 것 같지만 치명적인 단점이 있습니다. 

DB 커넥션을 오래 가지고 있기 때문에 트래픽이 많은 애플리케이션에서는 사용할 수 없다는 점입니다.  

 

OSIV false

이를 해결하기 위해 OSIV를 false로 설정해야 합니다. 이때는 Service, Repository에서만 영속 상태입니다.

따라서 컨트롤러에서는 지연로딩을 사용할 수 없습니다. 따라서 인스턴스의 동일성을 유지 하려면

Service, Repository안으로 넣어줘야 합니다.   

 

이렇게 하기 위해서는 기존 코드를 리팩토링 했는데 이번 글에서는 만화를 삭제하는 기능에 대한 리팩토링 과정에

대해서 작성하겠습니다.  

 

Before

Controller

@DeleteMapping("/cartoon/{cartoonId}")
public ResponseEntity<Void> delete(@LoginForAuthor AuthorSession authorSession,
                                   @PathVariable Long cartoonId) {
    cartoonService.validateAuthorityForCartoon(authorSession, cartoonId);
    cartoonService.delete(cartoonId);
    return ResponseEntity.ok().build();
}

Service

public void validateAuthorityForCartoon(AuthorSession authorSession, Long cartoonId) {
    Cartoon cartoon = cartoonRepository.getById(cartoonId);
    cartoon.validateAuthorityForCartoon(authorSession);
}
@Transactional
public void delete(Long cartoonId) {
    Cartoon cartoon = cartoonRepository.getById(cartoonId);
    cartoonRepository.delete(cartoon);
}

Controller에서 Service의 메소드 2개를 실행했습니다.  

로그인한 작가가 해당 만화에 대한 접근 권한이 있는지 확인 - 1

해당 만화를 삭제 - 2

 

이렇게 나누는 것이 더 가독성이 좋다고 생각했었습니다.

하지만 OSIV 설정을 false로 하니 이러한 방식을 더이상 사용할 수 없었습니다. 

 

validateAuthorityForCartoon 메소드에서 getById() delete메소드에서 getById()로 호출할 때

같은 쿼리가 두번 나갑니다. 영속 상태가 아니기때문에 1차 캐시를 사용할 수 없는 것 입니다.

 

After

Controller

@DeleteMapping("/cartoon/{cartoonId}")
public ResponseEntity<Void> delete(@LoginForAuthor AuthorSession authorSession,
                                   @PathVariable Long cartoonId) {
    cartoonService.delete(authorSession, cartoonId);
    return ResponseEntity.ok().build();
}

Service

@Transactional
public void delete(AuthorSession authorSession, Long cartoonId) {
    Cartoon cartoon = cartoonRepository.getById(cartoonId);
    validateAuthorityForCartoon(authorSession, cartoon);
    cartoonRepository.delete(cartoon);
}

Service에서의 두 메소드를 하나의 메소드로 합쳤습니다. 또한 이 방법이 합리적이라고 생각합니다. 

 

전에는 하나의 메소드는 반드시 하나의 일만 해야한다고 생각하여 Service 계층의 모든 메소드를

하나의 일만 담당하게 하였습니다. 이러한 방식으로하면 Controller 계층이 점점 두꺼워집니다. 

또한 Service 계층은 비즈니스 로직을 담당하는 것이 아니라 비즈니스 로직은 Domain이 담당하고

Service는 비즈니스 로직을 한 곳으로 묶어주는 역할을 하는 것이 더 맞다는 생각이 들었습니다. 

 

이렇게 하면 OSIV를 false로 설정할 수 있고 Controller 계층도 간결해집니다. 

 

하지만 이때 한가지 문제점이 있습니다. 바로 Service가 전달받는 파라미터가 많아진다는 점입니다.  

위의 예시는 단순해서 2개밖에 없었지만 조금더 복잡해져 하나의 메소드가 파라미터를 3, 4개 이상 전달받게 되는

경우가 생길 수 있습니다.  

이때는 파라미터를 하나로 추상화 할 수 있는 DTO 객체를 만들어 Service 계층에 전달하면 해결할 수 있습니다.

 

간단하게 요약하자면 다음과 같습니다.

 

1. OSIV를 true로 설정하면 성능 문제가 발생할 수 있으므로 false로 설정하자.

2. 하나의 전체 프로세스를 Service 계층에서 묶어 같은 트랜잭션 안에 있도록하자.

3. Service로 전달하는 파라미터가 많아진다면 DTO 객체를 만들어서 전달하자.

 

참고한 자료 : 자바 ORM 표준 JPA 프로그래밍 (김영한)