Skip to content

Commit

Permalink
Merge pull request #97 from study-hub-inu/feat/concurrency-redisson
Browse files Browse the repository at this point in the history
Feat/concurrency redisson
  • Loading branch information
wellbeing-dough authored Jul 14, 2024
2 parents c8ed4ed + 610c520 commit 13ba63f
Show file tree
Hide file tree
Showing 12 changed files with 240 additions and 15 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-mail'
// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.redisson:redisson-spring-boot-starter:3.17.4'

//s3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@

@RequiredArgsConstructor
@Service
@Transactional(readOnly = true)
public class ApplyService {

private final UserRepository userRepository;
Expand Down Expand Up @@ -110,7 +109,6 @@ public void rejectApply(final RejectApplyRequest rejectApplyRequest, final UserI
rejectRepository.save(rejectApplyRequest.toRejectEntity());
}

@Transactional
public void acceptApply(AcceptApplyRequest acceptApplyRequest, UserId userId) {
userRepository.findById(userId.getId()).orElseThrow(UserNotFoundException::new);
StudyEntity study = studyRepository.findById(acceptApplyRequest.getStudyId()).orElseThrow(StudyNotFoundException::new);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ public enum StatusType {
SORT_TYPE_NOT_FOUND(404, "SORT_TYPE_NOT_FOUND"),
MAJOR_TYPE_NOT_FOUND(404, "MAJOR_TYPE_NOT_FOUND"),
NO_REMAINING_SEAT(409, "NO_REMAINING_SEAT"),
STUDY_POST_CLOSED(409, "STUDY_POST_CLOSED");
STUDY_APPLY_LOCK_ACQUISITION(423, "STUDY_APPLY_LOCK_ACQUISITION"),
STUDY_POST_CLOSED(409, "STUDY_POST_CLOSED"),
REMAINING_SEATS_OVER_STUDY_PERSON(409, "REMAINING_SEATS_OVER_STUDY_PERSON");

private final int statusCode;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package kr.co.studyhubinu.studyhubserver.exception.apply;

import kr.co.studyhubinu.studyhubserver.exception.StatusType;
import kr.co.studyhubinu.studyhubserver.exception.common.CustomException;

public class StudyApplyLockAcquisitionException extends CustomException {

private final StatusType status;
private static final String message = "스터디 지원에 대한 락 획득에 실패했습니다.";

public StudyApplyLockAcquisitionException() {
super(message);
this.status = StatusType.STUDY_APPLY_LOCK_ACQUISITION;
}

@Override
public StatusType getStatus() {
return status;
}

@Override
public String getMessage() {
return message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package kr.co.studyhubinu.studyhubserver.exception.study;

import kr.co.studyhubinu.studyhubserver.exception.StatusType;
import kr.co.studyhubinu.studyhubserver.exception.common.CustomException;

public class PostRemainingSeatOverStudyPerson extends CustomException {

private final StatusType status;
private static final String message = "잔여석은 0개가 될 수 없습니다";

public PostRemainingSeatOverStudyPerson() {
super(message);
this.status = StatusType.REMAINING_SEATS_OVER_STUDY_PERSON;
}

@Override
public StatusType getStatus() {
return status;
}

@Override
public String getMessage() {
return message;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public StudyPostEntity(Long id, String title, String content, String chatUrl, Ma
this.studyId = studyId;
}

public void update(UpdateStudyPostInfo info) {
public void update(UpdateStudyPostInfo info, int currentJoinCount) {
this.title = info.getTitle();
this.content = info.getContent();
this.chatUrl = info.getChatUrl();
Expand All @@ -103,6 +103,7 @@ public void update(UpdateStudyPostInfo info) {
this.close = info.isClose();
this.studyStartDate = info.getStudyStartDate();
this.studyEndDate = info.getStudyEndDate();
this.remainingSeat = info.getStudyPerson() - currentJoinCount;
}

public boolean isPostOfUser(Long userId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,51 @@
package kr.co.studyhubinu.studyhubserver.studypost.domain.implementations;

import kr.co.studyhubinu.studyhubserver.exception.study.PostNotFoundException;
import kr.co.studyhubinu.studyhubserver.common.timer.Timer;
import kr.co.studyhubinu.studyhubserver.exception.apply.StudyApplyLockAcquisitionException;
import kr.co.studyhubinu.studyhubserver.studypost.domain.StudyPostEntity;
import kr.co.studyhubinu.studyhubserver.studypost.repository.StudyPostRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.TimeUnit;

@Component
@RequiredArgsConstructor
@Slf4j
public class StudyPostApplyEventPublisher {

private final StudyPostRepository studyPostRepository;
private final RedissonClient redissonClient;
private final StudyPostWriter studyPostWriter;
private final StudyPostReader studyPostReader;

@Transactional
public void acceptApplyEventPublish(Long studyId) {
StudyPostEntity studyPost = studyPostRepository.findByIdWithPessimisticLock(studyId).orElseThrow(PostNotFoundException::new);
studyPost.decreaseRemainingSeat();
studyPost.closeStudyPostIfRemainingSeatIsZero();
studyPostRepository.save(studyPost);
StudyPostEntity studyPost = studyPostReader.readByStudyId(studyId);
RLock lock = redissonClient.getLock(studyPost.getId().toString());
boolean available = false;
try {
available = lock.tryLock(10, 1, TimeUnit.SECONDS);
if (!available) {
throw new StudyApplyLockAcquisitionException();
}
studyPostWriter.updateStudyPostApply(studyPost.getId());
} catch (InterruptedException e) {
throw new StudyApplyLockAcquisitionException();
} finally {
if (available) {
lock.unlock();
}
}

}
// @Transactional
// public void acceptApplyEventPublish(Long studyId) {
// StudyPostEntity studyPost = studyPostRepository.findByIdWithPessimisticLock(studyId).orElseThrow(PostNotFoundException::new);
// studyPost.decreaseRemainingSeat();
// studyPost.closeStudyPostIfRemainingSeatIsZero();
// studyPostRepository.save(studyPost);
// }


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package kr.co.studyhubinu.studyhubserver.studypost.domain.implementations;

import kr.co.studyhubinu.studyhubserver.exception.study.PostNotFoundException;
import kr.co.studyhubinu.studyhubserver.studypost.domain.StudyPostEntity;
import kr.co.studyhubinu.studyhubserver.studypost.repository.StudyPostRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
@RequiredArgsConstructor
@Transactional(readOnly = true)
@Slf4j
public class StudyPostReader {

private final StudyPostRepository studyPostRepository;

public StudyPostEntity readByStudyId(Long studyId) {
return studyPostRepository.findByStudyId(studyId).orElseThrow(PostNotFoundException::new);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package kr.co.studyhubinu.studyhubserver.studypost.domain.implementations;

import kr.co.studyhubinu.studyhubserver.exception.study.PostNotFoundException;
import kr.co.studyhubinu.studyhubserver.studypost.domain.StudyPostEntity;
import kr.co.studyhubinu.studyhubserver.studypost.repository.StudyPostRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;


@Component
@RequiredArgsConstructor
@Slf4j
public class StudyPostWriter {

private final StudyPostRepository studyPostRepository;
private final StudyPostReader studyPostReader;

@Transactional
public void updateStudyPostApply(Long studyPostId) {
StudyPostEntity studyPost = studyPostRepository.findById(studyPostId).orElseThrow(PostNotFoundException::new);
studyPost.decreaseRemainingSeat();
studyPost.closeStudyPostIfRemainingSeatIsZero();
studyPostRepository.saveAndFlush(studyPost);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import kr.co.studyhubinu.studyhubserver.exception.study.PostEndDateConflictException;
import kr.co.studyhubinu.studyhubserver.exception.study.PostNotFoundException;
import kr.co.studyhubinu.studyhubserver.exception.study.PostRemainingSeatOverStudyPerson;
import kr.co.studyhubinu.studyhubserver.exception.study.PostStartDateConflictException;
import kr.co.studyhubinu.studyhubserver.exception.user.UserNotAccessRightException;
import kr.co.studyhubinu.studyhubserver.exception.user.UserNotFoundException;
Expand Down Expand Up @@ -51,10 +52,21 @@ public Long updatePost(UpdateStudyPostInfo info) {
StudyPostEntity post = findPost(info.getPostId());
validStudyPostDate(info.getStudyStartDate(), info.getStudyEndDate());
validatePostByUser(user.getId(), post);
post.update(info);
int currentJoinCount = getCurrentJoinCount(info.getStudyPerson(), post.getRemainingSeat(), post.getRemainingSeat());
post.update(info, currentJoinCount);
return post.getId();
}

private int getCurrentJoinCount(int studyPerson, Integer updateRemainingSeat, Integer remainingSeat) {
int currentJoinCount = studyPerson - remainingSeat;
if(studyPerson - currentJoinCount <= 0) {
throw new PostRemainingSeatOverStudyPerson();
}
return currentJoinCount;
}

// 총10명 3명 들어와서 잔여석 7명됬어, 근데 총 인원 5명으로 하면 5-3 해서 2가 되어야돼 근데

@Transactional
public void deletePost(Long postId, Long userId) {
UserEntity user = findUser(userId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package springfox.documentation.spring.web.plugins;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import springfox.documentation.RequestHandler;
import springfox.documentation.spi.service.RequestHandlerProvider;
import springfox.documentation.spring.web.OnServletBasedWebApplication;
import springfox.documentation.spring.web.WebMvcRequestHandler;
import springfox.documentation.spring.web.readers.operation.HandlerMethodResolver;

import javax.servlet.ServletContext;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import static java.util.stream.Collectors.toList;
import static springfox.documentation.builders.BuilderDefaults.nullToEmptyList;
import static springfox.documentation.spi.service.contexts.Orderings.byPatternsCondition;
import static springfox.documentation.spring.web.paths.Paths.ROOT;

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@Conditional(OnServletBasedWebApplication.class)
public class WebMvcRequestHandlerProvider implements RequestHandlerProvider {
private final List<RequestMappingInfoHandlerMapping> handlerMappings;
private final HandlerMethodResolver methodResolver;
private final String contextPath;

@Autowired
public WebMvcRequestHandlerProvider(
Optional<ServletContext> servletContext,
HandlerMethodResolver methodResolver,
List<RequestMappingInfoHandlerMapping> handlerMappings) {
this.handlerMappings = handlerMappings.stream()
.filter(mapping -> mapping.getPatternParser() == null)
.collect(Collectors.toList());
this.methodResolver = methodResolver;
this.contextPath = servletContext
.map(ServletContext::getContextPath)
.orElse(ROOT);
}

@Override
public List<RequestHandler> requestHandlers() {
return nullToEmptyList(handlerMappings).stream()
.filter(requestMappingInfoHandlerMapping ->
!("org.springframework.integration.http.inbound.IntegrationRequestMappingHandlerMapping"
.equals(requestMappingInfoHandlerMapping.getClass()
.getName())))
.map(toMappingEntries())
.flatMap((entries -> StreamSupport.stream(entries.spliterator(), false)))
.map(toRequestHandler())
.sorted(byPatternsCondition())
.collect(toList());
}

private Function<RequestMappingInfoHandlerMapping,
Iterable<Map.Entry<RequestMappingInfo, HandlerMethod>>> toMappingEntries() {
return input -> input.getHandlerMethods()
.entrySet();
}

private Function<Map.Entry<RequestMappingInfo, HandlerMethod>, RequestHandler> toRequestHandler() {
return input -> new WebMvcRequestHandler(
contextPath,
methodResolver,
input.getKey(),
input.getValue());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

public enum StudyPostEntityFixture {

SQLD("SQLD딸사람 구해요", "열심히 할 사람만요", MajorType.COMPUTER_SCIENCE_ENGINEERING, 5, GenderType.MALE, StudyWayType.MIX, 10000, "지각비", LocalDate.of(2023, 10, 25), LocalDate.of(2023, 12, 25), 100, 5L),
SQLD("SQLD딸사람 구해요", "열심히 할 사람만요", MajorType.COMPUTER_SCIENCE_ENGINEERING, 100, GenderType.MALE, StudyWayType.MIX, 10000, "지각비", LocalDate.of(2023, 10, 25), LocalDate.of(2023, 12, 25), 100, 5L),
ENGINEER_INFORMATION_PROCESSING("정처기 딸사람 구해요", "심장을 바칠 사람만요", MajorType.COMPUTER_SCIENCE_ENGINEERING, 8, GenderType.FEMALE, StudyWayType.MIX, 5000, "지각비", LocalDate.of(2023, 10, 25), LocalDate.of(2024, 2, 13), 8, 7L),
TOEIC("토익 딸사람 구해요", "800점 이상 목표인 사람들만", MajorType.NONE, 3, GenderType.NULL, StudyWayType.MIX, 50000, "800 달성 실패시", LocalDate.of(2023, 4, 30), LocalDate.of(2023, 8, 25), 3, 8L),
ENGINEER_INFORMATION_PROCESSING_WITH_MALE("정처기 딸사람!!", "남자만요", MajorType.COMPUTER_SCIENCE_ENGINEERING, 8, GenderType.MALE, StudyWayType.MIX, 5000, "지각비", LocalDate.of(2023, 10, 25), LocalDate.of(2024, 2, 13), 8, 10L);
Expand Down

0 comments on commit 13ba63f

Please sign in to comment.