@Transactional 내부에서 save가 발생하는 경우 처리방법 질문드립니다
502
작성한 질문수 23
@Entity @Data
public class Product {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
}
@Data @Entity
public class Order {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@Column(nullable = false)
Product product;
}
@Service @RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepo;
private final ProductRepository prodRepo;
@Transactional
public void CreateOrderV1(){
Product product = new Product();
prodRepo.save(product);
Order order = new Order();
order.setProduct(product);
orderRepo.save(order);
throw new RuntimeException("order 에러");
}
}
Product와 Order를 생성하는데 예외가 발생하면 Product와 Order 둘 다 롤백하겠다는 의도의 비즈니스 로직을 가정해보겠습니다.
Repository는 JpaRepository를 상속받아서 사용하고 있습니다. DB는 MySQL을 사용하고 있고, IDENTITY 전략으로 키를 생성하는데, 위 CreateOrderV1 함수를 실행하면 에러가 발생합니다.
Order는 Product를 참조하고 있기 때문에 결국 product의 save 쿼리가 DB에 전달되야 키 값을 알 수 있기 때문으로 생각했고, 아래처럼 CreateOrderV2를 생성했습니다.
@Transactional
public void CreateOrderV2() {
Product product = internalService.CreateProduct();
Order order = internalService.CreateOrder(product);
throw new RuntimeException("order 에러");
}
@Service @RequiredArgsConstructor
public class InternalService {
private final ProductRepository prodRepo;
private final OrderRepository orderRepo;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Product CreateProduct(){
Product product = new Product();
return prodRepo.save(product);
}
@Transactional
public Order CreateOrder(Product product){
Order order = new Order();
order.setProduct(product);
return orderRepo.save(order);
}
}
InternalService의 CreateProduct는 결국 쿼리가 DB에 날아가야하기 때문에 REQUIRES_NEW로 트랜잭션을 분리시켜줬습니다.
그런데 이렇게 하면 CreateOrderV2에서 런타임 에러를 뱉기 때문에 롤백이 수행될 텐데, DB 테이블을 보면 order는 롤백이 됐지만 product의 경우 롤백이 되지 않습니다.
이런 현상은 결국 강의에서 원했던 상황인 '회원 데이터는 저장되고, 로그 데이터만 롤백되는' 상황에는 부합하지만, 위 예제 코드에서는 Product에 의존하는 Order를 위해서 CreateProduct 코드에 REQUIRES_NEW를 사용했기 때문에 product까지 롤백되기를 원하는 비즈니스 로직의 의도대로 동작하지 않는다고까지는 이해했습니다. 그런데 이러한 비즈니스 로직을 어떻게 처리해야할지 고민하다가 질문글 올립니다!
이렇게 Product에 의존적인 Order를 가정할 때 @Transactional 혹은 다른 방법으로든 해결할 수 있는 방법이 있을까요? 엔티티매니저를 주입 받아서 prodRepo.save 직후에 em.flush 를 호출도 해봤지만 의도대로 동작하지는 않았고(@Transactional 내부에서 em.flush 호출 관련해서 어떤 식으로 동작하는지 알아보기 위해 구글링은 해봤는데 정확한 정보는 찾기 어려웠습니다ㅠㅜ) 결국 CreateOrderV2 내에서 Product product = internalService.CreateProduct() 호출 이후 try catch로 감싸서 예외 발생 시 catch 문에서 prodRepo.delete(product) 를 호출해줘서 수동으로 롤백처럼 동작하게끔 처리하는 방법까지밖에 생각이 나지 않았습니다..ㅠ
답변 1
0
안녕하세요. G General님
죄송하지만, 정확하게 어떤 것을 질문하시는지 잘 이해가 되지 않습니다.
처음 질문에서
"Product와 Order를 생성하는데 예외가 발생하면 Product와 Order 둘 다 롤백하겠다는 의도의 비즈니스 로직을 가정해보겠습니다."
이렇게 이야기하고 코드를 남기셨는데요.
CreateOrderV1는 코드만 봐서는 정상 동작해야 합니다. 따라서 해당 코드는 함께 커밋되거나 함께 롤백되는 것이 정상입니다.
만약 그게 아니라면 다른 문제가 있는 것 같아요.
좀 더 확인해보시면 좋겠습니다.
설정 정보 없이 임베디드 데이터베이스 생성
0
43
1
RepositoryTest의 패키지 위치가 domain인 이유
0
61
2
REQUIRES_NEW 해결 방법에 대해서 질문있습니다!!
0
50
1
update()에 사용하는 setter 질문드립니다.
0
63
1
SQL 중심적 개발의 문제점에 대한 질문
0
95
1
혹시 Containing 을 안쓰신 이유가 있을까요?
0
96
2
[공유] 스프링부트 4.x 버전 mybatis 연동
0
213
1
@repository 어노테이션
0
111
3
ItemService
0
67
1
논리 커밋, 물리 커밋 질문드립니다.
0
59
1
내부 트랜잭션 커밋은 필수인가요?
0
64
1
프록시 커넥션 객체를 반환할 때 생성하는건가요?
0
63
1
Transaction readOnly 성능 개선 (김영한님의 대한 감사인사)
2
192
2
JPQL 대신 네이티브 쿼리를 사용해야 하는 경우
0
88
1
@EventListener(ApplicationReadyEvent.class) 관련
0
101
1
트랜잭션 동기화 매니저와 데이터 소스
0
83
1
DB 관련 강의 개설 계획은 없으신건가요?
0
141
2
물리 트랜잭션 과 논리트랜잭션 용어를 맞게 이해한걸까요
0
102
1
스프링 3 버전 이상 rollbackFor 변경된듯요
1
125
1
트랜잭션 전파 질문.
0
95
1
프로젝트 오픈 에러
0
139
1
외부 트랜잭션에서 isNewTransaction이 false로 나오는거에 대해 질문드립니다
0
87
2
같은 스레드를 사용하면 트랜잭션 동기화 매니저는 같은 커넥션을 반환
0
80
1
h2 인메모리 테스트중 예약어 충돌날 경우 대처방법
0
107
1





