Spring OSIV

이광훈·2024년 5월 26일
/// 테스트 코드
@Test
public void doVoteTest(){
    Restaurant restaurant1 = Restaurant.builder().name("abc").restaurantHash("13").build();
    Restaurant restaurant2 = Restaurant.builder().name("def").restaurantHash("12").build();

    restaurantRepository.save(restaurant1);
    restaurantRepository.save(restaurant2);

	..... (중략)

    List<String> options = new ArrayList<>();
    options.add("13");

    CreateVoteResultRequest createVoteResultRequest = new CreateVoteResultRequest();
    createVoteResultRequest.setUserId(voter1.getId());
    createVoteResultRequest.setNickname("123");
    createVoteResultRequest.setOptions(options);
		/// 테스트 하는 부분
    voteService.createVoteResult(voteOption1.getVote().getVoteHash() , createVoteResultRequest);
}

/// 서비스 레이어 코드
public void createVoteResult(String voteHash, CreateVoteResultRequest createVoteResultRequest) {
        Long userId = createVoteResultRequest.getUserId();
        List<String> options = createVoteResultRequest.getOptions();

        Vote vote = checkVoteExists(voteHash);
        Voter voter = checkVoterExists(voteHash, userId);
        checkUserVoted(vote, userId);
        checkDuplicated(vote, options);

        List<VoteResult> voteResults = new ArrayList<>();
        options.forEach(option -> {
            vote.getVoteOptions().forEach(voteOption -> {
                if (Objects.equals(voteOption.getRestaurant().getRestaurantHash(), option)) {
                    VoteResult voteResult = VoteResult.builder()
                            .voter(voter)
                            .voteOption(voteOption)
                            .build();
                    voteResults.add(voteResult);
                }
            });
        });

        voteResultRepository.saveAll(voteResults);
    }

    

OSIV ( Open Session In View )

  • Capstone 프로젝트를 하다가 이상한 점을 발견했다. 지금까지 난 lazy loading 은 영속성 컨텍스트의 도움을 받아야 하기 때문에 하나의 트랜잭션 안에서만 가능하다고 알고있었다.
  • 그런데 현재 service 레이어의 코드에는 @Transactional 어노테이션이 존재하지 않는다. 그래서 나는 당연히 LazyInitializationException 이 발생할 줄 알았다.
  • 그런데 막상 테스트를 해보니, 테스트코드 에서는 LazyInitializationException 이 발생했지만 직접 API 를 쏴서 사용하는 경우에는 해당 exception 이 발생하지 않았다.
  • 그래서 직접 API 를 사용할 때 Persistence Context 의 지속 시간과 Test 를 수행할 때의 지속시간이 다른가? 라는 생각이 들었다. 그래서 위 질문에 대해 쭉 찾아보던 도중 OSIV 에 이르렀다.

OSIV 란?

  • 본래 영속성 컨텍스트는 transaction 의 시작과 끝 과 동일한 lifecycle 을 갖는다. 즉, transaction 이 시작할 때 영속성 컨텍스트가 생성되어서 transaction 이 commit 혹은 rollback 될 때 사라진다.
  • Open Session In View 의 약자로, 이 OSIV 전략을 이용하면 DB 커넥션이 맺어진 시점부터, API 응답이 끝날때까지 영속성 컨텍스트를 유지한다.
  • Spring Boot 는 이 OSIV 가 default 로 true 이다. 모든 요청마다 영속성 컨텍스트가 api 응답이 끝날때까지 유지된다는 것이다.

OSIV 의 장점

  • OSIV 은 Transaction 밖에서도 lazy loading 이 가능하게 한다는 장점이 있다. 이는 persistence context 가 transaction 과 함께 사라지는 것이 아닌 요청에 대한 응답이 끝날 때 까지 유지되기 때문에 가능하다.

그러면 왜 LazyInitializationException 이 발생했을까?

  • Spring Boot 를 이용한 application 의 경우, 디폴트로 osiv 가 true 로 설정되어있다. 그래서 Service layer 에서 @Transactional 어노테이션이 없어도 lazy loading 이 가능했다.

  • 하지만 테스트코드에는 OSIV 가 적용되지 않는다. 따라서 해당 테스트코드가 같은 영속성 컨텍스트를 공유하게 하려면 @Transactional 어노테이션을 붙이면 된다.

profile
허허,,,

0개의 댓글