Transaction 에 대한 처리를 하기 위해 관련 테스트를 작성하였다. 에러가 발생했을 경우 저장했던 데이터가 롤백되었는지 확인하는 테스트이다. 처음 코드는 다음과 같이 작성하였다.

@DisplayName("사용자 정보 저장(MySQL) 시 에러가 발생하면 MySQL 이 롤백된다")
@Test
void errorMySQL() {
		// mocking
    final String oAuthId = null;
    final OAuthTokenResponse oAuthTokenResponse = new OAuthTokenResponse(ACCESS_TOKEN, EXPIRES_IN, REFRESH_TOKEN, EXPIRES_IN, TOKEN_TYPE);
    final OAuthUserResponse oAuthUserResponse = new OAuthUserResponse(oAuthId);
    given(connector.requestToken(AUTHORIZATION_CODE, REDIRECT_URL)).willReturn(oAuthTokenResponse);
    given(connector.requestUserInfo(TOKEN_TYPE, ACCESS_TOKEN)).willReturn(oAuthUserResponse);

		// when
    final OAuthRequest request = new OAuthRequest("google", REDIRECT_URL, REFERRER_URL);
    final OAuthCodeRequest codeRequest = new OAuthCodeRequest(AUTHORIZATION_CODE);
    try {
        oAuthService.login(request, codeRequest);
    } catch (DataIntegrityViolationException ignored) {
    }

		// then
    final Optional<Member> savedMember = memberRepository.findByOauthId(oAuthId); // 에러 발생!!
    assertThat(savedMember).isEmpty();
}

MySQL 에 저장된 데이터가 에러가 발생하여 롤백되는 것을 확인하기 위해 NOT NULL 제약 조건이 걸린 컬럼에 null 을 입력하였다. 필드의 값이 null 인 자바 객체가 생성되고, 영속 컨텍스트에 엔티티로 영속되며, 바로 DB 에 INSERT 쿼리문이 실행된다. INSERT 쿼리문 실행 시 제약 조건이 위반되어 DataIntegrityViolationException 에러가 발생하고 find 시 데이터가 저장되어 있지 않다는 것을 확인한다.

하지만 memberRepository 에 findByOauthId 메서드를 실행하는 위치에서 에러가 발생하였다.

Hibernate: 
    select
        m1_0.id,
        m1_0.archived,
        m1_0.archived_at,
        m1_0.created_at,
        m1_0.nickname,
        m1_0.oauth_id,
        m1_0.phone_number,
        m1_0.resource_id,
        m1_0.updated_at 
    from
        member m1_0 
    where
        m1_0.oauth_id is null
2023-05-05T22:20:03.542+09:00 ERROR 34192 --- [    Test worker] org.hibernate.AssertionFailure           : HHH000099: an assertion failure occurred (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): org.hibernate.AssertionFailure: null id in com.dobugs.yologaauthenticationapi.domain.Member entry (don't flush the Session after an exception occurs)

null id in com.dobugs.yologaauthenticationapi.domain.Member entry (don't flush the Session after an exception occurs)
org.hibernate.AssertionFailure: null id in com.dobugs.yologaauthenticationapi.domain.Member entry (don't flush the Session after an exception occurs)
...

SELECT 쿼리문은 실행되었지만 에러가 발생하였다. 에러가 발생한 이후에는 flush 할 수 없다고 한다. 즉, 엔티티 저장 시 제약 조건에 의해 에러가 발생하였는데 동일한 트랜잭션 내에서 또 다른 JPA 관련 연산을 실행하여 에러가 발생하였다.

트랜잭션이 시작되면 현재 세션의 변경사항을 메모리에 저장해두고 데이터베이스에 flush 하여 적용한다. 예외가 발생할 경우 메모리에 저장된 데이터가 손상(불일치)되어 그 이후의 연산에 영향을 끼쳐 이후의 연산이 불가능하다. 추가 연산을 하려면 세션에 저장된 데이터를 지워주어야 한다.

@Autowired
private TestEntityManager entityManager;

@DisplayName("사용자 정보 저장(MySQL) 시 에러가 발생하면 MySQL 이 롤백된다")
@Test
void errorMySQL() {
		// mocking
    final String oAuthId = null;
    final OAuthTokenResponse oAuthTokenResponse = new OAuthTokenResponse(ACCESS_TOKEN, EXPIRES_IN, REFRESH_TOKEN, EXPIRES_IN, TOKEN_TYPE);
    final OAuthUserResponse oAuthUserResponse = new OAuthUserResponse(oAuthId);
    given(connector.requestToken(AUTHORIZATION_CODE, REDIRECT_URL)).willReturn(oAuthTokenResponse);
    given(connector.requestUserInfo(TOKEN_TYPE, ACCESS_TOKEN)).willReturn(oAuthUserResponse);

		// when
    final OAuthRequest request = new OAuthRequest("google", REDIRECT_URL, REFERRER_URL);
    final OAuthCodeRequest codeRequest = new OAuthCodeRequest(AUTHORIZATION_CODE);
    try {
        oAuthService.login(request, codeRequest);
    } catch (DataIntegrityViolationException ignored) {
    }
		entityManager.clear(); // 세션 지우기

		// then
    final Optional<Member> savedMember = memberRepository.findByOauthId(oAuthId);
    assertThat(savedMember).isEmpty();
}

세션에 저장된 데이터를 지우기 위해 EntityManager 를 clear 해주었다. clear() 를 호출하여 flush 되지 않은 영속 엔티티들을 준영속 상태로 돌려놓는다.

EntityManager#clear

Clear the persistence context, causing all managed entities to become detached. Changes made to entities that have not been flushed to the database will not be persisted.

Untitled