본문 바로가기
Computer Science/데이터베이스

AUTO_INCREMENT는 ROLLBACK되지 않는다

by 어썸오184 2022. 4. 27.
728x90
반응형

데이터 접근 객체 테스트 작성 중 다음과 같은 문제를 겪었습니다.

@SpringBootTest
@Transactional
@ActiveProfiles("test")
class ChessGameDAOTest {

    @Autowired
    private ChessGameDAO dao;

    ...

    @Test
    @DisplayName("체스 게임방을 ID로 불러온다")
    void findChessGameById() {
        // arrange
        String gameName = "test1";
        dao.addGame(new GameCreationDTO(gameName, "123"));

        // act
        ChessGame findGame = dao.findGameById(1);

        // assert
        assertThat(findGame.getName()).isEqualTo(gameName);
    }

    ...
}

DB에 게임 하나를 추가한 후, PK를 통해 해당 데이터를 잘 찾아오는지 확인하는 테스트입니다. @Transactional 애너테이션으로 테스트가 돌아갈 때마다 모든 변경 사항이 롤백되도록 설정했기 때문에, addGame 메서드로 추가된 데이터의 PK는 1이 될 것입니다.

예상한대로 테스트가 성공합니다.

다른 테스트를 하나 더 추가해봅시다.

게임의 상태를 종료 상태로 업데이트 하는 기능을 테스트합니다.

@SpringBootTest
@Transactional
@ActiveProfiles("test")
class ChessGameDAOTest {

    @Autowired
    private ChessGameDAO dao;

    ...

    @Test
    @DisplayName("게임을 종료 상태로 업데이트한다")
    void updateGameEnd() {
        // arrange
        dao.addGame(new GameCreationDTO("test1", "123"));

        // act
        long pk = 1;
        dao.updateGameEnd(pk);
        ChessGame updatedGame = dao.findGameById(pk);

        // assert
        assertThat(updatedGame.isEnd()).isTrue();
    }

    ...
}

게임을 하나 추가합니다. 역시 pk는 1일 것이기 때문에 1로 추가한 게임을 찾아온 뒤 게임의 상태가 end로 변화되었는지 확인합니다.

역시 테스트가 잘 성공합니다.

그럼 이번엔 두 테스트를 같이 돌려볼까요?

갑자기 조회 테스트가 실패합니다. 에러 메시지를 살펴보니 해당 pk를 가진 데이터가 없음을 알 수 있었습니다.

이 같은 에러가 발생하는 이유는 롤백을 하더라도 auto_increment로 증가한 pk는 롤백 되지 않기 때문입니다.

예를 들어 테이블 A에 insert 연산을 하고 롤백을 하면, 테이블에 데이터는 없지만 다음에 insert 되는 데이터의 pk는 2가 됩니다.

생각해보면 이렇게 설계하는 것이 당연합니다.

  • 프로그램 1이 트랜잭션을 열고 테이블 A에 데이터를 추가합니다. pk는 1이 됩니다.
  • 프로그램 2가 트랜잭션을 열고 테이블 A에 데이터를 추가합니다. pk는 2가 됩니다.
  • 프로그램 2가 테이블 A에 추가한 pk를 fk로 사용하여 테이블 B에 어떤 데이터를 추가합니다.
  • 프로그램 2가 커밋합니다.
  • 프로그램 1이 롤백합니다.

참고 : transactions - MySQL AUTO_INCREMENT does not ROLLBACK - Stack Overflow

만약 어떤 트랜잭션이 롤백될 때, pk도 같이 롤백한다면 위와 같은 상황에서 데이터 정합성을 보장하기가 매우 어렵습니다.

따라서 어떤 작업을 할 때 auto_increment pk에 의존하도록 해서는 안됩니다. 처음의 테스트는 아래와 같이 affected row의 pk를 반환하도록 한 후 이를 이용하도록 테스트하면 안전합니다.

ChessGameDAO.java

public long addGame(final GameCreationDTO gameCreationDTO) {
    String sql = "INSERT INTO CHESS_GAME (name, password) VALUES (?, ?)";
    KeyHolder keyHolder = new GeneratedKeyHolder();

    jdbcTemplate.update(connection -> {
        PreparedStatement statement = connection.prepareStatement(sql, new String[]{"id"});
        statement.setString(1, gameCreationDTO.getName());
        statement.setString(2, gameCreationDTO.getPassword());
        return statement;
    }, keyHolder);
    return keyHolder.getKey().longValue();
}

 

ChessGameDAOTest.java

@Test
@DisplayName("체스 게임방을 ID로 불러온다")
void findChessGameById() {
    // arrange
    String gameName = "test1";
    long id = dao.addGame(new GameCreationDTO(gameName, "123"));

    // act
    ChessGame findGame = dao.findGameById(id);

    // assert
    assertThat(findGame.getName()).isEqualTo(gameName);
}

@Test
@DisplayName("게임을 종료 상태로 업데이트한다")
void updateGameEnd() {
    // arrange
    long addedGame = dao.addGame(new GameCreationDTO("test1", "123"));

    // act
    long updateGameId = dao.updateGameEnd(addedGame);
    ChessGame updatedGame = dao.findGameById(updateGameId);

    // assert
    assertThat(updatedGame.isEnd()).isTrue();
}

 

 

728x90
반응형

댓글