인프런 커뮤니티 질문&답변

opensesame님의 프로필 이미지
opensesame

작성한 질문수

재고시스템으로 알아보는 동시성이슈 해결방법

서비스 로직에서 saveAndFlush 질문입니다.

작성

·

2.4K

0

안녕하세요. 먼저 알차고 좋은 강의 만들어 주셔서

감사하단 말씀을 드리고 싶습니다 ㅎㅎ

강의를 보다보니 서비스 로직에서 saveAndFlush 를 해주고 있는데 @Transactional 어노테이션이 있어서 디비에 반영이 잘 될거 같은데 saveAndFlush 를 해주는 이유가 따로 있을까요?

그리고 저는 보통 save 를 사용했는데 saveAndFlush 를 사용하는 다른 특별한 이유가 있는지 궁금합니다.

답변 2

3

최상용님의 프로필 이미지
최상용
지식공유자

opensesame 님 안녕하세요.

일단 saveAndFlush 를 사용한이유는 예제를 간단하게 작성하기 위해서 사용하였습니다.

save 메소드를 사용하게 된다면 데이터베이스에 바로 flush 가 되는것이 아니기때문에 synchronized 를 이용한 방법을 테스트할 때 오류가 날것입니다.

그 이유는 @Transactional 의 동작방식때문에 그렇습니다. (DB 에 값이 입력되기전에 다른스레드가 메소드에 접근이 가능해집니다)

SynchronizedFacade 를 만들어서 한번 더 래핑해준다면 save 메소드를 사용하는편이 더 좋을것 같습니다.

강의를 들어주셔서 감사합니다.

혹시 다른질문이 있으시다면 말씀 부탁드리겠습니다!!

 

save 와 saveAndFlush 의 차이점은 아래와 같습니다 :)

https://www.baeldung.com/spring-data-jpa-save-saveandflush

 

SynchronizdeFacade를 만들어서 래핑해주면 save 메소드를 사용하는것이 이해가 안갑니다. 혹시 설명이 가능할까요? 효율성의 측면에서 save가 좋아서 save를 이용하려고 하는데 말씀하신것처럼 해당 메소드를 사용하기 위해서 SynchronizdeFacade이 필요한데 해당 내용에 대해 이해가 안가서 댓글을 남깁니다.

최상용님의 프로필 이미지
최상용
지식공유자

하지홍님 안녕하세요.
제가 댓글에서 말한 것처럼 save 메소드를 사용하게 된다면 데이터베이스에 바로 데이터를 업데이트 하는게 아니기 때문에 DB 에 값이 업데이트 되기전에 다른 스레드가 메소드에 접근이 가능해집니다.
A 스레드가 수량을 99개로 업데이트를 완료하기 이전에 B 스레드가 수량이 100개인 데이터를 읽을 수 있다는 뜻입니다.
이로인해서 레이스컨디션이 발생하게 됩니다.

감사합니다.

답변 매우 감사합니다 :)

마지막으로 질문이 있습니다. 멀티스레드 환경에서 잘 작동할 수 있도록 과정과 해결방안을 설명해주셨는데 예로서 낙관적 락을 적용하여 갱신손실 문제를 해결해주는데 테스트코드에서는 Executors를 사용해서 멀티스레드 환경을 만들어 주었습니다. 그러면 테스트코드가 아니라 실제 비즈니스 로직(서비스단)에서 멀티스레드 환경을 따로 만들어 주어야 하는 건가요? 예로서 Executors를 사용하거나 SpringAsyncConfig을 만들어서 해결해야 하나요? 강의 내에서는 테스트 코드에서만 멀티스레드환경만 구축된 것인가 궁금해서 적습니다.

예시)

@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;
    private final ItemRepository itemRepository;
    private final MemberService memberService;


    @Transactional
    public Item decreaseStock(Long id, int quantity) {
        Item item = itemRepository.findByIdWithOptimisticLock(id).orElseThrow();
        item.decrease(quantity);
        return item;
    }



    @Transactional
    @Async("threadPoolTaskExecutor")
    public Order createOrder(UserDetails userDetails, OrderRequest orderRequest) {
        Member member = memberService.findByEmail(userDetails.getUsername())
                .orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND));
        Item item = decreaseStock(orderRequest.getItemId(), orderRequest.getItemNumber());
        OrderItem orderItem = OrderItem.createOrderItem(item, item.getPrice(), orderRequest.getItemNumber());

        Order order = Order.createOrder(member, orderItem);
        orderRepository.save(order);
        return order;
    }
}
@Configuration
public class SpringAsyncConfig {

    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(10);
        taskExecutor.setMaxPoolSize(100);
        taskExecutor.setQueueCapacity(1000);
        taskExecutor.setThreadNamePrefix("Executor-");
        taskExecutor.initialize();
        return taskExecutor;
    }
}
최상용님의 프로필 이미지
최상용
지식공유자

테스트코드에서는 여러 사용자가 동시에 요청을 보내는것을 가정하기 위하여 멀티스레드를 사용한 것 입니다.
따라서, 비즈니스 로직에서는 별도의 스레드풀을 사용하지 않아도 됩니다.

1

최상용님의 프로필 이미지
최상용
지식공유자

해당 답변에 잘못 전달해드린 부분이 있어서 정정하고자 댓글을 답니다.

save, saveAndFlush 무엇을 사용하던 synchronized 메소드위에 @Transactional 이 붙어있다면 에러가 발생합니다.
그 이유는 @Transactional 의 동작방식때문에 그렇습니다. (DB 에 값이 입력되기전에 다른스레드가 메소드에 접근이 가능해집니다)

해당 강의에서 전달하고자 했던 것은 "Transactional 의 동작방식과 synchronized 동작방식 나아가 1개의 스레드만 접근이 가능해야하고, 그 스레드가 데이터베이스의 업데이트까지 하고난 후 다른스레드가 접근이 가능하게 해야한다" 입니다.

혼란을 드려서 죄송합니다.

2024.03.11 업데이트

opensesame님의 프로필 이미지
opensesame

작성한 질문수

질문하기