[SpringBoot] OSIV 영속성 컨텍스트 유지 범위 테스트와 RESTAPI에서의 사용

이혜성·2024년 9월 27일

SpringBoot

목록 보기
3/9

항상 스프링을 실행시키면 다음과 같은 로그를 확인할 수 있다.

WARN 13164 --- [book] [  restartedMain] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning

open session in view

  • 영속성 컨텍스트가 트랜잭션 범위 밖인 컨트롤러, 뷰에서도 유지되도록 하는 기능이며 지연로딩을 위해 사용하는 의미가 크다.
  • 스프링에서 기본 설정은 true이다.
  • 지연로딩 : 엔티티에서 연관관계로 맺어진 객체를 바로 가져오는 것이 아닌 필요한 순간에 가져오는 기능으로 영속성 컨텍스트 안에서만 가능하다.
spring.jpa.open-in-view=true #기본값

true로 명시 해 적어주지 않아도 기본 설정은 true이며 영속성 컨텍스트가 컨트롤러와 뷰 계층에서도 유효하다.

spring.jpa.open-in-view=false

프로퍼티에서 다음과 같이 설정한다면 영속성 컨텍스트는 트랜잭션 범위 내에서만 유효하다.


유지의 여부 테스트

public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String author;
    private String publisher;
    private LocalDate published_date;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "store_id")
    @JsonBackReference
    private Store store;
    }
public class Store {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
   }

책과 상점의 엔티티를 만들고, 책에서 상점으로의 단방향 매핑을 맺는다.
책과 연관된 상점을 가져올 땐 지연로딩을 사용한다.

public Book insertBook(BookDto bookDto){
        Store store = storeRepository.save(Store.builder().name(""+Math.random()).build());
        Book book = Book.builder().title(bookDto.getTitle()).author(bookDto.getAuthor()).publisher(bookDto.getPublisher()).published_date(bookDto.getPublished_date())
                .store(store).build();
        return bookRepository.save(book);
    }

테스트를 위해 책을 등록 시 랜덤 숫자를 이름으로 가지는 상점의 엔티티를 생성해 만들고 책과 연관관계를 맺는다.


트랜잭션 범위 내(영속성 컨텍스트 생존 범위 내)에서의 연관객체 호출 테스트

@Transactional(readOnly = true)
    public Book getBookById(Long id){
        Book book = bookRepository.findById(id).orElse(null);
        log.info("이 책이 속한 상점 : {}",book.getStore().getName());
        return book;
    }

서비스에서 책의 상점의 이름을 호출 해 로그로 확인한다.

spring.jpa.open-in-view=true

spring.jpa.open-in-view=false

트랜잭션 범위 내에선 지연로딩이 잘 이루어지는 것을 확인할 수 있다.


컨트롤러에서 연관 객체 호출 테스트

@GetMapping("/{id}")
    public ResponseEntity<Book> searchBook(@PathVariable Long id){
        Book book = bookService.getBookById(id);
        log.info("서점의 번호 : " + book.getStore().getName());
        return ResponseEntity.ok(book);
    }

컨트롤러에서 책의 상점의 이름을 호출 해 로그로 확인한다.

spring.jpa.open-in-view=true

spring.jpa.open-in-view=false

osiv를 끄면 컨트롤러에서 영속성 컨텍스트가 끊겨 연관 객체를 불러올 수 없는 것을 확인할 수 있다.


뷰에서 연관 객체 호출 테스트

@Controller
@RequiredArgsConstructor
public class BookWebController {
    private final BookService bookService;

    @RequestMapping("/test")
    public String test(Model model){
        Book book = bookService.getBookById(1L);
        model.addAttribute("book",book);
        return "test";
    }
}

미리 책을 생성하여 책을 찾아 뷰로 반환한다

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>test</title>
</head>
<body>
<h1 th:text="${book.getStore().getName()}"></h1>
</body>
</html>

타임리프 사용해 책 객체에 있는 상점의 이름을 가져온다.

spring.jpa.open-in-view=true

spring.jpa.open-in-view=false

osiv를 끌 시 뷰에서도 연관객체를 가져올 수 없다.


정리

osiv를 킨다면 뷰까지 영속성 컨텍스트가 유지된다.
어디서든지 원하는 연관된 객체를 불러올 수 있다는 편리한 점이 있지만,
그만큼 데이터베이스 커넥션을 잡아먹고 있다는 뜻이다.

OSIV를 끈 상태에서 뷰, 컨트롤러에서 연관 객체를 호출하고 싶다면?

1. 즉시로딩을 사용한다면 기존 객체를 불러오는 시점에서 연관 객체를 불러오기 때문에 뷰나 컨트롤러 계층에서 연관 객체를 호출할 수 있다.
2. 트랜잭션 범위에서 연관 객체의 호출하고 싶은 속성을 호출한다. 한 번 호출하면 그 정보는 메모리에 저장되기 때문에 컨트롤러나 뷰 계층에서도 그 데이터를 사용 가능하다.

RESTAPI에서는?

하지만 타임리프를 통해 뷰를 제공하는 것이 아닌,
RESTAPI를 개발에서는 컨트롤러에서 서비스에서 반환된 dto를 바로 응답하기에 컨트롤러까지 영속성 컨텍스트가 유지되어 db 커넥션을 먹어도 찰나의 순간 차이이기에osiv가 on, off 든 차이가 미세하거나 거의 없다는 생각이 들었다.

결론

RESTAPI에서 oisv의 on,off 상태가 성능의 미세한 차이를 가진다 해도, 혹시 모를 실수로 인한 컨트롤러에서의 엔티티 접근 방지와 조금의 성능 개선을 위해 끄는 것이 좋다고 생각한다.

profile
반갑습니다

0개의 댓글