From 784aab0dd9149d30973a9af79bebdf66549bef91 Mon Sep 17 00:00:00 2001 From: Subin Date: Sun, 9 Mar 2025 10:39:09 +0900 Subject: [PATCH 1/2] =?UTF-8?q?:recycle:=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EB=AC=B4=EA=B2=B0=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sejongpeer/domain/study/entity/Study.java | 17 +--- .../studyrelation/entity/StudyRelation.java | 3 + .../service/StudyRelationService.java | 88 +++++++++++-------- .../global/error/exception/ErrorCode.java | 1 + 4 files changed, 60 insertions(+), 49 deletions(-) diff --git a/src/main/java/com/sejong/sejongpeer/domain/study/entity/Study.java b/src/main/java/com/sejong/sejongpeer/domain/study/entity/Study.java index 64159af..3f6807a 100644 --- a/src/main/java/com/sejong/sejongpeer/domain/study/entity/Study.java +++ b/src/main/java/com/sejong/sejongpeer/domain/study/entity/Study.java @@ -5,12 +5,12 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import com.sejong.sejongpeer.domain.image.entity.Image; import com.sejong.sejongpeer.domain.study.entity.type.Frequency; import com.sejong.sejongpeer.domain.study.entity.type.StudyMethod; import com.sejong.sejongpeer.domain.studyrelation.entity.StudyRelation; +import jakarta.persistence.*; import org.hibernate.annotations.Comment; import com.sejong.sejongpeer.domain.common.BaseAuditEntity; @@ -23,18 +23,6 @@ import com.sejong.sejongpeer.global.error.exception.CustomException; import com.sejong.sejongpeer.global.error.exception.ErrorCode; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -117,6 +105,9 @@ public class Study extends BaseAuditEntity { @OneToMany(mappedBy = "study", cascade = CascadeType.ALL, orphanRemoval = true) private List images = new ArrayList<>(); + @Version + private Long version; + @Builder(access = AccessLevel.PRIVATE) private Study( String title, diff --git a/src/main/java/com/sejong/sejongpeer/domain/studyrelation/entity/StudyRelation.java b/src/main/java/com/sejong/sejongpeer/domain/studyrelation/entity/StudyRelation.java index 7bb141a..56a264c 100644 --- a/src/main/java/com/sejong/sejongpeer/domain/studyrelation/entity/StudyRelation.java +++ b/src/main/java/com/sejong/sejongpeer/domain/studyrelation/entity/StudyRelation.java @@ -39,6 +39,9 @@ public class StudyRelation extends BaseAuditEntity { @Enumerated(EnumType.STRING) private StudyMatchingStatus status; + @Version + private Integer version; + @Builder(access = AccessLevel.PRIVATE) private StudyRelation(Member member, Study study, StudyMatchingStatus status) { this.member = member; diff --git a/src/main/java/com/sejong/sejongpeer/domain/studyrelation/service/StudyRelationService.java b/src/main/java/com/sejong/sejongpeer/domain/studyrelation/service/StudyRelationService.java index d6671a6..4eb6c28 100644 --- a/src/main/java/com/sejong/sejongpeer/domain/studyrelation/service/StudyRelationService.java +++ b/src/main/java/com/sejong/sejongpeer/domain/studyrelation/service/StudyRelationService.java @@ -13,6 +13,8 @@ import com.sejong.sejongpeer.domain.study.entity.type.StudyType; import com.sejong.sejongpeer.domain.studyrelation.dto.request.StudyMatchingRequest; import com.sejong.sejongpeer.global.util.SecurityUtil; +import jakarta.persistence.OptimisticLockException; +import org.springframework.orm.ObjectOptimisticLockingFailureException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -84,60 +86,74 @@ private void sendStudyApplicantAlarmToStudyWriter(StudyRelation newStudyApplicai smsService.sendSms(newStudyApplicaitonHistory.getStudy().getMember().getPhoneNumber(), SmsText.STUDY_APPLY_ALARM); } + @Transactional public void deleteStudyApplicationHistory(final Long studyId) { final String loginMemberId = securityUtil.getCurrentMemberId(); - StudyRelation studyApplicationHistory = studyRelationRepository.findTopByMemberIdAndStudyIdOrderByIdDesc(loginMemberId, studyId) - .orElseThrow(() -> new CustomException(ErrorCode.STUDY_RELATION_NOT_FOUND)); - - studyApplicationHistory.registerCanceledAt(LocalDateTime.now()); - studyApplicationHistory.changeStudyMatchingStatus(StudyMatchingStatus.CANCEL); - studyRelationRepository.save(studyApplicationHistory); - + for (int attempt = 0; attempt < 3; attempt++) { + try { + StudyRelation studyApplicationHistory = studyRelationRepository.findTopByMemberIdAndStudyIdOrderByIdDesc(loginMemberId, studyId) + .orElseThrow(() -> new CustomException(ErrorCode.STUDY_RELATION_NOT_FOUND)); + + studyApplicationHistory.registerCanceledAt(LocalDateTime.now()); + studyApplicationHistory.changeStudyMatchingStatus(StudyMatchingStatus.CANCEL); + studyRelationRepository.save(studyApplicationHistory); + return; + } catch (OptimisticLockException e) { + if (attempt == 2) { + throw new CustomException(ErrorCode.CONCURRENT_UPDATE_CONFLICT); + } + } + } } + @Transactional public Map updateStudyMatchingStatus(StudyMatchingRequest request) { - Member studyApplicant = memberRepository.findByNickname(request.applicantNickname()) - .orElseThrow(() -> new CustomException(ErrorCode.NICKNAME_IS_NULL)); - - StudyRelation studyResume = studyRelationRepository.findTopByMemberIdAndStudyIdOrderByIdDesc(studyApplicant.getId(), request.studyId()) - .orElseThrow(() -> new CustomException(ErrorCode.STUDY_APPLY_HISTORY_NOT_FOUND)); - - boolean isFulleApplication = false; + boolean isFullApplication = false; Map response = new HashMap<>(); - if (studyResume.getStatus().equals(StudyMatchingStatus.CANCEL)) { - throw new CustomException(ErrorCode.INVALID_STUDY_MATHCING_STATUS_UPDATE_CONDITION); - } + try { + Member studyApplicant = memberRepository.findByNickname(request.applicantNickname()) + .orElseThrow(() -> new CustomException(ErrorCode.NICKNAME_IS_NULL)); + + StudyRelation studyResume = studyRelationRepository.findTopByMemberIdAndStudyIdOrderByIdDesc(studyApplicant.getId(), request.studyId()) + .orElseThrow(() -> new CustomException(ErrorCode.STUDY_APPLY_HISTORY_NOT_FOUND)); - Study appliedStudy = studyResume.getStudy(); - if (request.isAccept()) { - if (appliedStudy.getRecruitmentCount() <= appliedStudy.getParticipantsCount()) { - throw new CustomException(ErrorCode.STUDY_APPLICANT_CANNOT_BE_ACCEPTED); + if (studyResume.getStatus().equals(StudyMatchingStatus.CANCEL)) { + throw new CustomException(ErrorCode.INVALID_STUDY_MATHCING_STATUS_UPDATE_CONDITION); } - studyResume.changeStudyMatchingStatus(StudyMatchingStatus.ACCEPT); - appliedStudy.addParticipantsCount(); - studyRepository.save(appliedStudy); + Study appliedStudy = studyResume.getStudy(); - if (appliedStudy.getRecruitmentCount() <= appliedStudy.getParticipantsCount()) { - appliedStudy.changeStudyRecruitmentStatus(RecruitmentStatus.CLOSED); + if (request.isAccept()) { + if (appliedStudy.getRecruitmentCount() <= appliedStudy.getParticipantsCount()) { + throw new CustomException(ErrorCode.STUDY_APPLICANT_CANNOT_BE_ACCEPTED); + } + + studyResume.changeStudyMatchingStatus(StudyMatchingStatus.ACCEPT); + appliedStudy.addParticipantsCount(); studyRepository.save(appliedStudy); - List appliedStudyHistory = studyRelationRepository.findAllByStudyAndStatus(appliedStudy, StudyMatchingStatus.ACCEPT); - appliedStudyHistory.forEach(this::sendStudyKakaoLink); + if (appliedStudy.getRecruitmentCount() <= appliedStudy.getParticipantsCount()) { + appliedStudy.changeStudyRecruitmentStatus(RecruitmentStatus.CLOSED); + studyRepository.save(appliedStudy); - isFulleApplication = true; - } - } else { - studyResume.changeStudyMatchingStatus(StudyMatchingStatus.REJECT); - sendStudyRejectAlarmToStudyApplicant(studyResume); - } + List appliedStudyHistory = studyRelationRepository.findAllByStudyAndStatus(appliedStudy, StudyMatchingStatus.ACCEPT); + appliedStudyHistory.forEach(this::sendStudyKakaoLink); - studyRelationRepository.save(studyResume); - response.put("isFull", isFulleApplication); + isFullApplication = true; + } + } else { + studyResume.changeStudyMatchingStatus(StudyMatchingStatus.REJECT); + sendStudyRejectAlarmToStudyApplicant(studyResume); + } + studyRelationRepository.save(studyResume); + response.put("isFull", isFullApplication); + } catch (ObjectOptimisticLockingFailureException | OptimisticLockException e) { + throw new CustomException(ErrorCode.CONCURRENT_UPDATE_CONFLICT); + } return response; } diff --git a/src/main/java/com/sejong/sejongpeer/global/error/exception/ErrorCode.java b/src/main/java/com/sejong/sejongpeer/global/error/exception/ErrorCode.java index fbe8e67..dbd0969 100644 --- a/src/main/java/com/sejong/sejongpeer/global/error/exception/ErrorCode.java +++ b/src/main/java/com/sejong/sejongpeer/global/error/exception/ErrorCode.java @@ -73,6 +73,7 @@ public enum ErrorCode { CANNOT_REAPPLY_WITHIN_AN_HOUR(HttpStatus.FORBIDDEN, "스터디 게시글을 지원했다가 취소한 유저는 일정시간 동안 같은 스터디를 지원할 수 없습니다."), INVALID_STUDY_MATHCING_STATUS_UPDATE_CONDITION(HttpStatus.BAD_REQUEST, "지원을 취소한 경우 수락/거절 처리를 할 수 없습니다."), STUDY_APPLICANT_CANNOT_BE_ACCEPTED(HttpStatus.BAD_REQUEST, "해당 스터디 게시글의 모집 인원까지만 지원자를 수락할 수 있습니다."), + CONCURRENT_UPDATE_CONFLICT(HttpStatus.CONFLICT, "이미 사용자의 지원 상태가 변경되었습니다."), // Lecture LECTURE_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 강의를 찾을 수 없습니다."), From 4f42b056897d0e89b0ef305eea6b60cc11d6460e Mon Sep 17 00:00:00 2001 From: Subin Date: Mon, 10 Mar 2025 22:02:26 +0900 Subject: [PATCH 2/2] =?UTF-8?q?:recycle:=20=EB=82=B4=EA=B0=80=20=EB=A7=8C?= =?UTF-8?q?=EB=93=A0=20=EC=8A=A4=ED=84=B0=EB=94=94=20=EC=A7=80=EC=9B=90?= =?UTF-8?q?=EC=9E=90=20=EC=A1=B0=ED=9A=8C=20=EC=BD=94=EB=93=9C=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/StudyRelationService.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/sejong/sejongpeer/domain/studyrelation/service/StudyRelationService.java b/src/main/java/com/sejong/sejongpeer/domain/studyrelation/service/StudyRelationService.java index 4eb6c28..f180264 100644 --- a/src/main/java/com/sejong/sejongpeer/domain/studyrelation/service/StudyRelationService.java +++ b/src/main/java/com/sejong/sejongpeer/domain/studyrelation/service/StudyRelationService.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import com.sejong.sejongpeer.domain.member.repository.MemberRepository; import com.sejong.sejongpeer.domain.scrap.application.ScrapService; @@ -214,20 +215,19 @@ public List getAppliedStudies() { studyRelations.sort((sr1, sr2) -> sr2.getStudy().getId().compareTo(sr1.getStudy().getId())); - List list = new ArrayList<>(); - studyRelations.stream() - .forEach(studyRelation -> { - if(!studyRelation.getStatus().equals(StudyMatchingStatus.CANCEL)) { - Study study = studyRelation.getStudy(); + return studyRelations.stream() + .filter(studyRelation -> !studyRelation.getStatus().equals(StudyMatchingStatus.CANCEL)) + .map(studyRelation -> { + Study study = studyRelation.getStudy(); - Long scrapCount = scrapService.getScrapCountByStudyPost(study.getId()); - List tags = tagService.getTagsNameByStudy(study); - boolean hasMemberScrappedStudy = scrapService.hasMemberScrappedStudy(loginMember, study); + Long scrapCount = scrapService.getScrapCountByStudyPost(study.getId()); + List tags = tagService.getTagsNameByStudy(study); + boolean hasMemberScrappedStudy = scrapService.hasMemberScrappedStudy(loginMember, study); - list.add(AppliedStudyResponse.of(study, tags, scrapCount, hasMemberScrappedStudy)); - } - }); - return list; + return AppliedStudyResponse.of(study, tags, scrapCount, hasMemberScrappedStudy); + + }) + .collect(Collectors.toList()); } public Map> getApplicatnsList() {