From 3241692e8fb85eacaf81a12996779a6ef9e59b0d Mon Sep 17 00:00:00 2001 From: sanghoon Date: Tue, 19 Aug 2025 16:43:48 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat(#254):=20=EC=83=81=EB=8B=A8=20?= =?UTF-8?q?=EA=B3=B5=EC=A7=80=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EC=8B=9C=20=EB=B0=9C=ED=96=89=20event,=20consumer?= =?UTF-8?q?=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mos/backend/common/event/EventType.java | 2 +- .../application/StudyNoticeService.java | 8 ++++++- .../ImportantNoticeChangedEventPayload.java | 11 +++++++++ .../consumer/UserStudySettingConsumer.java | 24 +++++++++++++++++++ .../application/StudyNoticeServiceTest.java | 19 +++++++++++++++ 5 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/mos/backend/studynotices/application/event/ImportantNoticeChangedEventPayload.java create mode 100644 src/main/java/com/mos/backend/userstudysettings/application/consumer/UserStudySettingConsumer.java diff --git a/src/main/java/com/mos/backend/common/event/EventType.java b/src/main/java/com/mos/backend/common/event/EventType.java index 56ecf000..3c1d1a44 100644 --- a/src/main/java/com/mos/backend/common/event/EventType.java +++ b/src/main/java/com/mos/backend/common/event/EventType.java @@ -11,5 +11,5 @@ public enum EventType { STUDY_JOIN_CANCELED, STUDY_DELETED, STUDY_UPDATED, - + IMPORTANT_NOTICE_CHANGED, } diff --git a/src/main/java/com/mos/backend/studynotices/application/StudyNoticeService.java b/src/main/java/com/mos/backend/studynotices/application/StudyNoticeService.java index 1856433f..0129ba40 100644 --- a/src/main/java/com/mos/backend/studynotices/application/StudyNoticeService.java +++ b/src/main/java/com/mos/backend/studynotices/application/StudyNoticeService.java @@ -1,8 +1,11 @@ package com.mos.backend.studynotices.application; +import com.mos.backend.common.event.Event; +import com.mos.backend.common.event.EventType; import com.mos.backend.common.exception.MosException; import com.mos.backend.common.infrastructure.EntityFacade; import com.mos.backend.studies.entity.Study; +import com.mos.backend.studynotices.application.event.ImportantNoticeChangedEventPayload; import com.mos.backend.studynotices.application.responsedto.StudyNoticeResponseDto; import com.mos.backend.studynotices.entity.StudyNotice; import com.mos.backend.studynotices.entity.StudyNoticeErrorCode; @@ -10,6 +13,7 @@ import com.mos.backend.users.application.UserService; import com.mos.backend.users.entity.User; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -27,6 +31,7 @@ public class StudyNoticeService { private final StudyNoticeRepository studyNoticeRepository; private final UserService userService; private final EntityFacade entityFacade; + private final ApplicationEventPublisher eventPublisher; /** * 공지 생성 @@ -38,8 +43,8 @@ public StudyNoticeResponseDto create(long studyId, long currentUserId, String ti // 중요 공지로 설정했을 때 중요 공지가 이미 존재하다면 기존 중요 공지 마크 해제 if (important) { studyNoticeRepository.findByStudyIdAndImportantIsTrue(studyId).ifPresent(StudyNotice::unmarkAsImportant); + eventPublisher.publishEvent(new Event<>(EventType.IMPORTANT_NOTICE_CHANGED, new ImportantNoticeChangedEventPayload(studyId))); } - Study study = entityFacade.getStudy(studyId); User currentUser = entityFacade.getUser(currentUserId); @@ -58,6 +63,7 @@ public StudyNoticeResponseDto update(long studyId, long studyNoticeId, String ti // 중요 공지로 설정했을 때 중요 공지가 이미 존재하다면 기존 중요 공지 마크 해제 if (important) { studyNoticeRepository.findByStudyIdAndImportantIsTrue(studyId).ifPresent(StudyNotice::unmarkAsImportant); + eventPublisher.publishEvent(new Event<>(EventType.IMPORTANT_NOTICE_CHANGED, new ImportantNoticeChangedEventPayload(studyId))); } StudyNotice studyNotice = findByIdWithUser(studyNoticeId); diff --git a/src/main/java/com/mos/backend/studynotices/application/event/ImportantNoticeChangedEventPayload.java b/src/main/java/com/mos/backend/studynotices/application/event/ImportantNoticeChangedEventPayload.java new file mode 100644 index 00000000..61349d6b --- /dev/null +++ b/src/main/java/com/mos/backend/studynotices/application/event/ImportantNoticeChangedEventPayload.java @@ -0,0 +1,11 @@ +package com.mos.backend.studynotices.application.event; + +import com.mos.backend.common.event.Payload; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ImportantNoticeChangedEventPayload implements Payload { + private Long studyId; +} diff --git a/src/main/java/com/mos/backend/userstudysettings/application/consumer/UserStudySettingConsumer.java b/src/main/java/com/mos/backend/userstudysettings/application/consumer/UserStudySettingConsumer.java new file mode 100644 index 00000000..8abb7c90 --- /dev/null +++ b/src/main/java/com/mos/backend/userstudysettings/application/consumer/UserStudySettingConsumer.java @@ -0,0 +1,24 @@ +package com.mos.backend.userstudysettings.application.consumer; + +import com.mos.backend.common.event.Event; +import com.mos.backend.studies.application.event.StudyCreatedEventPayload; +import com.mos.backend.studycurriculum.application.StudyCurriculumService; +import com.mos.backend.studynotices.application.event.ImportantNoticeChangedEventPayload; +import com.mos.backend.userstudysettings.application.UserStudySettingService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionalEventListener; + +import static org.springframework.transaction.event.TransactionPhase.BEFORE_COMMIT; + +@Component +@RequiredArgsConstructor +public class UserStudySettingConsumer { + private final UserStudySettingService userStudySettingService; + + @TransactionalEventListener(phase = BEFORE_COMMIT) + public void handleImportantNoticeChangedEvent(Event event) { + ImportantNoticeChangedEventPayload payload = event.getPayload(); + userStudySettingService.showNoticeForAllMembers(payload.getStudyId()); + } +} diff --git a/src/test/java/com/mos/backend/studynotices/application/StudyNoticeServiceTest.java b/src/test/java/com/mos/backend/studynotices/application/StudyNoticeServiceTest.java index 14e842fa..fd78199b 100644 --- a/src/test/java/com/mos/backend/studynotices/application/StudyNoticeServiceTest.java +++ b/src/test/java/com/mos/backend/studynotices/application/StudyNoticeServiceTest.java @@ -1,8 +1,11 @@ package com.mos.backend.studynotices.application; +import com.mos.backend.common.event.Event; import com.mos.backend.common.exception.MosException; import com.mos.backend.common.infrastructure.EntityFacade; +import com.mos.backend.studies.application.event.StudyCreatedEventPayload; import com.mos.backend.studies.entity.Study; +import com.mos.backend.studynotices.application.event.ImportantNoticeChangedEventPayload; import com.mos.backend.studynotices.application.responsedto.StudyNoticeResponseDto; import com.mos.backend.studynotices.entity.StudyNotice; import com.mos.backend.studynotices.entity.StudyNoticeErrorCode; @@ -12,9 +15,12 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; import java.util.Optional; @@ -38,6 +44,12 @@ class StudyNoticeServiceTest { @Mock private EntityFacade entityFacade; + @Mock + private ApplicationEventPublisher eventPublisher; + + @Captor + private ArgumentCaptor> eventCaptor; + @Test @DisplayName("공지를 성공적으로 생성한다.") @@ -70,6 +82,9 @@ void create_success() { StudyNoticeResponseDto studyNoticeResponseDto = studyNoticeService.create(studyId, currentUserId, title, content, pinned, important); // then + verify(eventPublisher).publishEvent(eventCaptor.capture()); + Event event = eventCaptor.getValue(); + assertThat(event.getPayload().getStudyId()).isEqualTo(studyId); assertThat(studyNoticeResponseDto.getStudyId()).isEqualTo(studyId); assertThat(studyNoticeResponseDto.getCreatorId()).isEqualTo(currentUserId); assertThat(studyNoticeResponseDto.getTitle()).isEqualTo(title); @@ -114,6 +129,9 @@ void whenCreateImportantNotice_thenUnmarksOldOne() { studyNoticeService.create(studyId, currentUserId, "new title", "new content", false, true); // then + verify(eventPublisher).publishEvent(eventCaptor.capture()); + Event event = eventCaptor.getValue(); + assertThat(event.getPayload().getStudyId()).isEqualTo(studyId); assertThat(oldImportantNotice.isImportant()).isFalse(); // 기존 공지가 해제되었는지 확인 verify(studyNoticeRepository).save(any(StudyNotice.class)); } @@ -163,6 +181,7 @@ void update_success() { StudyNoticeResponseDto updatedNotice = studyNoticeService.update(studyId, studyNoticeId, updateTitle, updateContent, updatePinned, updateImportant); // then + verify(eventPublisher, times(0)).publishEvent(any()); assertThat(updatedNotice.getTitle()).isEqualTo(updateTitle); assertThat(updatedNotice.getContent()).isEqualTo(updateContent); assertThat(updatedNotice.isPinned()).isEqualTo(updatePinned); From 8a7f22c6237acda8056ce95933e485ce6fcef711 Mon Sep 17 00:00:00 2001 From: sanghoon Date: Tue, 19 Aug 2025 16:48:36 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat(#254):=20=EC=83=81=EB=8B=A8=20?= =?UTF-8?q?=EA=B3=B5=EC=A7=80=20=EB=B3=80=EA=B2=BD=20=ED=9B=84=20=ED=9B=84?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 스터디의 모든 멤버의 noticePined를 true로 벌크 쿼리로 처리 --- .../application/UserStudySettingService.java | 7 +++++ .../UserStudySettingQueryDslRepository.java | 27 +++++++++++++++++++ .../UserStudySettingRepository.java | 2 ++ .../UserStudySettingRepositoryImpl.java | 6 +++++ 4 files changed, 42 insertions(+) create mode 100644 src/main/java/com/mos/backend/userstudysettings/infrastructure/UserStudySettingQueryDslRepository.java diff --git a/src/main/java/com/mos/backend/userstudysettings/application/UserStudySettingService.java b/src/main/java/com/mos/backend/userstudysettings/application/UserStudySettingService.java index 7babad52..24c56212 100644 --- a/src/main/java/com/mos/backend/userstudysettings/application/UserStudySettingService.java +++ b/src/main/java/com/mos/backend/userstudysettings/application/UserStudySettingService.java @@ -52,4 +52,11 @@ public void create(Long studyId, Long userId) { StudyMember studyMember = entityFacade.getStudyMember(userId, studyId); userStudySettingRepository.save(UserStudySetting.create(studyMember)); } + + /** + * 스터디의 모든 멤버의 noticePined 를 true로 변경 + */ + public void showNoticeForAllMembers(Long studyId) { + userStudySettingRepository.showNoticeForAllMembers(studyId); + } } diff --git a/src/main/java/com/mos/backend/userstudysettings/infrastructure/UserStudySettingQueryDslRepository.java b/src/main/java/com/mos/backend/userstudysettings/infrastructure/UserStudySettingQueryDslRepository.java new file mode 100644 index 00000000..17e4ab96 --- /dev/null +++ b/src/main/java/com/mos/backend/userstudysettings/infrastructure/UserStudySettingQueryDslRepository.java @@ -0,0 +1,27 @@ +package com.mos.backend.userstudysettings.infrastructure; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import static com.mos.backend.userstudysettings.entity.QUserStudySetting.userStudySetting; + +@RequiredArgsConstructor +@Repository +public class UserStudySettingQueryDslRepository { + private final EntityManager em; + private final JPAQueryFactory queryFactory; + + public void showNoticeForAllMembers(Long studyId) { + long number = queryFactory + .update(userStudySetting) + .set(userStudySetting.noticePined, true) + .where(userStudySetting.studyMember.study.id.eq(studyId)) + .execute(); + if (number > 0) { + em.flush(); + em.clear(); + } + } +} diff --git a/src/main/java/com/mos/backend/userstudysettings/infrastructure/UserStudySettingRepository.java b/src/main/java/com/mos/backend/userstudysettings/infrastructure/UserStudySettingRepository.java index 4b292905..06aaf3e4 100644 --- a/src/main/java/com/mos/backend/userstudysettings/infrastructure/UserStudySettingRepository.java +++ b/src/main/java/com/mos/backend/userstudysettings/infrastructure/UserStudySettingRepository.java @@ -9,4 +9,6 @@ public interface UserStudySettingRepository { Optional findByStudyMember(StudyMember studyMember); void save(UserStudySetting userStudySetting); + + void showNoticeForAllMembers(Long studyId); } diff --git a/src/main/java/com/mos/backend/userstudysettings/infrastructure/UserStudySettingRepositoryImpl.java b/src/main/java/com/mos/backend/userstudysettings/infrastructure/UserStudySettingRepositoryImpl.java index 927d7cf4..9f81dfbb 100644 --- a/src/main/java/com/mos/backend/userstudysettings/infrastructure/UserStudySettingRepositoryImpl.java +++ b/src/main/java/com/mos/backend/userstudysettings/infrastructure/UserStudySettingRepositoryImpl.java @@ -12,6 +12,7 @@ public class UserStudySettingRepositoryImpl implements UserStudySettingRepository{ private final UserStudySettingJpaRepository userStudySettingJpaRepository; + private final UserStudySettingQueryDslRepository userStudySettingQueryDslRepository; @Override public Optional findByStudyMember(StudyMember studyMember) { @@ -22,4 +23,9 @@ public Optional findByStudyMember(StudyMember studyMember) { public void save(UserStudySetting userStudySetting) { userStudySettingJpaRepository.save(userStudySetting); } + + @Override + public void showNoticeForAllMembers(Long studyId) { + userStudySettingQueryDslRepository.showNoticeForAllMembers(studyId); + } }