From 07ea90343c68c9997fffbfa101e3a3f2f35caca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Fri, 22 May 2026 16:56:14 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20NotificationJdbcRepository=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationJdbcRepository.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/main/java/in/koreatech/koin/domain/notification/repository/NotificationJdbcRepository.java diff --git a/src/main/java/in/koreatech/koin/domain/notification/repository/NotificationJdbcRepository.java b/src/main/java/in/koreatech/koin/domain/notification/repository/NotificationJdbcRepository.java new file mode 100644 index 000000000..aad7c069b --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/notification/repository/NotificationJdbcRepository.java @@ -0,0 +1,60 @@ +package in.koreatech.koin.domain.notification.repository; + +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import in.koreatech.koin.domain.notification.model.Notification; +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class NotificationJdbcRepository { + + private static final int BATCH_SIZE = 1_000; + + private final JdbcTemplate jdbcTemplate; + + public void batchInsert(List notifications) { + if (notifications.isEmpty()) { + return; + } + + String sql = """ + INSERT INTO notification ( + app_path, + scheme_uri, + title, + message, + image_url, + type, + users_id, + is_read, + created_at, + updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """; + + LocalDateTime now = LocalDateTime.now(); + + jdbcTemplate.batchUpdate( + sql, + notifications, + BATCH_SIZE, + (preparedStatement, notification) -> { + preparedStatement.setString(1, notification.getMobileAppPath().name()); + preparedStatement.setString(2, notification.getSchemeUri()); + preparedStatement.setString(3, notification.getTitle()); + preparedStatement.setString(4, notification.getMessage()); + preparedStatement.setString(5, notification.getImageUrl()); + preparedStatement.setString(6, notification.getType().toUpperCase()); + preparedStatement.setInt(7, notification.getUser().getId()); + preparedStatement.setBoolean(8, notification.isRead()); + preparedStatement.setObject(9, now); + preparedStatement.setObject(10, now); + } + ); + } +} From 47656f4a132205d1fea47d3b44ef2ac93644c9ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Fri, 22 May 2026 17:02:47 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20pushNotifications=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EB=82=B4=EB=B6=80=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/NotificationService.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/notification/service/NotificationService.java b/src/main/java/in/koreatech/koin/domain/notification/service/NotificationService.java index 7a7c686cd..386624221 100644 --- a/src/main/java/in/koreatech/koin/domain/notification/service/NotificationService.java +++ b/src/main/java/in/koreatech/koin/domain/notification/service/NotificationService.java @@ -20,7 +20,7 @@ import in.koreatech.koin.domain.notification.model.NotificationFactory; import in.koreatech.koin.domain.notification.model.NotificationSubscribe; import in.koreatech.koin.domain.notification.model.NotificationSubscribeType; -import in.koreatech.koin.domain.notification.repository.NotificationRepository; +import in.koreatech.koin.domain.notification.repository.NotificationJdbcRepository; import in.koreatech.koin.domain.notification.repository.NotificationSubscribeRepository; import in.koreatech.koin.domain.user.model.User; import in.koreatech.koin.domain.user.repository.UserRepository; @@ -38,19 +38,27 @@ public class NotificationService { public record NotificationDeliveryResult(Notification notification, boolean delivered) {} private final UserRepository userRepository; - private final NotificationRepository notificationRepository; private final NotificationPersistenceService notificationPersistenceService; private final FcmClient fcmClient; private final NotificationSubscribeRepository notificationSubscribeRepository; private final NotificationFactory notificationFactory; + private final NotificationJdbcRepository notificationJdbcRepository; @Transactional public void pushNotifications(List notifications) { if (notifications.isEmpty()) { return; } - notificationRepository.saveAll(notifications); - runAfterCommit(() -> notifications.forEach(this::sendNotificationSafely)); + notificationJdbcRepository.batchInsert(notifications); + notifications.forEach(notification -> fcmClient.sendMessage( + notification.getUser().getDeviceToken(), + notification.getTitle(), + notification.getMessage(), + notification.getImageUrl(), + notification.getMobileAppPath(), + notification.getSchemeUri(), + notification.getType().toLowerCase() + )); } @Transactional From d221f639ba9f3adb11c19a6d450194f686be8008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Fri, 22 May 2026 17:04:32 +0900 Subject: [PATCH 3/3] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/NotificationServiceTest.java | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/test/java/in/koreatech/koin/unit/domain/notification/service/NotificationServiceTest.java b/src/test/java/in/koreatech/koin/unit/domain/notification/service/NotificationServiceTest.java index 7e5172853..693125744 100644 --- a/src/test/java/in/koreatech/koin/unit/domain/notification/service/NotificationServiceTest.java +++ b/src/test/java/in/koreatech/koin/unit/domain/notification/service/NotificationServiceTest.java @@ -3,26 +3,21 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; import org.mockito.InOrder; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import in.koreatech.koin.domain.notification.model.Notification; import in.koreatech.koin.domain.notification.model.NotificationFactory; -import in.koreatech.koin.domain.notification.repository.NotificationRepository; +import in.koreatech.koin.domain.notification.repository.NotificationJdbcRepository; import in.koreatech.koin.domain.notification.repository.NotificationSubscribeRepository; import in.koreatech.koin.domain.notification.service.NotificationPersistenceService; import in.koreatech.koin.domain.notification.service.NotificationService; @@ -40,9 +35,6 @@ class NotificationServiceTest { @Mock private UserRepository userRepository; - @Mock - private NotificationRepository notificationRepository; - @Mock private NotificationPersistenceService notificationPersistenceService; @@ -55,6 +47,9 @@ class NotificationServiceTest { @Mock private NotificationFactory notificationFactory; + @Mock + private NotificationJdbcRepository notificationJdbcRepository; + @Test @DisplayName("알림 전송 결과 조회는 전송 성공 시에만 알림 레코드를 저장한다.") void pushNotificationsWithResult_whenDelivered_savesNotification() { @@ -137,12 +132,11 @@ void pushNotification_savesNotificationBeforeSend() { notificationService.pushNotification(notification); - InOrder inOrder = inOrder(notificationRepository, fcmClient); - inOrder.verify(notificationRepository).saveAll(List.of(notification)); + InOrder inOrder = inOrder(notificationJdbcRepository, fcmClient); + inOrder.verify(notificationJdbcRepository).batchInsert(List.of(notification)); inOrder.verify(fcmClient).sendMessage( anyString(), anyString(), anyString(), any(), any(), anyString(), anyString() ); - verify(notificationRepository, never()).save(notification); } private Notification createNotification(String deviceToken) {