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.