트랜잭션이 무엇인가? 그리고 특징은? @Transaction을 같은 클래스에서 호출할 경우? @Transaction이 CheckedException, UnCheckedException에서 어떻게 동작하는지?
트랜잭션이란 무엇인가?
- 트랜잭션이란 데이터 베이스의 상태를 변경하는 작업 또는 한번에 수행되어야 하는 연산들을 의미
- 상태변경 - insert, update, select, delete
트랜잭션의 특징(ACID)
- 원자성(Atomicity)
- 트랜잭션이 데이터베이스에 모두 반영되던가 전혀 반영이 되지 않아야한다.
- 일관성(Consistency)
- 트랜잭션 처리 결과가 항상 일관성 있어야한다.
- 독립성(Isolation)
- 둘 이상의 트랜잭션이 동시에 실행되고 있을 경우 다른 트랜잭션의 연산에 끼어들 수 없다.
- 지속성(Durability)
- 트랜잭션이 성공적으로 완료됐을 경우, 결과는 영구적으로 반영이 되어야 한다.
@Transaction을 같은 클래스에서 호출할 경우?
- transaction 처리
- aop 방식으로 한 클래스에서 일괄적으로 처리
- 각 메서드나 클래스 범위에 transaction 처리
예시 transaction처리를 각 메서드에서 처리해주는데 여러개 update요청한다. 예를 들어 상점아이디 5개가 들어왔는데 3번째에서 실패했을 경우 실패 이전의 상점 이름 변경을 성공처리 한다. ⇒ 반복을 통해서 update() 시킨다.
@Service
@RequiredArgsConstructor
public class StoreSystemService {
private final StoreRepository storeRepository;
public void updateStore(TestRequest request) {
for (Long id : request.getStoreList()) {
update(id);
}
}
@Transactional
public void update(Long id) {
Store store = storeRepository.findStoreById(id)
.orElseThrow(NotFoundException::new);
store.updateStoreName();
}
}
- 처음에는 updateStore를 호출하고 안에 있는 update에서만 @Transaction 처리 해주면 될 줄 알았다…
- 하지만 한 클래스 내 @Transactional이 설정되어 있지 않은 메서드에서 @Transactional이 설정된 메서드를 호출할 경우 (위와 같은 경우) @Transactional이 작동하지 않는다. ⇒ @Transactional 는 Proxy 기반이고 AOP로 구성되어 있다. 이는 Method 혹은 Class가 실행되기 전/후 등의 단계에서 자동으로 트랜잭션을 묶는다. 그렇기 때문에 @Transactional 은 인스턴스에서 처음으로 호출하는 메서드나 클래스의 속성을 따라가게 된다. 그래서 동일한 Bean안에 상위 메서드가 @Transactional 가 없으면 하위에는 선언이 되었다 해도 전이되지 않는다고 한다. (https://lemontia.tistory.com/878)
해결 코드
@Service
@RequiredArgsConstructor
public class StoreSystemService {
private final TestService testService;
@Transactional
public void updateStore(TestRequest request) {
for (Long id : request.getStoreList()) {
testService.update(id);
}
}
}
@Service
@RequiredArgsConstructor
public class TestService {
private final StoreRepository storeRepository;
@Transactional
public void update(Long id) {
Store store = storeRepository.findStoreById(id)
.orElseThrow(NotFoundException::new);
store.updateStoreName();
}
}
// exception 발생 이전꺼는 성공처리가 된다.
@Transaction이 CheckedException, UnCheckedException에서 어떻게 동작하는지?
- Error는 시스템이 비정상적인 상황에서 발생
- 일단 RuntimeException을 상속받는 것이 uncheckedException
일단 checkedException에서는 rollback이 안된다고 하는데 알아보자.
checkedException일 때 rollback이 일어나지 않는지 확인 예시(예시로 IOException이 발생했다고 가정)
@Service
@RequiredArgsConstructor
public class StoreSystemService {
private final StoreRepository storeRepository;
private final TestService testService;
@Transactional
public void updateStore(TestRequest request) throws IOException {
for (Long id : request.getStoreList()) {
testService.update(id);
}
}
}
@Service
@RequiredArgsConstructor
public class TestService {
private final StoreRepository storeRepository;
@Transactional
public void update(Long id) throws IOException {
Optional<Store> storeById = storeRepository.findStoreById(id);
if (storeById.isEmpty()) {
throw new IOException();
}
}
}
- checkedException이 일어났을 경우 롤백되지 않고 예외 발생
- ⇒ 이유는 spring 의 transactional의 기본 정책이 Unchecked Exception과 Errors로 @Transactional 은 @Transactional(rollbackFor = {RuntimeException.class,Error.class})와 같다.
- 해결 ⇒ Checked Exception을 처리하기 위해서는 throws를 이용해 피호출 메소드에서 호출하는 메소드로 예외를 던진다고 말할 수 있다. 이 "던짐(throws)"은 해당 예외를 처리할 수 있는 메소드까지 던져지게 된다. 하지만 이런 무분별한 throws의 활용은 코드의 가독성을 떨어트림과 동시에, 어떤 메소드의 어떤 부분에서 예외가 발생했는지 알기 어렵게 만든다. ⇒ try catch를 활용해서 uncheckedException을 발생시키자
@Service
@RequiredArgsConstructor
public class StoreSystemService {
private final StoreRepository storeRepository;
private final TestService testService;
@Transactional
public void updateStore(TestRequest request) {
for (Long id : request.getStoreList()) {
testService.update(id);
}
}
}
@Service
@RequiredArgsConstructor
public class TestService {
private final StoreRepository storeRepository;
@Transactional
public void update(Long id) {
Optional<Store> storeById = storeRepository.findStoreById(id);
try {
if (storeById.isEmpty()) {
throw new IOException();
}
} catch (IOException e) {
throw new NotFoundException();
}
}
결론
- 트랜잭션에 대해 모르는게 많았다…
- CheckedExcpetion에서 rollback이 안된다는 것 ⇒ chekcedExcpetion이 발생했을 때 rollback 작성하는 것보다 unCheckedException 발생시켜서 rollback 시키는게 더 좋을듯? 하다.
- 한 클래스 내 @Transactional이 설정되어 있지 않은 메서드에서 @Transactional이 설정된 메서드를 호출할 경우
'spring boot > 기술 적용' 카테고리의 다른 글
converter를 만들지 않고 jpa mysql에서 json 사용하기 (0) | 2022.06.28 |
---|---|
fetchJoin과 pagination을 같이 사용할 때 (0) | 2022.06.24 |
MultipleBagFetchException 발생시 - fetchJoin 여러개 썼을 때 (0) | 2022.06.24 |
Spring Boot에서 GraphQl 적용해보기 (0) | 2022.05.31 |
JPA - pageable을 이용한 paging (0) | 2021.10.31 |