deleteAll과 deleteAllInBatch()의 차이
OrderServiceTest를 작성하면서...
@SpringBootTest
class OrderServiceTest {
@Autowired private ProductRepository productRepository;
@Autowired private OrderRepository orderRepository;
@Autowired private OrderProductRepository orderProductRepository;
@Autowired private StockRepository stockRepository;
@Autowired private OrderService orderService;
@AfterEach
void tearDown(){
orderProductRepository.deleteAllInBatch();
productRepository.deleteAllInBatch();
orderRepository.deleteAllInBatch();
}
@Test
@DisplayName("주문번호 리스트를 받아 주문을 생성한다.")
public void createOrder(){
// given
LocalDateTime registeredDTE = LocalDateTime.now();
Product product1 = createProduct(SIGNATURE_DISH,"001",18000,"맛있는김치찜");
Product product2 = createProduct(SIGNATURE_DISH,"002",28000,"와사비왕창");
Product product3 = createProduct(SIDE_DISH,"003",9900,"황도");
productRepository.saveAll(List.of(product1,product2,product3));
Stock stock1 = Stock.create("001", 2);
Stock stock2 = Stock.create("002", 1);
Stock stock3 = Stock.create("003", 2);
stockRepository.saveAll(List.of(stock1,stock2,stock3));
OrderCreateRequest request = OrderCreateRequest.builder()
.productNumber(List.of("001", "002"))
.build();
// when
OrderResponse orderResponse = orderService.createOrder(request.toServiceRequest(), registeredDTE);
// then
assertThat(orderResponse.getId()).isNotNull();
assertThat(orderResponse)
.extracting("registeredDateTime","totalPrice")
.contains(registeredDTE,46000);
assertThat(orderResponse.getProducts()).hasSize(2)
.extracting("productNumber","price")
.containsExactlyInAnyOrder(
tuple("001",18000),
tuple("002",28000)
);
}
orderProductRepository는 다:다 매핑을 위한 중간 테이블 입니다. LAZY를 이용했다.
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class OrderProduct extends BaseDate {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Order order;
@ManyToOne(fetch = FetchType.LAZY)
private Product product;
public OrderProduct(Order order, Product product) {
this.order = order;
this.product = product;
}
}
다시 주제로 돌아가면..
테스트가 여러개 있을 때 전체 테스트를 통과시키기 위해서 우리는 테스트했던 데이터들을 전부 기존의 상태로 되돌리기 위해서 전부 지워주는 작업을 할것이다.
@Transactional , @AfterEach
우선 AfterEach에서 DeleteAll과 DeleteAllInBatch의 차이를 query를 통해서 보려고 한다.
실행시켜보면 지우려고 했던 테이블이 delete from table;로 깔끔하게 3번 다지워지는 것을 알 수 있다.
문제는 중간 테이블이 제일 먼저 안 지워질 때 발생한다.
@AfterEach
void tearDown(){
orderRepository.deleteAllInBatch();
orderProductRepository.deleteAllInBatch(); //중간 테이블
productRepository.deleteAllInBatch();
}
Order가 아니라 Product가 먼저 지워져도 동일하게 에러가 발생했을 것이다.
Cannot delete or update a parent row: a foreign key constraint fails (`food_finder`.`order_product`, CONSTRAINT `FKl5mnj9n0di7k1v90yxnthkc73` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`))
- 에러가 발생하는데 중간 테이블에서 Order와 Product를 참조하고 있었는데 참조하고 있던 테이블이 사라지면 외부 키 제약 조건으로 인해서 발생하는거 같았다.
그렇다면 DeleteAll은?
@AfterEach
void tearDown(){
orderRepository.deleteAll();
orderProductRepository.deleteAll();
productRepository.deleteAll();
}
테스트는 통과하는데 omg,,, query가 너무 많이 나가는데..?
2023-07-18 14:53:31.379 DEBUG 91783 --- [ main] org.hibernate.SQL :
select
order0_.id as id1_10_,
order0_.create_date as create_d2_10_,
order0_.last_modified_date as last_mod3_10_,
order0_.order_status as order_st4_10_,
order0_.registered_date_time as register5_10_,
order0_.total_price as total_pr6_10_
from
orders order0_
2023-07-18 14:53:31.380 INFO 91783 --- [ main] p6spy : #1689659611380 | took 0ms | statement | connection 8| url jdbc:mysql://localhost:3306/food_finder?serverTimezone=UTC&characterEncoding=UTF-8
select order0_.id as id1_10_, order0_.create_date as create_d2_10_, order0_.last_modified_date as last_mod3_10_, order0_.order_status as order_st4_10_, order0_.registered_date_time as register5_10_, order0_.total_price as total_pr6_10_ from orders order0_
select order0_.id as id1_10_, order0_.create_date as create_d2_10_, order0_.last_modified_date as last_mod3_10_, order0_.order_status as order_st4_10_, order0_.registered_date_time as register5_10_, order0_.total_price as total_pr6_10_ from orders order0_;
2023-07-18 14:53:31.386 DEBUG 91783 --- [ main] org.hibernate.SQL :
select
orderprodu0_.order_id as order_id4_9_1_,
orderprodu0_.id as id1_9_1_,
orderprodu0_.id as id1_9_0_,
orderprodu0_.create_date as create_d2_9_0_,
orderprodu0_.last_modified_date as last_mod3_9_0_,
orderprodu0_.order_id as order_id4_9_0_,
orderprodu0_.product_id as product_5_9_0_
from
order_product orderprodu0_
where
orderprodu0_.order_id=?
2023-07-18 14:53:31.387 INFO 91783 --- [ main] p6spy : #1689659611387 | took 0ms | statement | connection 8| url jdbc:mysql://localhost:3306/food_finder?serverTimezone=UTC&characterEncoding=UTF-8
select orderprodu0_.order_id as order_id4_9_1_, orderprodu0_.id as id1_9_1_, orderprodu0_.id as id1_9_0_, orderprodu0_.create_date as create_d2_9_0_, orderprodu0_.last_modified_date as last_mod3_9_0_, orderprodu0_.order_id as order_id4_9_0_, orderprodu0_.product_id as product_5_9_0_ from order_product orderprodu0_ where orderprodu0_.order_id=?
select orderprodu0_.order_id as order_id4_9_1_, orderprodu0_.id as id1_9_1_, orderprodu0_.id as id1_9_0_, orderprodu0_.create_date as create_d2_9_0_, orderprodu0_.last_modified_date as last_mod3_9_0_, orderprodu0_.order_id as order_id4_9_0_, orderprodu0_.product_id as product_5_9_0_ from order_product orderprodu0_ where orderprodu0_.order_id=1;
2023-07-18 14:53:31.395 DEBUG 91783 --- [ main] org.hibernate.SQL :
delete
from
order_product
where
id=?
2023-07-18 14:53:31.396 INFO 91783 --- [ main] p6spy : #1689659611396 | took 0ms | statement | connection 8| url jdbc:mysql://localhost:3306/food_finder?serverTimezone=UTC&characterEncoding=UTF-8
delete from order_product where id=?
delete from order_product where id=1;
2023-07-18 14:53:31.397 DEBUG 91783 --- [ main] org.hibernate.SQL :
delete
from
order_product
where
id=?
2023-07-18 14:53:31.397 INFO 91783 --- [ main] p6spy : #1689659611397 | took 0ms | statement | connection 8| url jdbc:mysql://localhost:3306/food_finder?serverTimezone=UTC&characterEncoding=UTF-8
delete from order_product where id=?
delete from order_product where id=2;
2023-07-18 14:53:31.397 DEBUG 91783 --- [ main] org.hibernate.SQL :
delete
from
orders
where
id=?
2023-07-18 14:53:31.398 INFO 91783 --- [ main] p6spy : #1689659611398 | took 0ms | statement | connection 8| url jdbc:mysql://localhost:3306/food_finder?serverTimezone=UTC&characterEncoding=UTF-8
delete from orders where id=?
delete from orders where id=1;
2023-07-18 14:53:31.399 INFO 91783 --- [ main] p6spy : #1689659611399 | took 0ms | commit | connection 8| url jdbc:mysql://localhost:3306/food_finder?serverTimezone=UTC&characterEncoding=UTF-8
;
2023-07-18 14:53:31.402 DEBUG 91783 --- [ main] org.hibernate.SQL :
select
orderprodu0_.id as id1_9_,
orderprodu0_.create_date as create_d2_9_,
orderprodu0_.last_modified_date as last_mod3_9_,
orderprodu0_.order_id as order_id4_9_,
orderprodu0_.product_id as product_5_9_
from
order_product orderprodu0_
2023-07-18 14:53:31.403 INFO 91783 --- [ main] p6spy : #1689659611403 | took 0ms | statement | connection 9| url jdbc:mysql://localhost:3306/food_finder?serverTimezone=UTC&characterEncoding=UTF-8
select orderprodu0_.id as id1_9_, orderprodu0_.create_date as create_d2_9_, orderprodu0_.last_modified_date as last_mod3_9_, orderprodu0_.order_id as order_id4_9_, orderprodu0_.product_id as product_5_9_ from order_product orderprodu0_
select orderprodu0_.id as id1_9_, orderprodu0_.create_date as create_d2_9_, orderprodu0_.last_modified_date as last_mod3_9_, orderprodu0_.order_id as order_id4_9_, orderprodu0_.product_id as product_5_9_ from order_product orderprodu0_;
2023-07-18 14:53:31.403 INFO 91783 --- [ main] p6spy : #1689659611403 | took 0ms | commit | connection 9| url jdbc:mysql://localhost:3306/food_finder?serverTimezone=UTC&characterEncoding=UTF-8
;
2023-07-18 14:53:31.405 DEBUG 91783 --- [ main] org.hibernate.SQL :
select
product0_.id as id1_11_,
product0_.name as name2_11_,
product0_.price as price3_11_,
product0_.product_number as product_4_11_,
product0_.selling_status as selling_5_11_,
product0_.type as type6_11_
from
product product0_
2023-07-18 14:53:31.405 INFO 91783 --- [ main] p6spy : #1689659611405 | took 0ms | statement | connection 10| url jdbc:mysql://localhost:3306/food_finder?serverTimezone=UTC&characterEncoding=UTF-8
select product0_.id as id1_11_, product0_.name as name2_11_, product0_.price as price3_11_, product0_.product_number as product_4_11_, product0_.selling_status as selling_5_11_, product0_.type as type6_11_ from product product0_
select product0_.id as id1_11_, product0_.name as name2_11_, product0_.price as price3_11_, product0_.product_number as product_4_11_, product0_.selling_status as selling_5_11_, product0_.type as type6_11_ from product product0_;
2023-07-18 14:53:31.406 DEBUG 91783 --- [ main] org.hibernate.SQL :
delete
from
product
where
id=?
2023-07-18 14:53:31.407 INFO 91783 --- [ main] p6spy : #1689659611407 | took 0ms | statement | connection 10| url jdbc:mysql://localhost:3306/food_finder?serverTimezone=UTC&characterEncoding=UTF-8
delete from product where id=?
delete from product where id=1;
2023-07-18 14:53:31.407 DEBUG 91783 --- [ main] org.hibernate.SQL :
delete
from
product
where
id=?
2023-07-18 14:53:31.407 INFO 91783 --- [ main] p6spy : #1689659611407 | took 0ms | statement | connection 10| url jdbc:mysql://localhost:3306/food_finder?serverTimezone=UTC&characterEncoding=UTF-8
delete from product where id=?
delete from product where id=2;
2023-07-18 14:53:31.407 DEBUG 91783 --- [ main] org.hibernate.SQL :
delete
from
product
where
id=?
2023-07-18 14:53:31.408 INFO 91783 --- [ main] p6spy : #1689659611408 | took 0ms | statement | connection 10| url jdbc:mysql://localhost:3306/food_finder?serverTimezone=UTC&characterEncoding=UTF-8
delete from product where id=?
delete from product where id=3;
2023-07-18 14:53:31.408 INFO 91783 --- [ main] p6spy : #1689659611408 | took 0ms | commit | connection 10| url jdbc:mysql://localhost:3306/food_finder?serverTimezone=UTC&characterEncoding=UTF-8
우선 에러가 발생하지 않을 수 있던 이유는 delete하기 전에 select 문을 통해서 Order와 OrderProduct를 조회하고 OrderProduct를 먼저 지워주는 것을 알 수 있다.
@Entity @Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "orders")
public class Order extends BaseDate {
@OneToMany(mappedBy = "order" , cascade = CascadeType.ALL)
private List<OrderProduct> orderProducts = new ArrayList<>();
...
}
cascade 옵션이 들어가 있기 때문이다..!
- (사실,, 눈치 빠르신 분은 알겠지만 Product에서는 OrderProduct에 대해서 몰라도 되기 때문에 OrderProduct ➡️ Product단방향으로 되어 있어 제일 먼저 ProductRepository에서 deleteAll()을 호출하면 deleteAllInBatch()와 동일하게 에러가 발생한다.)
그렇다면 왜 자꾸 Select문이 호출 될 수 있었던 것일까..?
기본적인 JPA에서의 CRUD작업은 SimpleJpaRepository에서 구현되는거 같은데 @Transactional이 붙어 있는것을 알 수 있다. 내가 service로직에서 @Transactional을 안붙였는데 query가 찍힐 수 있던 이유..!
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
...
}
여기에서 deleteAll과 deleteAllInBatch을 확인해보자.
deleteAll
@Override
@Transactional
public void deleteAll() {
for (T element : findAll()) {
delete(element);
}
}
deleteAll은 findAll()을 호출하기 때문에 Where문이 없는 해당 Entity에 Select문의 query가 나온 것이다.
deleteAllInBatch
@Override
@Transactional
public void deleteAllInBatch() {
em.createQuery(getDeleteAllQueryString()).executeUpdate();
}
getDeleteAllQueryString()을 타고 들어가다 보면...!
public static final String DELETE_ALL_QUERY_STRING = "delete from %s x";
Where문이 없는 Delete 문으로 벌크성으로 전체 테이블의 행이 지워지는 것을 알 수 있다.
생각...생각...
참조 무결성 제약조건에 대해서는 알고 있어야 되는 부분이고 어떻게 되었든 만약 Product를 먼저 지웠다면 에러는 동일하게 나올것이다. 굳이... 테스트 하면서 select 쿼리를 저렇게 네트워크를 타게 해야되는 것인가..? 지금의 상황이라고 생각하면 DeleteAllInBatch를 사용하는것이 더 바람직한 것 같다.
물론 OrderProductRepository를 이용해서 지우지 않고 OrderRepository만 이용해서 사용한다면 DeleteAll도 사용할 수 있겠다.
즉 상황에 따라서 잘 이해하고 사용하는게 중요하다.
@Transactional
SimpleJpaRepository를 생각하면서 내로직에 대한 @Transactional만 인지하면 Test코드에서는 RollBack으로 사용할 수 있으니 평상시에 test코드 작성할 때 좋을 것이다.
- 예를 들어 update query를 발생시키는 코드가 test코드에 있다고 할 때 service단에 transactional이 없다면 변경감지가 안 일어날 수 있는 것이고 알고 사용하는게 중요한거 같다.
추가적으로 springBatch를 사용한 Batch통합 테스트에서 여러 트랜잭션 경계가 참여하기 때문에(트랜잭션이 여러개 걸리기 때문에) 트랜잭션 rollback을 사용하기 어려워서 이때는 deleteAllInBatch로 수동으로 지워야 된다고 한다.
CI/CD공부를 하면서 안전한 코드가 배포가 되려면 test code를 좀 잘 작성해야 되겠고 추가적으로 빠르게 복구 하려면 어느정도 최적화해서 테스트를 작성하는게 좋을거 같아 박우빈 개발자님의 강의를 봤는데 정말 좋은 강의 인거 같다.
'개발 > Spring Boot' 카테고리의 다른 글
회원 탈퇴 로직 Spring Event로 처리하기 (0) | 2024.04.06 |
---|---|
[Spring] 스레드 풀 (1) | 2024.01.29 |
토큰 재발급 로직을 테스트하면서 발생한 문제 (2) | 2024.01.18 |
UnknownEntityException 문제.. (0) | 2023.06.22 |
OAuth 2.0 GitHub 이메일 null이슈 (0) | 2023.04.08 |