트랜잭션의 격리 수준(Isolation level)이란 동시에 여러 트랜잭션이 처리될 때, 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있도록 허용할지 말지를 결정하는 것입니다.
격리 수준은 다음과 같이 4가지로 정의할 수 있습니다.
- READ UNCOMMITTED(커밋되지 않은 읽기)
- READ COMMITTED(커밋된 읽기)
- REPEATABLE READ(반복 가능한 읽기)
- SERIALIZABLE(직렬화 가능)
reference : https://zzang9ha.tistory.com/381
Spring Boot, JPA로 테스트를 진행할 것이고
테스트 시나리오는 이름을 변경하는 로직이 존재하고 이름이 변경될 때
일정시간에는 이름이 임시로 temp로 변경되었다가
일정 시간이 지나면 새로 바뀔 이름으로 변경되는 시나리오입니다.
ex) "이상규" -> "temp" -> "김형준" (이름 변경 transaction)
ㅤㅤㅤㅤㅤㅤㅤㅤㅤ↑
ㅤㅤㅤDirty Read 시도, temp가 나오면 성공
@Entity
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
}
/**
* 이름을 변경하는데 n초가 걸리고
* 변경되는 과정에선 temp 란 임시 이름으로 존재합니다.
*
* 핵심 로직을 잘보여주기위해 threadSleep 로직을 따로 빼주었습니다.
*/
@Transactional
public Member changeNameWait(Long id, String newName) {
Member member = memberRepository.findById(id)
.orElseThrow(IllegalArgumentException::new);
member.setName("temp");
memberRepository.flush();
threadSleep(1000);
member.setName(newName);
memberRepository.flush();
return member;
}
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public Member findByMemberId(Long id) {
return memberRepository.findById(id)
.orElseThrow(IllegalAccessError::new);
}
static private String name = "이상규";
static private String newName = "김형준";
@Test
void changeNameWaitDirtyReadTest() throws ExecutionException, InterruptedException {
final Member member = memberService.saveMember(name);
assertThat(member.getName()).isEqualTo(name);
CompletableFuture<Void> 이름변경 = CompletableFuture.runAsync(() -> {
memberService.changeWaitName(member.getId(), newName);
});
CompletableFuture<Member> 중간조회 = CompletableFuture.supplyAsync(()-> {
threadSleep(500);
return memberService.findByMemberId(member.getId());
});
Member result = 중간조회.get();
assertThat(result.getName()).isEqualTo("temp");
CompletableFuture.allOf(이름변경, 중간조회).join();
}
Hibernate: select member0_.id as id1_0_0_, member0_.name as name2_0_0_ from member member0_ where member0_.id=?
2022-10-02 00:04:47.579 TRACE 90860 --- [onPool-worker-3] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]
2022-10-02 00:04:47.582 TRACE 90860 --- [onPool-worker-3] o.h.type.descriptor.sql.BasicExtractor : extracted value ([name2_0_0_] : [VARCHAR]) - [이상규]
Hibernate: update member set name=? where id=?
2022-10-02 00:04:47.587 TRACE 90860 --- [onPool-worker-3] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [temp]
2022-10-02 00:04:47.587 TRACE 90860 --- [onPool-worker-3] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [BIGINT] - [1]
Hibernate: select member0_.id as id1_0_0_, member0_.name as name2_0_0_ from member member0_ where member0_.id=?
2022-10-02 00:04:48.083 TRACE 90860 --- [onPool-worker-5] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]
2022-10-02 00:04:48.084 TRACE 90860 --- [onPool-worker-5] o.h.type.descriptor.sql.BasicExtractor : extracted value ([name2_0_0_] : [VARCHAR]) - [temp]
Hibernate: update member set name=? where id=?
2022-10-02 00:04:48.590 TRACE 90860 --- [onPool-worker-3] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [김형준]
2022-10-02 00:04:48.590 TRACE 90860 --- [onPool-worker-3] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [BIGINT] - [1]
코드 : https://github.com/salgu1998/Transaction-Isolation-Level-Test