훈훈훈

Spring boot :: Transaction default isolation level 은 어떻게 결정이 될까 본문

Spring Framework/개념

Spring boot :: Transaction default isolation level 은 어떻게 결정이 될까

훈훈훈 2022. 3. 13. 17:18

Introduction


블로그 포스팅 스터디에서 스터디원과 Spring default isolation level 이 어떻게 결정 되는지 토론을 하게 되었다.

 

토론 도중에 스터디원분이 관련 StackOverFlow 글을 찾고 공유해주셨는데, 글 내용은 MySQL connectror/J 는 default isolation level 로 READ_COMMITTED 를 제공한다는 것이다.

 

실제로 코드 레벨에서 살펴보니 진짜 READ_COMMITTED 로 제공이 되고 있었다.

 

내가 알던 내용은 MySQL 은 storage engine 으로 innoDB 를 사용하는 경우 default isolation level 로 REPEATABLE_READ 를 제공한다는 것이다. 

 

그렇기 때문에 MySQL connector/J 도 당연히 REPEATABLE_READ 로 제공이 될 줄 알았는데 결과는 꽤나 놀라웠다.

 

그와 반대로 MariaDB connector 는 REPEATABLE_READ 로 제공한다.

 

테스트로 스프링 애플리케이션에서 jdbc url 을 MySQL 로 연결하였을 때 isolcation level 값을 확인해보았다.

그 결과 REPEATABLE_READ 가 나왔다.

 

왜 이런 결과가 나오는지 궁금하여 스프링 코드를 따라가 보면서 어떻게 default isolation level 을 결정하는지 찾아보게 되었고, 그 과정을 정리해보려고 한다.

 

결론부터 먼저 말하자면 스프링에서 MySQL 을 사용하는 경우 REPEATABLE_READ 로 제공이 된다.

(실제로는 RDBMS 와 커넥션을 맺고 정보를 가져옴)

 

이번 계기로 어설프게 대충 판단을 하면 안되는 것을 다시 한 번 느꼈다. 🥲

 

 

 

@Transactional test


일단 스프링에서 어떻게 동작하는지 아래 코드로 테스트 해보았다.

(해당 코드는 예전에 예제로 만든 프로젝트이며, 전체 코드는 Github 에서 확인할 수 있다.)

@Service
@RequiredArgsConstructor
public class AccountService {
  private final AccountRepository accountRepository;
  private final PasswordEncoder passwordEncoder;
  private final DataSource dataSource;

  @Transactional
  public AccountDto createAccount(@Valid final SignUpDto accountDto) throws SQLException {
    final Account existAccount = accountRepository.findAccountByEmail(accountDto.getEmail()).orElse(null);
    if (existAccount != null) {
      throw new IllegalArgumentException("duplicated");
    }

    final Account account = Account.builder()
        .name(accountDto.getName())
        .email(accountDto.getEmail())
        .password(passwordEncoder.encode(accountDto.getPassword()))
        .build();

    System.out.println("------------------------------------");
    System.out.println(dataSource.getConnection().getTransactionIsolation());
    System.out.println("------------------------------------");
    
    return AccountDto.of(accountRepository.save(account));
  }
}

간단하게 @Transactional 이 선언 된 메소드에서 transaction isolataion level 을 조회해보았다.

 

isolation level = 4로 나오고 해당 번호는 REPEATABLE_READ 를 뜻한다.

 

해당 값이 의미하는 바는 Connection interface 에서 확인할 수 있다.

 

 

 

 

Code Analysis


이제 스프링 코드를 디버깅하면서 어떻게 동작하는지 살펴보자.

 

Spring 애플리케이션을 실행시키면 NonRegisteringDriver 클래스의 connect 메소드는 jdbc url 을 파라미터로 전달 받은 후에 Connection 객체를 생성 후에 리턴을 하게 된다.

 

생성이 되는 Connection 객체는 com.mysql.cj.jdbc.ConnectionImpl 클래스이며 해당 객체가 생성이 되면서 isolation level 도 결정한다.

 

이제 ConnectionImpl 객체가 생성 되는 과정을 살펴보자 

ConnectionImpl 클래스의 getInstance 메소드는 정말 단순하다.

해당 클래스의 생성자를 호출해서 리턴하는 역할만 한다.

 

 

그 다음으로 ConnectionImpl 클래스 생성자에는 여러 로직들이 들어있어 다소 코드 라인이 좀 길다.

그래서 전체 코드는 첨부하지 않고 부분 부분 캡쳐해서 넣었다.

 

먼저 살펴볼 내용은 위에 서론에서 언급한 내용이다.

 

해당 라인에서 MySQL Connector/J 에서 정의한 메타 데이터를 가져오게 되는데, 여기에서는 코드에서 정의한 default isolation level 로 초기화가 된다. 

 

해당 라인을 실행시켰을 때 디버깅한 값을 확인해보면 isolation level = 2(READ_COMMITTED) 로 세팅된 것을 볼 수 있다.

 

 

그 다음에는 몇 라인 아래에 있는 createNewIO 메서드를 확인해보자 

해당 메서드 호출 후에 isolation level = 4 (REPEATABLE_READ) 로 세팅이 된다.

 

 

createNewIO 메소드르 이후 아래 흐름으로 메소드 Call 이 발생한다. 

 

 

마지막으로 checkTransactionIsolationLevel 메소드에서 MySQL 서버와 커넥션을 맺고 isolataion level 을 가져오게 된다.

 

 

이후 처리하는 과정을 더 있는 것 같지만, 목적은 이미 달성했기에 이정도에서 마무리하려고 한다. 

 

Comments