Skip to content

refactor: 인앱 알림의 트랜잭션 전파 옵션 제거#448

Merged
dh2906 merged 3 commits intodevelopfrom
refactor/CAM-255-group-chat-nofity-async
Mar 27, 2026
Merged

refactor: 인앱 알림의 트랜잭션 전파 옵션 제거#448
dh2906 merged 3 commits intodevelopfrom
refactor/CAM-255-group-chat-nofity-async

Conversation

@dh2906
Copy link
Copy Markdown
Contributor

@dh2906 dh2906 commented Mar 27, 2026

🔍 개요

  • 인앱 알림 저장 로직의 트랜잭션 전파 옵션이 REQUIRES_NEW로 설정됨

  • 이로 인해, 새로운 트랜잭션이 생성되어 추가로 커넥션을 차지하게 되어 DB 커넥션 고갈하는 문제가 발생


🚀 주요 변경 내용

  • 인앱 알림의 save(), saveAll() 메소드 트랜잭션 전파 옵션을 제거했습니다.

  • NotificationService.sendChatNotification()와 같은 비동기 메소드에서 save() 메소드를 호출하는데, 이는 읽기 전용 트랜잭션으로 되어있어 DB 저장에 실패하여 비동기 메소드에서도 트랜잭션을 명시했습니다.


💬 참고 사항


✅ Checklist (완료 조건)

  • 코드 스타일 가이드 준수
  • 테스트 코드 포함됨
  • Reviewers / Assignees / Labels 지정 완료
  • 보안 및 민감 정보 검증 (API 키, 환경 변수, 개인정보 등)

@dh2906 dh2906 self-assigned this Mar 27, 2026
@dh2906 dh2906 added the 리팩토링 리팩터링을 위한 이슈입니다. label Mar 27, 2026
@linear
Copy link
Copy Markdown

linear bot commented Mar 27, 2026

@dh2906 dh2906 changed the title Refactor/cam 255 group chat nofity async refactor: 인앱 알림의 트랜잭션 전파 옵션 제거 Mar 27, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

📝 Walkthrough

Walkthrough

NotificationInboxService의 save/saveAll 메서드 트랜잭션 전파를 REQUIRES_NEW에서 기본 @Transactional로 변경했고, NotificationService의 다수 @Async 알림 전송 메서드들에 @Transactional을 추가했습니다. (공개 시그니처 변경 없음)

Changes

Cohort / File(s) Summary
Inbox 트랜잭션 변경
src/main/java/gg/agit/konect/domain/notification/service/NotificationInboxService.java
save(...), saveAll(...)@Transactional(propagation = Propagation.REQUIRES_NEW)@Transactional로 변경. 불필요한 Propagation import 제거 및 DTO import 순서 정렬.
비동기 메서드에 트랜잭션 추가
src/main/java/gg/agit/konect/domain/notification/service/NotificationService.java
다수의 @Async 메서드에 @Transactional을 추가: sendChatNotification, sendGroupChatNotification, sendClubApplicationSubmittedNotification, sendClubApplicationApprovedNotification, sendClubApplicationRejectedNotification (기능/시그니처 변경 없음).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🐰 트랜잭션 실타래 다시 묶어,
비동기 발걸음도 함께 약속하네.
작은 변경, 큰 흐름은 바뀌지 않으니,
편지(알림)는 부드럽게 도착하리.
당근 한 조각, 축하의 껑충! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경 사항을 명확하게 설명하고 있습니다. 트랜잭션 전파 옵션 제거가 가장 중요한 변경으로 정확히 반영되었습니다.
Description check ✅ Passed PR 설명이 변경 사항과 관련이 있으며, 문제 상황, 주요 변경 내용, 그리고 해결 방안을 체계적으로 설명하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/CAM-255-group-chat-nofity-async

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/gg/agit/konect/domain/notification/service/NotificationService.java (1)

230-248: ⚠️ Potential issue | 🔴 Critical

[LEVEL: high] @Transactional 누락으로 알림 저장 실패 발생

sendClubApplicationApprovedNotificationsendClubApplicationRejectedNotification@Transactional이 없어 클래스 레벨의 readOnly=true가 적용됩니다. NotificationInboxService.save()에서 REQUIRES_NEW가 제거되었으므로, 읽기 전용 트랜잭션 내에서 저장 시도 시 데이터가 영속되지 않습니다. 다른 비동기 메서드와 동일하게 @Transactional을 추가해야 합니다.

🐛 누락된 `@Transactional` 추가 제안
 `@Async`
+@Transactional
 public void sendClubApplicationApprovedNotification(Integer receiverId, Integer clubId, String clubName) {
     String body = "동아리 지원이 승인되었어요.";
     String path = "clubs/" + clubId;
     NotificationInbox saved = notificationInboxService.save(
         receiverId, NotificationInboxType.CLUB_APPLICATION_APPROVED, clubName, body, path);
     notificationInboxService.sendSse(receiverId, NotificationInboxResponse.from(saved));
     sendNotification(receiverId, clubName, body, path);
 }

 `@Async`
+@Transactional
 public void sendClubApplicationRejectedNotification(Integer receiverId, Integer clubId, String clubName) {
     String body = "동아리 지원이 거절되었어요.";
     String path = "clubs/" + clubId;
     NotificationInbox saved = notificationInboxService.save(
         receiverId, NotificationInboxType.CLUB_APPLICATION_REJECTED, clubName, body, path);
     notificationInboxService.sendSse(receiverId, NotificationInboxResponse.from(saved));
     sendNotification(receiverId, clubName, body, path);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/gg/agit/konect/domain/notification/service/NotificationService.java`
around lines 230 - 248, The two async methods
sendClubApplicationApprovedNotification and
sendClubApplicationRejectedNotification are missing `@Transactional` so
class-level readOnly=true prevents persisting via
notificationInboxService.save() (REQUIRES_NEW was removed); add a method-level
`@Transactional` (without readOnly) to both methods so the save executes in a
writable transaction, keeping `@Async` and preserving existing logic including
calls to notificationInboxService.save, notificationInboxService.sendSse, and
sendNotification.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/gg/agit/konect/domain/notification/service/NotificationInboxService.java`:
- Around line 33-37: The class-level `@Transactional`(readOnly = true) prevents
write operations, so add `@Transactional` to the two methods
sendClubApplicationApprovedNotification and
sendClubApplicationRejectedNotification in NotificationInboxService to allow
notificationInboxService.save(...) to persist; annotate both methods with
`@Transactional` (and import if missing) so they run with a writable transaction
while leaving the class-level readOnly default for others.

---

Outside diff comments:
In
`@src/main/java/gg/agit/konect/domain/notification/service/NotificationService.java`:
- Around line 230-248: The two async methods
sendClubApplicationApprovedNotification and
sendClubApplicationRejectedNotification are missing `@Transactional` so
class-level readOnly=true prevents persisting via
notificationInboxService.save() (REQUIRES_NEW was removed); add a method-level
`@Transactional` (without readOnly) to both methods so the save executes in a
writable transaction, keeping `@Async` and preserving existing logic including
calls to notificationInboxService.save, notificationInboxService.sendSse, and
sendNotification.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5fd6069e-4529-43ca-9122-fed88c901cff

📥 Commits

Reviewing files that changed from the base of the PR and between 3888548 and 569e53a.

📒 Files selected for processing (2)
  • src/main/java/gg/agit/konect/domain/notification/service/NotificationInboxService.java
  • src/main/java/gg/agit/konect/domain/notification/service/NotificationService.java
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
src/main/java/**/*.java

⚙️ CodeRabbit configuration file

src/main/java/**/*.java: 아래 원칙으로 리뷰 코멘트를 작성한다.

  • 코멘트는 반드시 한국어로 작성한다.
  • 반드시 수정이 필요한 항목만 코멘트로 남기고, 단순 취향 차이는 지적하지 않는다.
  • 각 코멘트 첫 줄에 심각도를 [LEVEL: high|medium|low] 형식으로 반드시 표기한다.
  • 심각도 기준: high=운영 장애 가능, medium=품질 저하, low=개선 권고.
  • 각 코멘트는 "문제 -> 영향 -> 제안" 순서로 3문장 이내로 간결하게 작성한다.
  • 가능하면 재현 조건 및 실패 시나리오도 포함한다.
  • 제안은 현재 코드베이스(Spring Boot + JPA + Flyway) 패턴과 일치해야 한다.
  • 보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.
  • 가독성: 변수/메서드 이름이 의도를 바로 드러내는지, 중첩과 메서드 길이가 과도하지 않은지 점검한다.
  • 단순화: 불필요한 추상화, 중복 로직, 과한 방어 코드가 있으면 더 단순한 대안을 제시한다.
  • 확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.

Files:

  • src/main/java/gg/agit/konect/domain/notification/service/NotificationInboxService.java
  • src/main/java/gg/agit/konect/domain/notification/service/NotificationService.java
**/*

⚙️ CodeRabbit configuration file

**/*: 공통 리뷰 톤 가이드:

  • 모든 코멘트는 첫 줄에 [LEVEL: ...] 태그를 포함한다.
  • 과장된 표현 없이 사실 기반으로 작성한다.
  • 한 코멘트에는 하나의 이슈만 다룬다.
  • 코드 예시가 필요하면 최소 수정 예시를 제시한다.
  • 가독성/단순화/확장성 이슈를 발견하면 우선순위를 높여 코멘트한다.

Files:

  • src/main/java/gg/agit/konect/domain/notification/service/NotificationInboxService.java
  • src/main/java/gg/agit/konect/domain/notification/service/NotificationService.java
🔇 Additional comments (3)
src/main/java/gg/agit/konect/domain/notification/service/NotificationService.java (3)

76-77: [LEVEL: low] @Async@Transactional 조합 적용 확인됨

비동기 메서드에 @Transactional이 추가되어 notificationInboxService.save()가 쓰기 가능한 트랜잭션 내에서 실행됩니다. PR 목적에 부합하는 변경입니다.


126-127: [LEVEL: low] 그룹 채팅 알림 메서드 트랜잭션 설정 적용됨

sendGroupChatNotification@Transactional이 추가되어 saveAll() 호출 시 쓰기 트랜잭션이 보장됩니다.


213-214: [LEVEL: low] 클럽 가입 신청 알림 메서드 트랜잭션 설정 적용됨

sendClubApplicationSubmittedNotification@Transactional이 추가되어 알림 저장이 정상 동작합니다.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/main/java/gg/agit/konect/domain/notification/service/NotificationService.java (3)

213-228: ⚠️ Potential issue | 🟠 Major

[LEVEL: medium] 예외 처리 누락으로 인한 알림 손실 가능성

sendChatNotification, sendGroupChatNotification과 달리 try-catch 블록이 없어서 expoPushClient.sendNotification() 호출 시 예외 발생하면 트랜잭션이 롤백되어 인박스 저장이 취소됩니다. 다른 알림 메서드들과 동일하게 try-catch로 감싸서 푸시 실패와 무관하게 인박스 저장이 보장되도록 수정하세요.

🔧 제안 수정
 `@Async`
 `@Transactional`
 public void sendClubApplicationSubmittedNotification(
     Integer receiverId,
     Integer applicationId,
     Integer clubId,
     String clubName,
     String applicantName
 ) {
+    try {
         String body = applicantName + "님이 동아리 가입을 신청했어요.";
         String path = "mypage/manager/" + clubId + "/applications/" + applicationId;
         NotificationInbox saved = notificationInboxService.save(
             receiverId, NotificationInboxType.CLUB_APPLICATION_SUBMITTED, clubName, body, path);
         notificationInboxService.sendSse(receiverId, NotificationInboxResponse.from(saved));
         sendNotification(receiverId, clubName, body, path);
+    } catch (Exception e) {
+        log.error("Failed to send club application submitted notification: receiverId={}, clubId={}", receiverId, clubId, e);
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/gg/agit/konect/domain/notification/service/NotificationService.java`
around lines 213 - 228, The sendClubApplicationSubmittedNotification method
lacks exception handling around the external push call, so failures in
expoPushClient (triggered by sendNotification) can roll back the transaction and
undo the saved NotificationInbox; wrap the sendNotification(receiverId,
clubName, body, path) call in a try-catch that catches Exception (or a specific
client exception), logs the error, and does not rethrow it so the saved
NotificationInbox (notificationInboxService.save(...) stored in variable saved)
and the SSE send (notificationInboxService.sendSse(...)) remain committed;
alternatively move only the push dispatch outside the `@Transactional` boundary,
but the simplest fix is to keep the save and sendSse as-is and surround
sendNotification(...) with a try-catch that logs the failure.

230-239: ⚠️ Potential issue | 🟠 Major

[LEVEL: medium] 예외 처리 누락으로 인한 알림 손실 가능성

sendClubApplicationSubmittedNotification과 동일한 문제입니다. sendNotification() 실패 시 트랜잭션 롤백으로 인박스 저장이 취소됩니다. try-catch 블록을 추가하세요.

🔧 제안 수정
 `@Async`
 `@Transactional`
 public void sendClubApplicationApprovedNotification(Integer receiverId, Integer clubId, String clubName) {
+    try {
         String body = "동아리 지원이 승인되었어요.";
         String path = "clubs/" + clubId;
         NotificationInbox saved = notificationInboxService.save(
             receiverId, NotificationInboxType.CLUB_APPLICATION_APPROVED, clubName, body, path);
         notificationInboxService.sendSse(receiverId, NotificationInboxResponse.from(saved));
         sendNotification(receiverId, clubName, body, path);
+    } catch (Exception e) {
+        log.error("Failed to send club application approved notification: receiverId={}, clubId={}", receiverId, clubId, e);
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/gg/agit/konect/domain/notification/service/NotificationService.java`
around lines 230 - 239, The sendClubApplicationApprovedNotification method
currently calls sendNotification after saving the inbox entry inside the
transactional method, so if sendNotification throws the transaction will roll
back and the saved NotificationInbox (created via notificationInboxService.save)
will be lost; wrap the call to sendNotification(receiverId, clubName, body,
path) in a try-catch that catches Exception, logs the failure, and does not
rethrow (keeping the method transactional for the save/sendSse steps), or
alternatively move sendNotification outside the `@Transactional` scope so
notificationInboxService.save and notificationInboxService.sendSse (and
NotificationInboxResponse.from(saved)) always commit; update
sendClubApplicationApprovedNotification accordingly to ensure failures in
sendNotification do not roll back the saved inbox.

241-250: ⚠️ Potential issue | 🟠 Major

[LEVEL: medium] 예외 처리 누락으로 인한 알림 손실 가능성

위 메서드들과 동일한 문제입니다. sendNotification() 실패 시 트랜잭션 롤백으로 인박스 저장이 취소됩니다. try-catch 블록을 추가하세요.

🔧 제안 수정
 `@Async`
 `@Transactional`
 public void sendClubApplicationRejectedNotification(Integer receiverId, Integer clubId, String clubName) {
+    try {
         String body = "동아리 지원이 거절되었어요.";
         String path = "clubs/" + clubId;
         NotificationInbox saved = notificationInboxService.save(
             receiverId, NotificationInboxType.CLUB_APPLICATION_REJECTED, clubName, body, path);
         notificationInboxService.sendSse(receiverId, NotificationInboxResponse.from(saved));
         sendNotification(receiverId, clubName, body, path);
+    } catch (Exception e) {
+        log.error("Failed to send club application rejected notification: receiverId={}, clubId={}", receiverId, clubId, e);
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/gg/agit/konect/domain/notification/service/NotificationService.java`
around lines 241 - 250, The method sendClubApplicationRejectedNotification
currently saves the inbox (notificationInboxService.save) and sends SSE
(notificationInboxService.sendSse) but calls sendNotification without exception
handling so a failure there can trigger a transaction rollback and lose the
saved inbox; wrap the call to sendNotification in a try-catch (catch Exception)
inside sendClubApplicationRejectedNotification, log the error, and ensure the
saved NotificationInbox (saved) and SSE sending are not rolled back or retried;
keep the current behavior of persisting and sending SSE, but swallow or record
sendNotification failures instead of letting them propagate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@src/main/java/gg/agit/konect/domain/notification/service/NotificationService.java`:
- Around line 213-228: The sendClubApplicationSubmittedNotification method lacks
exception handling around the external push call, so failures in expoPushClient
(triggered by sendNotification) can roll back the transaction and undo the saved
NotificationInbox; wrap the sendNotification(receiverId, clubName, body, path)
call in a try-catch that catches Exception (or a specific client exception),
logs the error, and does not rethrow it so the saved NotificationInbox
(notificationInboxService.save(...) stored in variable saved) and the SSE send
(notificationInboxService.sendSse(...)) remain committed; alternatively move
only the push dispatch outside the `@Transactional` boundary, but the simplest fix
is to keep the save and sendSse as-is and surround sendNotification(...) with a
try-catch that logs the failure.
- Around line 230-239: The sendClubApplicationApprovedNotification method
currently calls sendNotification after saving the inbox entry inside the
transactional method, so if sendNotification throws the transaction will roll
back and the saved NotificationInbox (created via notificationInboxService.save)
will be lost; wrap the call to sendNotification(receiverId, clubName, body,
path) in a try-catch that catches Exception, logs the failure, and does not
rethrow (keeping the method transactional for the save/sendSse steps), or
alternatively move sendNotification outside the `@Transactional` scope so
notificationInboxService.save and notificationInboxService.sendSse (and
NotificationInboxResponse.from(saved)) always commit; update
sendClubApplicationApprovedNotification accordingly to ensure failures in
sendNotification do not roll back the saved inbox.
- Around line 241-250: The method sendClubApplicationRejectedNotification
currently saves the inbox (notificationInboxService.save) and sends SSE
(notificationInboxService.sendSse) but calls sendNotification without exception
handling so a failure there can trigger a transaction rollback and lose the
saved inbox; wrap the call to sendNotification in a try-catch (catch Exception)
inside sendClubApplicationRejectedNotification, log the error, and ensure the
saved NotificationInbox (saved) and SSE sending are not rolled back or retried;
keep the current behavior of persisting and sending SSE, but swallow or record
sendNotification failures instead of letting them propagate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a4a7f9df-9545-46d9-b18f-156db95dee9f

📥 Commits

Reviewing files that changed from the base of the PR and between 569e53a and 4e618d0.

📒 Files selected for processing (1)
  • src/main/java/gg/agit/konect/domain/notification/service/NotificationService.java
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
src/main/java/**/*.java

⚙️ CodeRabbit configuration file

src/main/java/**/*.java: 아래 원칙으로 리뷰 코멘트를 작성한다.

  • 코멘트는 반드시 한국어로 작성한다.
  • 반드시 수정이 필요한 항목만 코멘트로 남기고, 단순 취향 차이는 지적하지 않는다.
  • 각 코멘트 첫 줄에 심각도를 [LEVEL: high|medium|low] 형식으로 반드시 표기한다.
  • 심각도 기준: high=운영 장애 가능, medium=품질 저하, low=개선 권고.
  • 각 코멘트는 "문제 -> 영향 -> 제안" 순서로 3문장 이내로 간결하게 작성한다.
  • 가능하면 재현 조건 및 실패 시나리오도 포함한다.
  • 제안은 현재 코드베이스(Spring Boot + JPA + Flyway) 패턴과 일치해야 한다.
  • 보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.
  • 가독성: 변수/메서드 이름이 의도를 바로 드러내는지, 중첩과 메서드 길이가 과도하지 않은지 점검한다.
  • 단순화: 불필요한 추상화, 중복 로직, 과한 방어 코드가 있으면 더 단순한 대안을 제시한다.
  • 확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.

Files:

  • src/main/java/gg/agit/konect/domain/notification/service/NotificationService.java
**/*

⚙️ CodeRabbit configuration file

**/*: 공통 리뷰 톤 가이드:

  • 모든 코멘트는 첫 줄에 [LEVEL: ...] 태그를 포함한다.
  • 과장된 표현 없이 사실 기반으로 작성한다.
  • 한 코멘트에는 하나의 이슈만 다룬다.
  • 코드 예시가 필요하면 최소 수정 예시를 제시한다.
  • 가독성/단순화/확장성 이슈를 발견하면 우선순위를 높여 코멘트한다.

Files:

  • src/main/java/gg/agit/konect/domain/notification/service/NotificationService.java
🔇 Additional comments (2)
src/main/java/gg/agit/konect/domain/notification/service/NotificationService.java (2)

76-124: LGTM - 트랜잭션 및 예외 처리 패턴이 적절합니다.

@Async 메서드에 @Transactional을 추가하여 읽기 전용 트랜잭션 문제를 해결했고, try-catch 블록으로 푸시 실패 시에도 인박스 저장이 롤백되지 않도록 처리되어 있습니다.


126-211: LGTM - 배치 알림 처리 로직이 적절합니다.

sendChatNotification과 동일한 패턴으로 트랜잭션과 예외 처리가 구성되어 있어 일관성이 유지됩니다.

@dh2906 dh2906 merged commit 6c81e4d into develop Mar 27, 2026
3 checks passed
@dh2906 dh2906 deleted the refactor/CAM-255-group-chat-nofity-async branch March 27, 2026 14:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

리팩토링 리팩터링을 위한 이슈입니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant