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

celinaym님의 프로필 이미지
celinaym

작성한 질문수

실전! 스프링 데이터 JPA

test code에서 repository의 값을 읽어오지 못합니다.

작성

·

318

·

수정됨

0

안녕하세요. 강의 잘 듣고 있습니다. tdd를 작성 중 계속 findByStoreId, findByMemberId가 다 되지 않고 있어 그 이유가 궁금해 질문 남깁니다. executorService를 사용하지 않고 orderService.postOrder을 사용하면 findByStoreId, findByMemberId가 정상적으로 실행이 되는데 executorService를 사용하기만 하면 계속 repo에 있는 값을 찾아오지 못하네요,, ㅠ 코드는 아래 첨부해놓았습니다!

public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {

    Optional<Member> findByMemberId(Long id);
}
public class Member extends Timestamped {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long memberId;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String nickname;
    @Column(nullable = false)
    private String password;

    @Column(nullable = false)
    private String address;

    @Column(nullable = false)
    private String role;

    @JsonIgnore
    @OneToMany(fetch = FetchType.LAZY,
            mappedBy = "member",
            cascade = CascadeType.ALL,
            orphanRemoval = true
    )
    private List<Orders> orders = new ArrayList<>();

    @JsonManagedReference
    @OneToMany(fetch = FetchType.LAZY,
            mappedBy = "member",
            cascade = CascadeType.ALL,
            orphanRemoval = true
    )
    private List<Likes> likes = new ArrayList<>();


    @OneToMany(fetch = FetchType.LAZY,
            mappedBy = "member",
            cascade = CascadeType.ALL,
            orphanRemoval = true
    )
    private List<Store> stores = new ArrayList<>();
}
public class Item extends Timestamped{

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long itemId;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private int amount;

    @Column(nullable = false)
    private int price;

    //@Enumerated(EnumType.STRING)
    private String category;

    @JsonIgnore
    @OneToMany(fetch = FetchType.LAZY,
            mappedBy = "item",
            cascade = CascadeType.ALL,
            orphanRemoval = true
    )
    private List<OrderHasItem> orderHasItems = new ArrayList<>();

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="store_id", nullable = true)
    private Store store;

    public Integer changeStock(int amount){
        //case1) this.amount < amount
        if (this.amount < amount){
            throw new CustomException(ErrorCode.NOT_ENOUGH_STOCK);
        } else if (this.amount == 0){
            throw new CustomException(ErrorCode.OUT_OF_STOCK);
        } else{
            this.amount -= amount;  //stock 감소
        }
        return this.amount;
    }
@Transactional(rollbackFor = {CustomException.class})
    public ResponseDto<?> postOrder(OrderRequestDto requestDto, HttpServletRequest request) {

        //case1)case2)token validity check
        tokenProvider.tokenValidationCheck(request);

        //case3)
        memberService.isPresentMember(requestDto.getMemberId());

        //case4)StoreId에 해당하는 가게가 존재하지 않을 때
        if (!storeRepository.existsById(requestDto.getStoreId())) {
            throw new CustomException(ErrorCode.STORE_NOT_FOUND);
        }

        //case5)ItemId에 해당하는 Item이 존재하지 않을 때
        requestDto.getItemId().forEach(itemId -> {
            if (!itemRepository.existsById(itemId)) {
                throw new CustomException(ErrorCode.ITEM_NOT_FOUND);
            }
        });

        //case6)Item 주문 수량이 0일 때
        if (requestDto.getItemId().size() == 0) {
            throw new CustomException(ErrorCode.NEED_OVER_ONE);
        }

        //itemId에 해당하는 내용들을 찾아서 리스트로
        List<Item> itemList = requestDto.getItemId()
                .stream()
                .map(item -> itemRepository.findByItemId(item).orElse(null))    //x-Lock(pessimistic_write)
                .collect(toList());
  
        //item을 OrderHasItems에 넣어두기
        Orders order = Orders.builder()
                .member(memberRepository.findByMemberId(requestDto.getMemberId()).orElse(null)) //s-Lock
                .store(storeRepository.findByStoreId(requestDto.getStoreId()).orElse(null)) //s-Lock
                .build();

        orderRepository.save(order);    //s-Lock

    for (int i=0; i<requestDto.getItemId().size(); i++){
        Item item = itemList.get(i);
        Integer amount = requestDto.getAmount().get(i);
        OrderHasItem item1 = OrderHasItem.builder()
                .orders(order)
                .item(item)
                .amount(amount)
                .build();
        orderHasItemRepository.save(item1); //s-Lock

        //stock update
        item.changeStock(amount);
     
    }
@Transactional
@SpringBootTest
public class ServiceCustom{

    @Autowired
    MemberService memberService;

    @Autowired
    OrderService orderService;

    @Autowired
    LikeService likeService;

    @Autowired
    SearchService searchService;

    @Autowired
    StoreService storeService;

    @Autowired
    MemberRepository memberRepository;

    @Autowired
    OrderRepository orderRepository;

    @Autowired
    OrderHasItemRepository orderHasItemRepository;

    @Autowired
    LikeRepository likeRepository;

    @Autowired
    ItemRepository itemRepository;

    @Autowired
    StoreRepository storeRepository;

    @Autowired
    RefreshTokenRepository refreshTokenRepository;

    @Autowired
    TokenProvider tokenProvider;

    @BeforeEach
    public void beforeEach() {
        //FK check 후 삭제
        orderHasItemRepository.deleteAll();
        likeRepository.deleteAll();
        storeRepository.deleteAll();
        itemRepository.deleteAll();
        orderRepository.deleteAll();
        refreshTokenRepository.deleteAll();
        memberRepository.deleteAll();
    }

    @AfterEach
    public void afterEach() {
        //FK check 후 삭제
        orderHasItemRepository.deleteAll();
        likeRepository.deleteAll();
        storeRepository.deleteAll();
        itemRepository.deleteAll();
        refreshTokenRepository.deleteAll();
        orderRepository.deleteAll();
        memberRepository.deleteAll();
    }

    //create Entity
    protected Store createStore(String name, String address, String category, Long memberId) {

        Store store = Store.builder()
                .name(name)
                .address(address)
                .category(category)
                .member(memberRepository.findByMemberId(memberId).get())
                .build();
        storeRepository.save(store);
        return store;
    }

    protected Item createItem(String name, int amount, int price, String category, Long storeId) {
            Item item = Item.builder()
                    .name(name)
                    .amount(amount)
                    .store(storeRepository.findByStoreId(storeId).get())
                    .price(price)
                    .category(category)
                    .build();
            itemRepository.save(item);
        return item;

        }

    protected Member createMember(String name, String nickname, String password, String address, String role) {
        Member member = Member.builder()
                .name(name)
                .nickname(nickname)
                .password(password)
                .address(address)
                .role(role)
                .build();
        memberRepository.save(member);
        return member;
    }

    //create Dto
    protected MemberRequestDto createMemberRequestDto(String name, String nickname, String password, String address, String role) {

        return MemberRequestDto.builder()
                .name(name)
                .nickname(nickname)
                .password(password)
                .address(address)
                .role(role)
                .build();
    }

    protected OrderRequestDto createOrderRequestDto(Long storeId, Long item1, Integer amount1, Long memberId) {

        List<Long> itemId = new ArrayList<>();
        itemId.add(item1);

        List<Integer> amount = new ArrayList<>();
        amount.add(amount1);

        return OrderRequestDto.builder()
                .storeId(storeId)
                .itemId(itemId)
                .amount(amount)
                .memberId(memberId)
                .build();
    }
@Test
    @DisplayName("주문 요청 동시에 진행할 시 동시성 issue 확인")
    public void 주문_요청_동시성_문제_테스트() {

        //given
        Member member = memberRepository.save(createMember("member1", "member1", "1234", "address1", "CONSUMER"));
        Store store = storeRepository.save(createStore( "store1","address1","category1",member.getMemberId()));
        Item item = itemRepository.save(createItem( "item1", 1000, 8000,"category1",store.getStoreId()));

        OrderRequestDto order = createOrderRequestDto(store.getStoreId(), item.getItemId(), 1, member.getMemberId());

        MockHttpServletRequest request = new MockHttpServletRequest();
        request.addHeader("Authorization", "Bearer " + "1234567890");
        request.addHeader("Refresh-Token", "1234567890");
        int amountBefore = itemRepository.findAmountByItemId(item.getItemId());

        //when
        int threadCount = 2;
        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);    //concurrent : 2
        CountDownLatch latch = new CountDownLatch(threadCount);

        for (int i=0; i<2; i++){
            executorService.submit(() -> {
                try{
                    orderService.postOrder(order, request);
                }catch (Exception e) {
                    log.info("exception : {}", e.getMessage());
                }finally{
                    latch.countDown();
                }
            });
        }

        //then
        int amountAfter = itemRepository.findAmountByItemId(item.getItemId());
        assertThat(amountAfter-amountBefore).isNotEqualTo(2);

    }

답변 2

0

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. celinaym님

spring 비동기 트랜잭션 동기화를 검색해보시면 도움이 되실꺼에요.

감사합니다.

0

celinaym님의 프로필 이미지
celinaym
질문자

test 통과를 못한 이유는 test code에 있는 catch(Exception)에서 계속 postOrder에 있는 memberService.isPresentMember(requestDto.getMemberId())을 통과하지 못합니다. isPresentMember은 repository에 있는 findByMemberId(Long id)이고요!(실제 코드는 orderService.postOrderWithPessimisticWrite가 아닌 orderService.postOrder로 정상적으로 되어 있습니다.)

image

celinaym님의 프로필 이미지
celinaym

작성한 질문수

질문하기