작성
·
431
0
@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는 코드만 봐서는 정상 동작해야 합니다. 따라서 해당 코드는 함께 커밋되거나 함께 롤백되는 것이 정상입니다.
만약 그게 아니라면 다른 문제가 있는 것 같아요.
좀 더 확인해보시면 좋겠습니다.
말씀듣고 다시 확인해보니까 다른 문제가 있었습니다! 감사합니다