JPA 영속성 유지기간을 관찰하기 위한 여러 예제를 설명합니다
@Transaction코드 밖에서는 영속성 유지가 되지 않는 것을 확인
@Override
@Transactional("REQUIRED")
@Async("taskPool")
public CompletableFuture<UserCredential> saveUser(RegisterUserRequest req) {
checkUserExistOrThrowCustomException(req.getUserId());
UserCredential user = userCredentialRepository.save(req.toEntity());
return CompletableFuture.completeFuture(user);
}
@Test
public void changeNameFunctionOutside(){
userService.saveUser(req)
.thenApply((user)->{
user.setUserName("newUserName");
return user;
}).thenAccept((user)->{
UserCredential findUser = userCredentialRepository.findById(user.getUserId()).orElseThrow(new RuntimeException());
assertThat(findUser.getUserName()).isNotEqualTo("newUserName"); // oldUserName != newUserName
})
}
@Transaction코드 내부에서 영속성 유지가 되는 것을 확인
@Override
@Transactional("REQUIRED")
@Async("taskPool")
public CompletableFuture<UserCredential> saveUser(RegisterUserRequest req) {
checkUserExistOrThrowCustomException(req.getUserId());
UserCredential user = userCredentialRepository.save(req.toEntity());
return CompletableFuture.supplyAsync(()->{
user.setUserName("newUserName"); // 내부로 변경
return user;
});
}
@Test
public void changeNameFunctionOutside(){
userService.saveUser(req)
.thenAccept((user)->{
UserCredential findUser = userCredentialRepository.findById(user.getUserId()).orElseThrow(new RuntimeException());
assertThat(findUser.getUserName()).isEqualTo("newUserName"); // oldUserName => newUserName == newUserName
})
}
이를 통해 바뀐 생각은 아래와 같아요.
- 기존
Transaction은 ThreadLocal 하게 설정되며 @Transactional 로 설정 시, Thread 가 callback 함수를 호출하는 시점에 commit 되는 것으로 알고있었습니다. 즉, @Transactional 이 선언된 함수 내에서 thenApply 와 같은 callback 함수가 호출 되어도 엔티티는 Detached 된 상태이므로 변경되지 않을것이라고 판단했어요.
- 변경
@Transactional 로 설정 시, 함수 가 Stack 에서 빠지는 시점에 commit 이 이루어지는것을 확인하였습니다. 즉, 함수 내부에서는 ThreadLocal 하다면 어떤 것이든 트랜잭션에 담길 것이고 외부에서는 Detached 된 엔티티를 반환받는것을 확인했어요.
This post explains several examples for observing how long JPA persistence is maintained.
- Confirm that persistence is not maintained outside
@Transactioncode
@Override
@Transactional("REQUIRED")
@Async("taskPool")
public CompletableFuture<UserCredential> saveUser(RegisterUserRequest req) {
checkUserExistOrThrowCustomException(req.getUserId());
UserCredential user = userCredentialRepository.save(req.toEntity());
return CompletableFuture.completeFuture(user);
}
@Test
public void changeNameFunctionOutside(){
userService.saveUser(req)
.thenApply((user)->{
user.setUserName("newUserName");
return user;
}).thenAccept((user)->{
UserCredential findUser = userCredentialRepository.findById(user.getUserId()).orElseThrow(new RuntimeException());
assertThat(findUser.getUserName()).isNotEqualTo("newUserName"); // oldUserName != newUserName
})
}
- Confirm that persistence is maintained inside
@Transactioncode
@Override
@Transactional("REQUIRED")
@Async("taskPool")
public CompletableFuture<UserCredential> saveUser(RegisterUserRequest req) {
checkUserExistOrThrowCustomException(req.getUserId());
UserCredential user = userCredentialRepository.save(req.toEntity());
return CompletableFuture.supplyAsync(()->{
user.setUserName("newUserName"); // changed inside
return user;
});
}
@Test
public void changeNameFunctionOutside(){
userService.saveUser(req)
.thenAccept((user)->{
UserCredential findUser = userCredentialRepository.findById(user.getUserId()).orElseThrow(new RuntimeException());
assertThat(findUser.getUserName()).isEqualTo("newUserName"); // oldUserName => newUserName == newUserName
})
}
What changed in my thinking from this is below.
- Before
I thought that a transaction is set in a ThreadLocal way, and when @Transactional is set, commit happens when the thread calls the callback function. In other words, even if a callback function such as thenApply is called inside a function annotated with @Transactional, I expected the entity to be detached and therefore not changed.
- After
I confirmed that when @Transactional is set, commit happens when the function leaves the stack. In other words, inside the function, anything ThreadLocal is included in the transaction, while outside the function, a detached entity is returned.