Skip to content

Commit

Permalink
Merge pull request #156 from PawWithU/feat/155-db-unique-constraint
Browse files Browse the repository at this point in the history
[Feature] 동시성 이슈 해결을 위한 DB Unique 조건 적용
  • Loading branch information
kyeong-hyeok committed Mar 14, 2024
2 parents b2dd88c + 50f245d commit 8ba5d32
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Entity
@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"post_id"}))
public class Application extends BaseTimeEntity {

@Id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ public interface ApplicationRepository extends JpaRepository<Application, Long>
Boolean existsByPostIdAndVolunteerId(Long postId, Long volunteerId);
Optional<Application> findByIdAndVolunteerId(Long id, Long volunteerId);
Optional<Application> findByIdAndIntermediaryId(Long id, Long intermediaryId);
Long countAllByPostId(Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,20 @@
import com.pawwithu.connectdog.domain.intermediary.repository.IntermediaryRepository;
import com.pawwithu.connectdog.domain.post.entity.Post;
import com.pawwithu.connectdog.domain.post.entity.PostStatus;
import com.pawwithu.connectdog.domain.post.repository.CustomPostRepository;
import com.pawwithu.connectdog.domain.post.repository.PostRepository;
import com.pawwithu.connectdog.domain.review.repository.ReviewRepository;
import com.pawwithu.connectdog.domain.volunteer.entity.Volunteer;
import com.pawwithu.connectdog.domain.volunteer.repository.VolunteerRepository;
import com.pawwithu.connectdog.error.exception.custom.BadRequestException;
import jakarta.persistence.LockModeType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -39,24 +45,22 @@ public class ApplicationService {
private final IntermediaryRepository intermediaryRepository;

public void volunteerApply(String email, Long postId, VolunteerApplyRequest request) {
// 이동봉사자
Volunteer volunteer = volunteerRepository.findByEmail(email).orElseThrow(() -> new BadRequestException(VOLUNTEER_NOT_FOUND));
// 공고
Post post = postRepository.findById(postId).orElseThrow(() -> new BadRequestException(POST_NOT_FOUND));
// 이동봉사 중개
Intermediary intermediary = post.getIntermediary();

// 해당 공고에 대한 신청이 이미 존재할 경우
if (applicationRepository.existsByPostId(postId)) {
try {
// 이동봉사자
Volunteer volunteer = volunteerRepository.findByEmail(email).orElseThrow(() -> new BadRequestException(VOLUNTEER_NOT_FOUND));
// 공고
Post post = postRepository.findById(postId).orElseThrow(() -> new BadRequestException(POST_NOT_FOUND));
// 이동봉사 중개
Intermediary intermediary = post.getIntermediary();

// 공고 신청 저장
Application application = request.toEntity(post, intermediary, volunteer);
applicationRepository.save(application);
// 공고 상태 승인 대기 중으로 변경
post.updateStatus(PostStatus.WAITING);
} catch (DataIntegrityViolationException e) {
throw new BadRequestException(ALREADY_EXIST_APPLICATION);
}

// 공고 신청 저장
Application application = request.toEntity(post, intermediary, volunteer);
applicationRepository.save(application);

// 공고 상태 승인 대기 중으로 변경
post.updateStatus(PostStatus.WAITING);
}

@Transactional(readOnly = true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.pawwithu.connectdog.domain.application.service;

import com.pawwithu.connectdog.domain.application.dto.request.VolunteerApplyRequest;
import com.pawwithu.connectdog.domain.application.repository.ApplicationRepository;
import com.pawwithu.connectdog.domain.volunteer.entity.SocialType;
import com.pawwithu.connectdog.domain.volunteer.entity.Volunteer;
import com.pawwithu.connectdog.domain.volunteer.entity.VolunteerRole;
import com.pawwithu.connectdog.domain.volunteer.repository.VolunteerRepository;
import com.pawwithu.connectdog.error.exception.custom.BadRequestException;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@SpringBootTest
class ApplicationServiceTest {

@Autowired
private ApplicationRepository applicationRepository;
@Autowired
private VolunteerRepository volunteerRepository;
@Autowired
private ApplicationService applicationService;

@Test
public void test_concurrency() throws InterruptedException {
// 이동봉사자 정보 설정
Volunteer volunteer1 = new Volunteer("[email protected]", VolunteerRole.AUTH_VOLUNTEER, SocialType.NAVER, "자동차");
Volunteer volunteer2 = new Volunteer("[email protected]", VolunteerRole.AUTH_VOLUNTEER, SocialType.NAVER, "12A");

volunteerRepository.save(volunteer1);
volunteerRepository.save(volunteer2);

int numberOfThreads = 2;
ExecutorService service = Executors.newFixedThreadPool(numberOfThreads);
CountDownLatch latch = new CountDownLatch(numberOfThreads);
VolunteerApplyRequest requestDto1 = new VolunteerApplyRequest("하노정",
"01022223333",
"자동차",
"이동봉사 신청하겠습니다!");
VolunteerApplyRequest requestDto2 = new VolunteerApplyRequest("민경혁",
"01044445555",
"자동차",
"이동봉사 신청하겠습니다!");

service.execute(() -> {
try {
applicationService.volunteerApply("[email protected]", 1L, requestDto1);
} catch (BadRequestException e) {
System.out.println("e.getMessage() = " + e.getMessage());
}
latch.countDown();
});
service.execute(() -> {
try {
applicationService.volunteerApply("[email protected]", 1L, requestDto2);
} catch (BadRequestException e) {
System.out.println("e.getMessage() = " + e.getMessage());
}
latch.countDown();
});
latch.await();
Thread.sleep(1000);

// 결과 확인
Long count = applicationRepository.countAllByPostId(1L);
Assertions.assertThat(count).isEqualTo(1);
}

}

0 comments on commit 8ba5d32

Please sign in to comment.