프로젝트 - 모임(event) 참여 취소 시 대기회원을 모임에 자동 참여시키기

김건우·2023년 2월 6일
0

Spring Data JPA

목록 보기
11/11
post-thumbnail

현재 프로젝트에서 스터디 모임에 참여 신청을 한 회원이 모임 신청을 취소 할 시에는 모임의 타입(EventType)이 선착순 모임일 시 자동으로 다음 대기의 회원을 모임 참여확정을 해줘야하는 기능을 만들어야한다.

연관관계

Event(모임)과 Enrollment(등록)의 연관관계는 1 대 N 양방향 연관관계로 설정해주었다. 다음은 두 엔티티의 코드이다.

  • Event(모임)🔽
@Entity
@Getter
@EqualsAndHashCode(of = "id")
@Builder @ToString
@NoArgsConstructor @AllArgsConstructor
@Where(clause = "deleted = false")
@SQLDelete(sql = "UPDATE event SET deleted = true WHERE id = ?")
public class Event {

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

    @ManyToOne(fetch = LAZY)
    private Study study;

    @ManyToOne(fetch = LAZY)
    private Account createdBy;

    @OneToMany(mappedBy = "event")
    private List<Enrollment> enrollments = new ArrayList<>();

    @Column(nullable = false)
    private String title;

    @Lob
    private String description;

    /**모임 개설 시간**/
    @Column(nullable = false)
    private LocalDateTime createdDateTime;
    /**접수 마감날짜**/
    @Column(nullable = false)
    private LocalDateTime endEnrollmentDateTime;
    /**모임 시작 일시**/
    @Column(nullable = false)
    private LocalDateTime startDateTime;
    /**모임 종료 일시**/
    @Column(nullable = false)
    private LocalDateTime endDateTime;

    @Column
    private Integer limitOfEnrollments;

    @Enumerated(value = STRING)
    private EventType eventType;

    /**SoftDeleteColumn**/
    @Column(name = "deleted")
    private boolean deleted = Boolean.FALSE;
    }
  • Enrollment(등록)🔽
@Entity
@Getter
@Builder
@EqualsAndHashCode(of = "id")
@NoArgsConstructor @AllArgsConstructor
public class Enrollment {

    @Id
    @GeneratedValue
    private Long id;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "event_id")
    private Event event;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "account_id")
    private Account account;

    private LocalDateTime enrolledAt;

    /**확정**/
    private boolean accepted;

    /**참석 여부**/
    private boolean attended;

}

연관관계의 주인은 Enrollment엔티티가 주인이다. 앞에서 말했듯이 구현해야하는 로직은 회원이 enrollment(등록)을 취소할 시에 다음 대기순서의 회원을 자동으로 Event(모임)에 참여하게하는 기능을 구현해야한다.

EventController

  • account 요청은 로그인한 회원의 정보가 들어있다.
  • pathVarable로 들어오는 path는 해당 스터디의 경로이다.(스터디의 경로로 스터디의 모임을 찾을 것이다.)
  • event는 해당 모임의 id가 들어올 것이다.
    @PostMapping("/events/{id}/disenroll")
    public String cancelEnrollment(@CurrentUser Account account, @PathVariable String path, @PathVariable("id") Event event) {
        Study study = studyService.findStudyByPath(path);
        eventService.cancelEnrollment(account, event);
        return "redirect:/study/" + study.encodePath() + "/events/" + event.getId();
    }

Study 조회 서비스 메서드

스터디의 경로(path)로 해당 study를 조회할 것이다.

public Study findStudyByPath(String path) {
        Study study = studyRepository.findStudyOnlyByPath(path);
        if (study == null) {
            throw new IllegalStateException("해당 스터디를 찾을 수 없습니다");
        }
        return study;
    }

Enrollment(등록) 취소 서비스 메서드

  • 회원과 이벤트의 정보로 해당 Enrollment(등록)를 조회 한다.
  • 조회한 enrollment의 참석 여부(attended)가 false일 시 취소 로직에 들어간다.
  • 양방향으로 연관된 엔티티의 값을 삭제해준다.
  public void cancelEnrollment(Account account, Event event) {
        Enrollment enrollment = enrollmentRepository.findByAccountAndEvent(account, event);
        if (!enrollment.isAttended()) {
            enrollmentRepository.delete(enrollment);
            event.removeEnrollment(enrollment);
            acceptNextWaitingEnrollment(event);
        }
        //해당 등록이 삭제 되면서 다음 대기 유저를 확정 상태로 만들어주어야함
    }

양방향 연관관계의 삭제

모임의 등록(enrollment) 값도 삭제해주어야한다.

  • 양방향 연관관계 삭제🔽
  enrollmentRepository.delete(enrollment);
  event.removeEnrollment(enrollment);
  • Event(모임)에서의 enrollment값 삭제
  public void removeEnrollment(Enrollment enrollment) {
        this.enrollments.remove(enrollment);
    }

위의 list에서 직접 remove를 해줘야 하는 이유 : 반복문을 돌면서 바로 다음회원을 찾을 것이기 때문이다.

대기 인원 자동 모임 참여하는 로직

acceptNextWaitingEnrollment()

  private void acceptNextWaitingEnrollment(Event event) {
        if (event.isAbleToAcceptWaitingEnrollment()) {
            Enrollment firstEnrollment = event.getFirstEnrollment();
            if (firstEnrollment != null) {
                firstEnrollment.setAccepted(true);
            }
        }
    }

우선 위의 메서드의 조건을 먼저보면 다음과 같다.
1. 모임의 타입이 선착순이어야한다 -> (FCFS)
2. 모임의 제한인원보다 현재 참석 인원이 작아야한다.(추가로 인원을 넣어줘야하기 때문에)

   /**모임 취소 처리가 들어왔을 시 다음 대기 회원이 모임 확정 상태가 되는 조건**/
    public boolean isAbleToAcceptWaitingEnrollment() {
        return this.eventType.equals(EventType.FCFS)
                && this.limitOfEnrollments > this.enrollments.stream().filter(Enrollment::isAccepted).count();
    }

대기 인원중 가장 먼저 등록한 회원 찾기

반복문을 돌면서 해당 Event의 enrollement list를 반복문을 하나하나 check할 것이다. 조건은 isAttend가 false인 회원 즉 참여 확정이 되지 않은 인원을 응답한다.

    /**첫번째 등록을 가져오는 메서드**/
    public Enrollment getFirstEnrollment() {
        for (Enrollment enrollment : enrollments) {
            if (!enrollment.isAccepted() ) {
                return enrollment;
            }
        }
        return null;
    }

반환된 대기인원 모임에 참여시키기

반환된 회원의 accepted(참석 여부)를 true로 수정시켜서(dirty check) 모임에 참여시켜준다.

  private void acceptNextWaitingEnrollment(Event event) {
        if (event.isAbleToAcceptWaitingEnrollment()) {
            Enrollment firstEnrollment = event.getFirstEnrollment();
            if (firstEnrollment != null) {
                firstEnrollment.setAccepted(true);
            }
        }
    }

위의 로직들을 모두 사용된 서비스 단을 다시 보면 모임에 취소한 회원이 발생할 시에 해당 모임(event)의 등록(enrollment)를 반복문을 돌며 조회하며 바로 다음 대기 인원의 accepted를 true로 수정하여 모임에 참여시킬 수 있다.

profile
Live the moment for the moment.

0개의 댓글