현재 프로젝트에서 스터디 모임에 참여 신청을 한 회원이 모임 신청을 취소 할 시에는 모임의 타입(EventType)이 선착순 모임일 시 자동으로 다음 대기의 회원을 모임 참여확정을 해줘야하는 기능을 만들어야한다.
Event(모임)과 Enrollment(등록)의 연관관계는 1 대 N 양방향 연관관계로 설정해주었다. 다음은 두 엔티티의 코드이다.
@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;
}
@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(모임)에 참여하게하는 기능을 구현해야한다.
@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();
}
스터디의 경로(path)로 해당 study를 조회할 것이다.
public Study findStudyByPath(String path) {
Study study = studyRepository.findStudyOnlyByPath(path);
if (study == null) {
throw new IllegalStateException("해당 스터디를 찾을 수 없습니다");
}
return study;
}
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를 해줘야 하는 이유 : 반복문을 돌면서 바로 다음회원을 찾을 것이기 때문이다.
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로 수정하여 모임에 참여시킬 수 있다.