Skip to content

[REFACTOR] DB Flush 호출 최소화 및 전역 Soft Delete 적용#102

Merged
ydking0911 merged 6 commits into
developfrom
refactor/#101/db-flush-호출-최소화-및-전역-soft-delete-적용
May 6, 2026

Hidden character warning

The head ref may contain hidden characters: "refactor/#101/db-flush-\ud638\ucd9c-\ucd5c\uc18c\ud654-\ubc0f-\uc804\uc5ed-soft-delete-\uc801\uc6a9"
Merged

[REFACTOR] DB Flush 호출 최소화 및 전역 Soft Delete 적용#102
ydking0911 merged 6 commits into
developfrom
refactor/#101/db-flush-호출-최소화-및-전역-soft-delete-적용

Conversation

@ydking0911
Copy link
Copy Markdown
Member

@ydking0911 ydking0911 commented May 4, 2026

📌 제목

[REFACTOR] DB Flush 호출 최소화 및 전역 Soft Delete 적용


📢 요약

트랜잭션 정합성 문제를 일으키던 명시적 flush() 호출을 제거하고, JPA 자동 flush 메커니즘으로 대체했습니다.

  • Roommate, ChatRoomMember, RoomRequest, RoomLike, RoomRule, ChatRoom, ChatMessage 등 하드 삭제(Hard Delete)로 처리되던 모든 엔티티를 deletedAt 기반 소프트 삭제(Soft Delete)로 전환
  • @SQLRestriction("deleted_at is null")을 적용해 SELECT 쿼리에서 삭제된 레코드가 자동 필터링되도록 함
    • 연관 유니크 제약 조건은 WHERE deleted_at IS NULL 조건을 포함한 Partial Index로 교체

🔗 연관 이슈: Resolves #101


🚀 PR 유형

해당하는 항목에 체크해주세요.

  • 🔨 코드 리팩토링
  • 🐛 버그 수정
  • 🧪 테스트 추가 또는 리팩토링

✅ PR 체크리스트

PR이 다음 요구 사항을 충족하는지 확인해주세요.

  • 🔹 커밋 메시지 컨벤션을 준수했습니다.
  • 🔹 변경 사항에 대한 테스트를 수행했습니다. (버그 수정/기능 테스트)
  • 🔹 관련 문서를 업데이트했습니다. (필요한 경우)

📜 상세 설명

1. 명시적 flush() 제거

기존에 flush()가 필요했던 이유

KickRoommateUseCaseDeleteRoomUseCase에서 엔티티 변경(room.minusCurrentMate(), room.delete()) 후 바로 @Modifying 벌크 JPQL을 실행하는 구조였습니다.

JPA의 기본 FlushMode.AUTOSELECT 직전에 동일 테이블이 연관된 경우에만 자동 flush합니다. 벌크 UPDATE/DELETE JPQL이 실행되는 시점에는 flush가 보장되지 않기 때문에, 1차 캐시에만 존재하는 변경 사항이 DB에 반영되지 않은 상태로 벌크 쿼리가 실행됩니다.

이로 인해 발생하던 문제:

UseCase 문제
KickRoommateUseCase room.minusCurrentMate()가 캐시에만 존재하는 상태에서 BEFORE_COMMIT 리스너의 decreaseUnreadCount(JPQL)가 실행되어 DB의 stale 값을 기준으로 처리됨
DeleteRoomUseCase room.delete()(deletedAt 설정)가 캐시에만 존재하는 상태에서 이후 벌크 삭제 쿼리가 실행되어 정합성 깨질 수 있음

이를 임시로 해결하기 위해 PR #93 에서 각 변경 직후 roomService.flush()를 명시적으로 호출했습니다.

이번에 제거할 수 있었던 이유

@Modifying(flushAutomatically = true)FlushMode.AUTO와 다르게, 쿼리 실행 전 EntityManager 전체를 무조건 flush합니다. 이를 활용해 flush 타이밍을 보장하는 구조로 전환했습니다.

UseCase 자동 flush 트리거
KickRoommateUseCase @TransactionalEventListener(BEFORE_COMMIT)decreaseUnreadCount(@Modifying flushAutomatically=true)가 실행되기 전 EntityManager 전체 flush → minusCurrentMate() 변경이 DB에 반영된 뒤 쿼리 실행
DeleteRoomUseCase @Modifying(flushAutomatically=true) 쿼리(deleteAllByRoom) 실행 시 room.delete() 포함 전체 flush

RoomService.flush() 메서드 자체도 함께 제거했습니다.

검증은 Testcontainers 대신 로컬 Docker PostgreSQL(dorumdorum_test)에 직접 연결하는 통합 테스트(FlushRemovalIntegrationTest)를 신규 작성해 수행했습니다. (Testcontainers 1.19.7 + Docker Desktop 환경에서 docker-java 클라이언트가 HTTP 400을 반환하는 호환성 문제로 Testcontainers를 사용하지 않았습니다.)


2. 전역 Soft Delete 전환

하드 삭제로 처리되던 모든 엔티티 및 연산을 deletedAt 기반 소프트 삭제로 전환했습니다.

엔티티별 변경 사항

엔티티 @SQLRestriction 추가 @UniqueConstraint 제거 Partial Index 추가
Roommate uk_roommate_user_no WHERE deleted_at IS NULL
ChatRoomMember uk_chat_room_member_room_user WHERE deleted_at IS NULL
RoomRequest uk_room_request_user_room_direction WHERE deleted_at IS NULL
RoomLike uk_room_like_user_room WHERE deleted_at IS NULL
RoomRule
ChatRoom uk_chat_room_group/directAND deleted_at IS NULL 조건 추가
ChatMessage

RoomRuleChecklistBase(@MappedSuperclass)를 상속하며 BaseEntity를 상속하지 않아 deletedAt 필드가 없었습니다. ChecklistBasedeletedAt 필드와 delete() 메서드를 추가했습니다.

서비스 레이어 변경 (개별 삭제)

// Before
roommateRepository.delete(roommate)

// After
roommate.delete()
roommateRepository.save(roommate)

동일 패턴을 RoommateService.leaveRoom(), ChatRoomMemberService.leave(), ChatRoomService.delete(), RoomRequestService.delete(), RoomRequestService.cancelJoinRequest()에 적용했습니다.

Repository 레이어 변경 (벌크 삭제)

// Before
@Query("DELETE FROM RoomRequest r WHERE r.room = :room")
void deleteAllByRoom(Room room);

// After
@Query("UPDATE RoomRequest r SET r.deletedAt = CURRENT_TIMESTAMP WHERE r.room = :room AND r.deletedAt IS NULL")
void deleteAllByRoom(Room room);

동일 패턴을 RoomLikeRepository, ChatRoomMemberRepository, ChatRoomRepository, ChatMessageRepository, RoomRuleRepository에 적용했습니다.

RoomLikeRepository.deleteByUserNoAndRoom은 Spring Data 파생 메서드(하드 삭제)에서 @Modifying @query 소프트 삭제로 교체했습니다.


3. @SQLRestriction 적용 효과

@SQLRestriction("deleted_at is null")은 해당 엔티티에 대한 모든 SELECT 쿼리에 AND deleted_at IS NULL 조건을 자동으로 추가합니다. 벌크 DML(UPDATE/DELETE JPQL)에는 적용되지 않으므로, 벌크 쿼리에는 조건을 명시적으로 추가했습니다.

주의: Room 엔티티는 기존 커스텀 쿼리에 이미 deleted_at IS NULL 필터가 포함되어 있어 @SQLRestriction을 추가하지 않았습니다. (findById로 소프트 삭제된 Room을 직접 조회해야 하는 케이스가 존재하기 때문)

Summary by CodeRabbit

  • Refactor

    • 여러 엔티티와 관련 흐름이 하드 삭제에서 deleted_at 기반 소프트 삭제로 전환되어 삭제된 항목이 쿼리에서 제외되고 관련 서비스 호출 방식이 변경되었습니다.
    • DB 제약·인덱스와 스키마가 소프트 삭제를 반영하도록 조정되었습니다.
    • 일부 서비스의 명시적 flush 호출이 제거되었습니다.
  • Tests

    • 소프트 삭제 동작과 flush 제거를 검증하는 단위·통합 테스트가 추가·수정되었습니다.
  • Chores

    • CI/빌드 설정에 통합 테스트 건너뛰기 옵션이 추가되었습니다.

@ydking0911 ydking0911 self-assigned this May 4, 2026
@ydking0911 ydking0911 requested review from KoungQ and Copilot and removed request for KoungQ May 4, 2026 10:10
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

Walkthrough

여러 도메인 엔티티에 Hibernate 수준의 @SQLRestriction("deleted_at is null") 필터를 추가하고, JPQL의 하드 삭제를 bulk UPDATE ... SET deletedAt = CURRENT_TIMESTAMP 형태의 soft-delete로 전환했습니다. 서비스 계층은 repository.delete(entity) 호출을 entity.delete(); repository.save(entity)로 변경했으며, 일부 명시적 flush() 호출을 제거하고 스키마의 partial unique 인덱스를 deleted_at IS NULL 조건으로 재생성했습니다.

Changes

Soft-Delete 통합 (Entity → Service → Repository → Schema → Tests)

Layer / File(s) Summary
Entity Annotation
src/main/java/.../chat/domain/entity/ChatMessage.java, .../ChatRoom.java, .../ChatRoomMember.java, .../room/domain/entity/RoomLike.java, .../RoomRequest.java, .../roommate/domain/entity/Roommate.java, .../checklist/domain/entity/RoomRule.java
엔티티 클래스들에 @SQLRestriction("deleted_at is null") 추가로 Hibernate 레벨에서 soft-deleted 행을 자동 제외하도록 설정했습니다.
Entity Field / API
src/main/java/.../checklist/domain/entity/ChecklistBase.java
deletedAt 컬럼 매핑(필요 시 추가)과 public boolean isDeleted(), public void delete() 메서드를 추가해 엔티티 수준의 soft-delete 상태를 조작하도록 했습니다.
Service Layer Deletion Semantics
src/main/java/.../chat/domain/service/ChatRoomMemberService.java, .../chat/domain/service/ChatRoomService.java, .../room/domain/service/RoomRequestService.java, .../roommate/domain/service/RoommateService.java
서비스에서 직접 repository 삭제 호출을 제거하고, entity.delete()repository.save(entity)를 호출하도록 변경했습니다(soft-delete 인스턴스 상태 변경 후 영속화).
Repository Bulk Delete → Soft-Update
src/main/java/.../chat/domain/repository/ChatMessageRepository.java, ChatRoomMemberRepository.java, ChatRoomRepository.java, src/main/java/.../room/domain/repository/RoomLikeRepository.java, RoomRequestRepository.java, src/main/java/.../checklist/domain/repository/RoomRuleRepository.java
기존 JPQL DELETE FROM ... 구문을 @Modifying 붙인 UPDATE ... SET <entity>.deletedAt = CURRENT_TIMESTAMP WHERE ... AND deletedAt IS NULL 형태의 bulk soft-delete로 변경했습니다. 필요한 곳에 clearAutomatically = true, flushAutomatically = true를 유지/추가했습니다.
Use Case / Flush 제거
src/main/java/.../room/application/usecase/DeleteRoomUseCase.java, KickRoommateUseCase.java, src/main/java/.../room/domain/service/RoomService.java
명시적 roomService.flush() 메서드 제거 및 호출 제거로 트랜잭션 종료 시점의 자동 flush에 의존하도록 변경했습니다. 관련 테스트의 flush 검증도 제거/조정되었습니다.
Database Schema
src/main/resources/schema.sql
soft-delete 일관성 확보를 위해 room_ruledeleted_at 칼럼 추가 및 기존 유니크 제약/인덱스를 드랍 후 WHERE deleted_at IS NULL 조건을 포함한 partial unique index로 재생성했습니다(여러 테이블).
Tests / Test infra
src/test/java/.../room/integration/FlushRemovalIntegrationTest.java, .../chat/integration/*, .../room/integration/*, 여러 단위 테스트들
단위/통합 테스트를 soft-delete 동작에 맞게 수정했고, 통합 테스트는 DB 초기화를 위해 JdbcTemplate 기반 TRUNCATE ... RESTART IDENTITY CASCADE 유틸을 도입했습니다. 새로운 통합 테스트(FlushRemovalIntegrationTest)를 추가해 flush 제거 후에도 영속성 결과가 반영되는지 검증합니다.

Sequence Diagram

sequenceDiagram
    participant Client
    participant UseCase as DeleteRoomUseCase
    participant RoomService as Room Service
    participant RoommateService as Roommate Service
    participant Repo as JPA Repository
    participant DB as Database

    Client->>UseCase: execute(requesterNo, roomNo)
    UseCase->>RoomService: room.delete()
    RoomService->>Repo: save(room) 
    Repo->>DB: UPDATE room SET deleted_at = NOW() WHERE ... AND deleted_at IS NULL
    UseCase->>RoommateService: leaveRoom(requesterNo, roomNo)
    RoommateService->>Repo: save(roommate)
    Repo->>DB: UPDATE roommate SET deleted_at = NOW() WHERE ... AND deleted_at IS NULL
    UseCase->>Repo: deleteAllByRoom (RoomRequest/RoomRule/RoomLike)
    Repo->>DB: UPDATE ... SET deleted_at = NOW() WHERE ... AND deleted_at IS NULL
    UseCase->>UseCase: publishEvent(RoomDeletedEvent)
    DB-->>Client: Commit success
Loading
sequenceDiagram
    participant Query
    participant SQLRestriction as SQLRestriction Filter
    participant DB as Database

    Query->>DB: SELECT * FROM chat_message
    DB-->>SQLRestriction: apply filter
    SQLRestriction->>DB: SELECT * FROM chat_message WHERE deleted_at IS NULL
    DB-->>Query: filtered rows
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • DorumDorum/BE#100: 동일한 chat/room 삭제 경로와 soft-delete 적용 관련 변경을 포함합니다.
  • DorumDorum/BE#84: 스키마·엔티티 매핑 및 partial unique index 변경과 연관성이 높습니다.
  • DorumDorum/BE#77: chat 도메인 엔티티 및 삭제 동작(soft-delete) 관련 변경과 코드 레벨로 중복/연관됩니다.

Suggested reviewers

  • KoungQ
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목 '[REFACTOR] DB Flush 호출 최소화 및 전역 Soft Delete 적용'은 변경 사항의 핵심을 명확하게 반영하고 있습니다. DB flush 최소화와 soft delete 적용이 이 PR의 주요 변경 사항이며, 제목에 정확하게 드러나 있습니다.
Linked Issues check ✅ Passed 변경 사항들이 이슈 #101의 요구사항을 충족하고 있습니다. (1) flush() 호출 최소화: RoomService.flush() 제거, @Modifying(flushAutomatically=true) 적용 (2) Hard Delete → Soft Delete: 7개 엔티티에 @SQLRestriction 적용, repository.delete() → entity.delete() + repository.save() 패턴 변경 (3) Partial Index로 유니크 제약 재구성 (4) 통합 테스트 추가로 검증.
Out of Scope Changes check ✅ Passed 모든 변경 사항이 soft delete 적용 및 flush 최소화 범위 내에 있습니다. 테스트 헬퍼 메서드(truncateTables), CI/CD 설정 변경(integration test 조건 실행), build.gradle 수정도 이 리팩토링을 지원하는 필수 변경으로 평가됩니다.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/#101/db-flush-호출-최소화-및-전역-soft-delete-적용

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

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the room/chat deletion flows to rely on JPA-triggered flushes instead of explicit flush() calls, and converts several previously hard-deleted entities to deletedAt-based soft delete with @SQLRestriction filtering. In the codebase, this mainly affects room lifecycle, chat membership cleanup, and the repository/query contract for entities that can now be logically deleted.

Changes:

  • Removed explicit roomService.flush() calls from room deletion/kick flows and updated tests around the new ordering assumptions.
  • Switched multiple delete paths from hard delete to soft delete (entity.delete() / bulk UPDATE ... SET deletedAt) across room, chat, request, like, roommate, and rule-related entities.
  • Added schema/index updates and new integration/unit tests intended to verify flush timing and soft-delete behavior.

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/test/java/com/project/dorumdorum/domain/room/unit/usecase/KickRoommateUseCaseTest.java Updated unit test expectations after removing explicit flush from kick flow.
src/test/java/com/project/dorumdorum/domain/room/unit/usecase/DeleteRoomUseCaseTest.java Updated room deletion tests for new cascade ordering without explicit flush.
src/test/java/com/project/dorumdorum/domain/room/unit/service/RoomRequestServiceTest.java Adjusted tests to expect soft delete/save instead of repository hard delete.
src/test/java/com/project/dorumdorum/domain/room/integration/FlushRemovalIntegrationTest.java Added integration test for flush removal and DB persistence behavior.
src/test/java/com/project/dorumdorum/domain/chat/unit/service/ChatRoomServiceTest.java Updated chat room service test to expect soft delete/save semantics.
src/test/java/com/project/dorumdorum/domain/chat/unit/service/ChatRoomMemberServiceTest.java Updated chat room member service test to expect soft delete/save semantics.
src/main/resources/schema.sql Replaced several global unique indexes with partial indexes for soft-deleted rows.
src/main/java/com/project/dorumdorum/domain/roommate/domain/service/RoommateService.java Changed room leave behavior from hard delete to soft delete + save.
src/main/java/com/project/dorumdorum/domain/roommate/domain/entity/Roommate.java Added global SQL restriction and removed table-level unique constraint mapping.
src/main/java/com/project/dorumdorum/domain/room/domain/service/RoomService.java Removed explicit repository flush helper.
src/main/java/com/project/dorumdorum/domain/room/domain/service/RoomRequestService.java Converted request deletion/cancel flows to soft delete.
src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomRequestRepository.java Replaced bulk hard delete with bulk soft-delete update.
src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomLikeRepository.java Replaced hard deletes with soft-delete JPQL updates for likes.
src/main/java/com/project/dorumdorum/domain/room/domain/entity/RoomRequest.java Added SQL restriction and removed hard unique constraint mapping.
src/main/java/com/project/dorumdorum/domain/room/domain/entity/RoomLike.java Added SQL restriction and removed hard unique constraint mapping.
src/main/java/com/project/dorumdorum/domain/room/application/usecase/KickRoommateUseCase.java Removed explicit flush from roommate kick flow.
src/main/java/com/project/dorumdorum/domain/room/application/usecase/DeleteRoomUseCase.java Removed explicit flush from room deletion flow and updated docs.
src/main/java/com/project/dorumdorum/domain/checklist/domain/repository/RoomRuleRepository.java Switched room rule bulk delete to soft-delete update.
src/main/java/com/project/dorumdorum/domain/checklist/domain/entity/RoomRule.java Added SQL restriction for soft-deleted room rules.
src/main/java/com/project/dorumdorum/domain/checklist/domain/entity/ChecklistBase.java Added deletedAt state and delete helpers to checklist-based entities.
src/main/java/com/project/dorumdorum/domain/chat/domain/service/ChatRoomService.java Converted chat room deletion to soft delete + save.
src/main/java/com/project/dorumdorum/domain/chat/domain/service/ChatRoomMemberService.java Converted chat member leave flow to soft delete + save.
src/main/java/com/project/dorumdorum/domain/chat/domain/repository/ChatRoomRepository.java Replaced chat room hard delete with soft-delete update.
src/main/java/com/project/dorumdorum/domain/chat/domain/repository/ChatRoomMemberRepository.java Replaced chat room member bulk delete with soft-delete update.
src/main/java/com/project/dorumdorum/domain/chat/domain/repository/ChatMessageRepository.java Added flushAutomatically to delete path and switched to soft delete for messages.
src/main/java/com/project/dorumdorum/domain/chat/domain/entity/ChatRoomMember.java Added SQL restriction and removed hard unique constraint mapping.
src/main/java/com/project/dorumdorum/domain/chat/domain/entity/ChatRoom.java Added SQL restriction for soft-deleted chat rooms.
src/main/java/com/project/dorumdorum/domain/chat/domain/entity/ChatMessage.java Added SQL restriction for soft-deleted chat messages.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +126 to +127
@Column(name = "deleted_at")
private LocalDateTime deletedAt;
Comment on lines +7 to +28
DROP INDEX IF EXISTS uk_roommate_user_no;
CREATE UNIQUE INDEX IF NOT EXISTS uk_roommate_user_no
ON roommate (user_no);
ON roommate (user_no)
WHERE deleted_at IS NULL;

DROP INDEX IF EXISTS uk_room_like_user_room;
CREATE UNIQUE INDEX IF NOT EXISTS uk_room_like_user_room
ON room_like (user_no, room_no);
ON room_like (user_no, room_no)
WHERE deleted_at IS NULL;

CREATE UNIQUE INDEX IF NOT EXISTS uk_device_user_device
ON devices (user_no, device_id);

DROP INDEX IF EXISTS uk_room_request_user_room_direction;
CREATE UNIQUE INDEX IF NOT EXISTS uk_room_request_user_room_direction
ON room_request (user_no, room_no, direction);
ON room_request (user_no, room_no, direction)
WHERE deleted_at IS NULL;

DROP INDEX IF EXISTS uk_chat_room_member_room_user;
CREATE UNIQUE INDEX IF NOT EXISTS uk_chat_room_member_room_user
ON chat_room_member (chat_room_no, user_no)
WHERE deleted_at IS NULL;
Comment on lines +58 to +70
* @ActiveProfiles("local-db"): 로컬 Docker PostgreSQL(dorumdorum_test DB)에 직접 연결.
* Testcontainers 없이 실제 DB로 동작을 검증한다.
*/
@SpringBootTest(properties = {
"spring.datasource.driver-class-name=org.postgresql.Driver",
"spring.datasource.url=jdbc:postgresql://localhost:5432/dorumdorum_test",
"spring.datasource.username=dorumdorum",
"spring.datasource.password=test1234",
"spring.jpa.defer-datasource-initialization=true",
"spring.jpa.hibernate.ddl-auto=create-drop",
"spring.sql.init.mode=always",
"spring.sql.init.schema-locations=classpath:schema.sql"
})
Comment on lines 66 to +70
public void leaveRoom(String userNo, String roomNo) {
Roommate roommate = roommateRepository.findByUserNoAndRoomNo(userNo, roomNo)
.orElseThrow(() -> new RestApiException(_NOT_FOUND));
// uk_roommate_user_no 유니크 제약으로 인해 소프트 삭제 불가 — 하드 삭제 유지
roommateRepository.delete(roommate);
roommate.delete();
roommateRepository.save(roommate);
ON room_request (user_no, room_no, direction)
WHERE deleted_at IS NULL;

DROP INDEX IF EXISTS uk_chat_room_member_room_user;
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: 5

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/com/project/dorumdorum/domain/checklist/domain/entity/RoomRule.java (1)

33-33: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

소프트 삭제와 충돌하는 일반 유니크 제약이 Partial Index로 교체되지 않았습니다.

@Index(unique = true) (Line 33)와 @JoinColumn(unique = true) (Line 51) 두 곳 모두 일반 유니크 제약으로 남아 있습니다. PR 설명에서 "연관 유니크 제약은 WHERE deleted_at IS NULL을 포함한 Partial Index로 교체함"이라고 명시했지만, 실제 코드에는 반영되지 않았습니다.

"Both deleted and live entities will share unique constraints and indexes. Hence, creating regular indexes will not work any longer."

소프트 삭제된 RoomRule 행은 room_no 값을 그대로 유지하면서 테이블에 남아 있기 때문에, 같은 방에 대해 새로운 RoomRule을 생성하려 하면 유니크 제약 위반이 발생합니다. ddl-auto=create-drop을 사용하는 통합 테스트 환경에서도 Hibernate가 일반 유니크 제약을 그대로 생성하므로 동일한 문제가 발생합니다.

🛠️ 수정 방향 제안

1. 어노테이션에서 unique = true 제거

  `@Index`(name = "idx_room_rule_updated_at", columnList = "updated_at"),
- `@Index`(name = "uk_room_rule_room_no", columnList = "room_no", unique = true)
+ `@Index`(name = "idx_room_rule_room_no", columnList = "room_no")
  `@JoinColumn`(
      name = "room_no",
      nullable = false,
-     unique = true,
      foreignKey = `@ForeignKey`(name = "fk_room_rule_room")
  )

2. DB 마이그레이션 스크립트(Flyway/Liquibase)에 Partial Index 추가

-- 기존 일반 유니크 제약 제거 후 Partial Index 생성
DROP INDEX IF EXISTS uk_room_rule_room_no;
CREATE UNIQUE INDEX uk_room_rule_room_no
    ON room_rule(room_no)
    WHERE deleted_at IS NULL;

Also applies to: 47-53

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/project/dorumdorum/domain/checklist/domain/entity/RoomRule.java`
at line 33, The RoomRule entity still declares regular unique constraints which
conflict with soft-delete: remove unique=true from the `@Index` on class RoomRule
(the `@Index`(name = "uk_room_rule_room_no", columnList = "room_no", unique =
true)) and from the `@JoinColumn`(unique = true) on the field referencing room_no
so Hibernate does not create a normal UNIQUE constraint; then add a DB migration
(Flyway/Liquibase) that drops the old uk_room_rule_room_no constraint if present
and creates a partial unique index on room_rule(room_no) WHERE deleted_at IS
NULL to enforce uniqueness only for non-deleted rows.
🤖 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/com/project/dorumdorum/domain/checklist/domain/entity/ChecklistBase.java`:
- Around line 126-135: ChecklistBase currently reimplements the soft-delete
fields/methods (deletedAt, isDeleted(), delete()) already present in BaseEntity;
refactor by either making class ChecklistBase extend BaseEntity and removing the
duplicated deletedAt/isDeleted()/delete() members, or extract those members into
a new `@MappedSuperclass` (e.g., SoftDeletableEntity) and have ChecklistBase
extend that; ensure the `@Column`(name = "deleted_at") mapping and any
auditing/timezone handling are moved to the chosen superclass and remove the
duplicate definitions from ChecklistBase so only the superclass owns the
soft-delete behavior.

In
`@src/main/java/com/project/dorumdorum/domain/room/domain/entity/RoomLike.java`:
- Around line 15-18: RoomLike 엔티티의 `@Table` 어노테이션에 빈 indexes 속성이 남아 있어 불필요하게 보입니다;
RoomLike 클래스에서 `@Table`(...)을 찾아 indexes 속성이 비어있으면 해당 속성 전체를 제거해 어노테이션을 간결하게 하거나,
만약 DB 스키마에서 Partial Index를 별도 관리 중이라 유지해야 한다면 주석으로 이유를 명시해두세요.

In
`@src/test/java/com/project/dorumdorum/domain/room/integration/FlushRemovalIntegrationTest.java`:
- Around line 63-65: The test currently hardcodes DB credentials in the embedded
properties array inside FlushRemovalIntegrationTest (the
"spring.datasource.username=dorumdorum" and
"spring.datasource.password=test1234" entries); remove these plaintext values
and externalize them by one of the following: load from
application-test.properties placeholders (e.g.
"spring.datasource.username=${DB_USERNAME}" and
"spring.datasource.password=${DB_PASSWORD}") and set env vars in CI/local, or
inject values dynamically via Testcontainers or a `@DynamicPropertySource` method
in FlushRemovalIntegrationTest that registers the JDBC URL/username/password
programmatically from System.getenv() or the Testcontainer instance; update the
test setup to reference the new placeholders or dynamic properties instead of
the hardcoded strings.
- Around line 61-70: The current SpringBootTest properties hardcode
localhost:5432 causing CI failures; update FlushRemovalIntegrationTest to use
Testcontainers by annotating the class with `@Testcontainers`, adding a static
PostgreSQLContainer<?> (e.g., named postgresContainer) and a
`@DynamicPropertySource` method that registers spring.datasource.url,
spring.datasource.username, and spring.datasource.password from
postgresContainer.getJdbcUrl()/getUsername()/getPassword(); ensure you remove or
stop using the hardcoded properties block so the test spins up an isolated DB in
CI. As a short-term alternative, wrap the test class with
`@EnabledIfEnvironmentVariable` or `@EnabledIfSystemProperty` to skip the test when
no external Postgres is available.
- Around line 176-183: The test
delete_SoftDeletedAt_PersistedWithoutExplicitFlush currently uses
roomRepository.findById(...).orElseThrow() which could break silently if a
future `@SQLRestriction` hides soft-deleted rows; change the verification to query
the DB directly via jdbcTemplate (bypass JPA filters) after calling
deleteRoomUseCase.execute(DELETE_HOST_NO, deleteRoom.getRoomNo()) and assert the
deleted_at column is not null for the given room_no. Locate the test method
(delete_SoftDeletedAt_PersistedWithoutExplicitFlush) and replace the
roomRepository lookup with a jdbcTemplate query that selects deleted_at by
room_no and assert its non-nullness to avoid orElseThrow masking the intended
assertion.

---

Outside diff comments:
In
`@src/main/java/com/project/dorumdorum/domain/checklist/domain/entity/RoomRule.java`:
- Line 33: The RoomRule entity still declares regular unique constraints which
conflict with soft-delete: remove unique=true from the `@Index` on class RoomRule
(the `@Index`(name = "uk_room_rule_room_no", columnList = "room_no", unique =
true)) and from the `@JoinColumn`(unique = true) on the field referencing room_no
so Hibernate does not create a normal UNIQUE constraint; then add a DB migration
(Flyway/Liquibase) that drops the old uk_room_rule_room_no constraint if present
and creates a partial unique index on room_rule(room_no) WHERE deleted_at IS
NULL to enforce uniqueness only for non-deleted rows.
🪄 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.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2141d1bd-04da-414a-ac3c-4a40e262313c

📥 Commits

Reviewing files that changed from the base of the PR and between e3cf928 and 57c4702.

📒 Files selected for processing (28)
  • src/main/java/com/project/dorumdorum/domain/chat/domain/entity/ChatMessage.java
  • src/main/java/com/project/dorumdorum/domain/chat/domain/entity/ChatRoom.java
  • src/main/java/com/project/dorumdorum/domain/chat/domain/entity/ChatRoomMember.java
  • src/main/java/com/project/dorumdorum/domain/chat/domain/repository/ChatMessageRepository.java
  • src/main/java/com/project/dorumdorum/domain/chat/domain/repository/ChatRoomMemberRepository.java
  • src/main/java/com/project/dorumdorum/domain/chat/domain/repository/ChatRoomRepository.java
  • src/main/java/com/project/dorumdorum/domain/chat/domain/service/ChatRoomMemberService.java
  • src/main/java/com/project/dorumdorum/domain/chat/domain/service/ChatRoomService.java
  • src/main/java/com/project/dorumdorum/domain/checklist/domain/entity/ChecklistBase.java
  • src/main/java/com/project/dorumdorum/domain/checklist/domain/entity/RoomRule.java
  • src/main/java/com/project/dorumdorum/domain/checklist/domain/repository/RoomRuleRepository.java
  • src/main/java/com/project/dorumdorum/domain/room/application/usecase/DeleteRoomUseCase.java
  • src/main/java/com/project/dorumdorum/domain/room/application/usecase/KickRoommateUseCase.java
  • src/main/java/com/project/dorumdorum/domain/room/domain/entity/RoomLike.java
  • src/main/java/com/project/dorumdorum/domain/room/domain/entity/RoomRequest.java
  • src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomLikeRepository.java
  • src/main/java/com/project/dorumdorum/domain/room/domain/repository/RoomRequestRepository.java
  • src/main/java/com/project/dorumdorum/domain/room/domain/service/RoomRequestService.java
  • src/main/java/com/project/dorumdorum/domain/room/domain/service/RoomService.java
  • src/main/java/com/project/dorumdorum/domain/roommate/domain/entity/Roommate.java
  • src/main/java/com/project/dorumdorum/domain/roommate/domain/service/RoommateService.java
  • src/main/resources/schema.sql
  • src/test/java/com/project/dorumdorum/domain/chat/unit/service/ChatRoomMemberServiceTest.java
  • src/test/java/com/project/dorumdorum/domain/chat/unit/service/ChatRoomServiceTest.java
  • src/test/java/com/project/dorumdorum/domain/room/integration/FlushRemovalIntegrationTest.java
  • src/test/java/com/project/dorumdorum/domain/room/unit/service/RoomRequestServiceTest.java
  • src/test/java/com/project/dorumdorum/domain/room/unit/usecase/DeleteRoomUseCaseTest.java
  • src/test/java/com/project/dorumdorum/domain/room/unit/usecase/KickRoommateUseCaseTest.java
💤 Files with no reviewable changes (3)
  • src/test/java/com/project/dorumdorum/domain/room/unit/usecase/KickRoommateUseCaseTest.java
  • src/main/java/com/project/dorumdorum/domain/room/domain/service/RoomService.java
  • src/main/java/com/project/dorumdorum/domain/room/application/usecase/KickRoommateUseCase.java

Comment on lines +126 to +135
@Column(name = "deleted_at")
private LocalDateTime deletedAt;

public boolean isDeleted() {
return deletedAt != null;
}

public void delete() {
deletedAt = LocalDateTime.now();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚖️ Poor tradeoff

BaseEntity와 동일한 소프트 삭제 패턴이 중복 구현되어 있습니다.

deletedAt, isDeleted(), delete()BaseEntity에 이미 동일하게 정의된 패턴입니다. ChecklistBaseBaseEntity를 상속하지 않기 때문에 부득이하게 중복이 발생했지만, 향후 두 구현이 따로 관리되면 타임존 처리나 감사(Auditing) 로직 변경 시 한 쪽이 누락될 위험이 있습니다.

ChecklistBaseBaseEntity를 상속하도록 리팩토링하거나, 소프트 삭제 로직을 별도의 @MappedSuperclass(예: SoftDeletableEntity)로 추출하는 방향을 장기적으로 검토해 보시면 좋겠습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/project/dorumdorum/domain/checklist/domain/entity/ChecklistBase.java`
around lines 126 - 135, ChecklistBase currently reimplements the soft-delete
fields/methods (deletedAt, isDeleted(), delete()) already present in BaseEntity;
refactor by either making class ChecklistBase extend BaseEntity and removing the
duplicated deletedAt/isDeleted()/delete() members, or extract those members into
a new `@MappedSuperclass` (e.g., SoftDeletableEntity) and have ChecklistBase
extend that; ensure the `@Column`(name = "deleted_at") mapping and any
auditing/timezone handling are moved to the chosen superclass and remove the
duplicate definitions from ChecklistBase so only the superclass owns the
soft-delete behavior.

Comment on lines 15 to 18
@Table(
uniqueConstraints = {
@UniqueConstraint(name = "uk_room_like_user_room", columnNames = {"user_no", "room_no"})
},
indexes = {
}
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

indexes 블록 정리를 고려해 주세요.

현재 인덱스가 정의되어 있지 않다면 @Table 어노테이션에서 indexes 속성을 제거하여 코드를 간결하게 유지할 수 있습니다. 다만 스키마에서 Partial Index를 별도로 관리하고 있다면 현재 상태로 두셔도 무방합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/project/dorumdorum/domain/room/domain/entity/RoomLike.java`
around lines 15 - 18, RoomLike 엔티티의 `@Table` 어노테이션에 빈 indexes 속성이 남아 있어 불필요하게
보입니다; RoomLike 클래스에서 `@Table`(...)을 찾아 indexes 속성이 비어있으면 해당 속성 전체를 제거해 어노테이션을 간결하게
하거나, 만약 DB 스키마에서 Partial Index를 별도 관리 중이라 유지해야 한다면 주석으로 이유를 명시해두세요.

Comment on lines +176 to +183
void delete_SoftDeletedAt_PersistedWithoutExplicitFlush() {
deleteRoomUseCase.execute(DELETE_HOST_NO, deleteRoom.getRoomNo());

Room persisted = roomRepository.findById(deleteRoom.getRoomNo()).orElseThrow();
assertThat(persisted.getDeletedAt())
.as("room.delete()가 flush 없이도 deletedAt이 DB에 반영되어야 한다")
.isNotNull();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

findById().orElseThrow() 호출이 향후 @SQLRestriction 추가 시 오해를 유발할 수 있습니다

현재 Room 엔티티에는 @SQLRestriction이 없으므로 소프트 삭제 후에도 findById가 정상적으로 Row를 반환합니다. 그러나 PR 방향성을 고려할 때 추후 Room에도 @SQLRestriction("deleted_at is null")이 추가될 경우, orElseThrow()NoSuchElementException을 던지고 의도한 assertion 메시지 없이 테스트가 실패하게 됩니다.

deletedAt 값을 검증할 때는 JPA 필터를 우회하는 jdbcTemplate을 사용하는 것이 더 안정적입니다.

🛡️ jdbcTemplate으로 직접 검증하는 방식
 `@Test`
 `@DisplayName`("방 삭제 후 room.deletedAt이 DB에 반영된다 (explicit flush 없이)")
 void delete_SoftDeletedAt_PersistedWithoutExplicitFlush() {
     deleteRoomUseCase.execute(DELETE_HOST_NO, deleteRoom.getRoomNo());

-    Room persisted = roomRepository.findById(deleteRoom.getRoomNo()).orElseThrow();
-    assertThat(persisted.getDeletedAt())
-            .as("room.delete()가 flush 없이도 deletedAt이 DB에 반영되어야 한다")
-            .isNotNull();
+    String deletedAt = jdbcTemplate.queryForObject(
+            "SELECT deleted_at FROM room WHERE room_no = ?",
+            String.class, deleteRoom.getRoomNo());
+    assertThat(deletedAt)
+            .as("room.delete()가 flush 없이도 deletedAt이 DB에 반영되어야 한다")
+            .isNotNull();
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void delete_SoftDeletedAt_PersistedWithoutExplicitFlush() {
deleteRoomUseCase.execute(DELETE_HOST_NO, deleteRoom.getRoomNo());
Room persisted = roomRepository.findById(deleteRoom.getRoomNo()).orElseThrow();
assertThat(persisted.getDeletedAt())
.as("room.delete()가 flush 없이도 deletedAt이 DB에 반영되어야 한다")
.isNotNull();
}
`@Test`
`@DisplayName`("방 삭제 후 room.deletedAt이 DB에 반영된다 (explicit flush 없이)")
void delete_SoftDeletedAt_PersistedWithoutExplicitFlush() {
deleteRoomUseCase.execute(DELETE_HOST_NO, deleteRoom.getRoomNo());
String deletedAt = jdbcTemplate.queryForObject(
"SELECT deleted_at FROM room WHERE room_no = ?",
String.class, deleteRoom.getRoomNo());
assertThat(deletedAt)
.as("room.delete()가 flush 없이도 deletedAt이 DB에 반영되어야 한다")
.isNotNull();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/com/project/dorumdorum/domain/room/integration/FlushRemovalIntegrationTest.java`
around lines 176 - 183, The test
delete_SoftDeletedAt_PersistedWithoutExplicitFlush currently uses
roomRepository.findById(...).orElseThrow() which could break silently if a
future `@SQLRestriction` hides soft-deleted rows; change the verification to query
the DB directly via jdbcTemplate (bypass JPA filters) after calling
deleteRoomUseCase.execute(DELETE_HOST_NO, deleteRoom.getRoomNo()) and assert the
deleted_at column is not null for the given room_no. Locate the test method
(delete_SoftDeletedAt_PersistedWithoutExplicitFlush) and replace the
roomRepository lookup with a jdbcTemplate query that selects deleted_at by
room_no and assert its non-nullness to avoid orElseThrow masking the intended
assertion.

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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/resources/schema.sql`:
- Around line 9-30: The partial-index replacement for roommate, room_like,
room_request, and chat_room_member is unsafe because you only DROP CONSTRAINT IF
EXISTS (e.g. uk_roommate_user_no, uk_room_like_user_room,
uk_room_request_user_room_direction, uk_chat_room_member_room_user) but not DROP
INDEX IF EXISTS, which leaves an old full UNIQUE INDEX in place; update the
migration to mirror chat_room by also issuing DROP INDEX IF EXISTS for each
corresponding index name (uk_roommate_user_no, uk_room_like_user_room,
uk_room_request_user_room_direction, uk_chat_room_member_room_user) before
creating the new CREATE UNIQUE INDEX ... WHERE deleted_at IS NULL so that either
prior constraint or prior standalone index is removed.

In
`@src/test/java/com/project/dorumdorum/domain/chat/integration/ChatTransactionAtomicityIntegrationTest.java`:
- Around line 349-362: Multiple integration tests duplicate the same
truncateTables() implementation; extract it into a shared helper to avoid
repetitive maintenance. Create a common test base class (e.g.,
AbstractIntegrationTest) or a utility class (e.g., TestDbUtils) containing the
truncateTables() method and make it accessible (protected instance method on the
base class or a public static method on the util); then remove the duplicated
truncateTables() implementations from ChatTransactionAtomicityIntegrationTest,
KickRoommatePersistenceIntegrationTest, DeleteRoomPersistenceIntegrationTest,
and FlushRemovalIntegrationTest and call the shared method from each test class
(reference method name: truncateTables()).
🪄 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.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d00a5483-6a3a-4c4a-af08-fe0b134e7408

📥 Commits

Reviewing files that changed from the base of the PR and between 57c4702 and 6203a3a.

📒 Files selected for processing (8)
  • .github/workflows/cicd.yml
  • build.gradle
  • src/main/resources/schema.sql
  • src/test/java/com/project/dorumdorum/domain/chat/integration/ChatTransactionAtomicityIntegrationTest.java
  • src/test/java/com/project/dorumdorum/domain/room/integration/DeleteRoomPersistenceIntegrationTest.java
  • src/test/java/com/project/dorumdorum/domain/room/integration/FlushRemovalIntegrationTest.java
  • src/test/java/com/project/dorumdorum/domain/room/integration/KickRoommatePersistenceIntegrationTest.java
  • src/test/java/com/project/dorumdorum/domain/roommate/unit/service/RoommateServiceTest.java

Comment thread src/main/resources/schema.sql
Comment on lines +349 to +362
private void truncateTables() {
jdbcTemplate.execute("""
TRUNCATE TABLE
chat_message,
chat_room_member,
chat_room,
room_request,
room_rule,
room_like,
roommate,
room
RESTART IDENTITY CASCADE
""");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

truncateTables() 메서드가 4개 통합 테스트 클래스에 그대로 복사되어 있습니다

ChatTransactionAtomicityIntegrationTest, KickRoommatePersistenceIntegrationTest, DeleteRoomPersistenceIntegrationTest, FlushRemovalIntegrationTest 모두 동일한 truncateTables() 구현을 갖고 있습니다. 테이블 목록이 변경될 때 4곳을 일일이 수정해야 하는 유지보수 부담이 생깁니다.

공통 부모 클래스 또는 유틸리티 클래스로 추출하시는 것을 권장드립니다.

♻️ 공통 부모 클래스 추출 예시
// 신규: src/test/java/com/project/dorumdorum/testsupport/IntegrationTestBase.java
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+public abstract class IntegrationTestBase {
+
+    `@Autowired`
+    protected JdbcTemplate jdbcTemplate;
+
+    protected void truncateTables() {
+        jdbcTemplate.execute("""
+                TRUNCATE TABLE
+                    chat_message,
+                    chat_room_member,
+                    chat_room,
+                    room_request,
+                    room_rule,
+                    room_like,
+                    roommate,
+                    room
+                RESTART IDENTITY CASCADE
+                """);
+    }
+}
-class ChatTransactionAtomicityIntegrationTest {
+class ChatTransactionAtomicityIntegrationTest extends IntegrationTestBase {

-    `@Autowired` private JdbcTemplate jdbcTemplate;
     // ...

-    private void truncateTables() {
-        jdbcTemplate.execute("""
-                TRUNCATE TABLE
-                    ...
-                """);
-    }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/java/com/project/dorumdorum/domain/chat/integration/ChatTransactionAtomicityIntegrationTest.java`
around lines 349 - 362, Multiple integration tests duplicate the same
truncateTables() implementation; extract it into a shared helper to avoid
repetitive maintenance. Create a common test base class (e.g.,
AbstractIntegrationTest) or a utility class (e.g., TestDbUtils) containing the
truncateTables() method and make it accessible (protected instance method on the
base class or a public static method on the util); then remove the duplicated
truncateTables() implementations from ChatTransactionAtomicityIntegrationTest,
KickRoommatePersistenceIntegrationTest, DeleteRoomPersistenceIntegrationTest,
and FlushRemovalIntegrationTest and call the shared method from each test class
(reference method name: truncateTables()).

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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/resources/schema.sql`:
- Around line 47-58: The GROUP unique must be defensively dropped like DIRECT:
add a DROP CONSTRAINT IF EXISTS uk_chat_room_group before (or alongside) the
existing DROP INDEX IF EXISTS uk_chat_room_group in the chat_room migration SQL
so both constraint and index removal are handled (mirror the pattern used for
uk_chat_room_direct); update the block that references uk_chat_room_group to
include that DROP CONSTRAINT IF EXISTS statement to ensure consistency and
safety across environments.
🪄 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.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d077b4f3-4110-474f-81ee-377d88a85f7b

📥 Commits

Reviewing files that changed from the base of the PR and between 6203a3a and e013bfb.

📒 Files selected for processing (1)
  • src/main/resources/schema.sql

Comment on lines 47 to +58
ALTER TABLE IF EXISTS chat_room
DROP CONSTRAINT IF EXISTS uk_chat_room_direct;

DROP INDEX IF EXISTS uk_chat_room_group;
CREATE UNIQUE INDEX IF NOT EXISTS uk_chat_room_group
ON chat_room (room_no)
WHERE chat_room_type = 'GROUP';
WHERE chat_room_type = 'GROUP' AND deleted_at IS NULL;

DROP INDEX IF EXISTS uk_chat_room_direct;
CREATE UNIQUE INDEX IF NOT EXISTS uk_chat_room_direct
ON chat_room (room_no, applicant_user_no)
WHERE chat_room_type = 'DIRECT';
WHERE chat_room_type = 'DIRECT' AND deleted_at IS NULL;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

uk_chat_room_group도 방어적으로 DROP CONSTRAINT IF EXISTS를 함께 추가하는 것이 어떨까요?

라인 9-34에서 적용한 패턴(이전 리뷰 반영)과 비교했을 때, chat_room의 경우 uk_chat_room_direct는 라인 47-48에서 DROP CONSTRAINT, 라인 55에서 DROP INDEX로 양쪽 케이스를 모두 처리하지만, uk_chat_room_group은 라인 50의 DROP INDEX IF EXISTS만 존재합니다.

현재 코드베이스 기준으로는 GROUP 인덱스가 INDEX 형태로만 존재했을 가능성이 높지만, 과거 마이그레이션 이력이나 환경별 차이를 고려하면 direct와 동일하게 양쪽 모두 방어적으로 처리해두는 편이 일관성 측면에서도, 운영 안정성 측면에서도 더 좋을 것 같습니다.

🛡️ 제안 수정
 ALTER TABLE IF EXISTS chat_room
-    DROP CONSTRAINT IF EXISTS uk_chat_room_direct;
+    DROP CONSTRAINT IF EXISTS uk_chat_room_direct;
+ALTER TABLE IF EXISTS chat_room
+    DROP CONSTRAINT IF EXISTS uk_chat_room_group;

 DROP INDEX IF EXISTS uk_chat_room_group;
 CREATE UNIQUE INDEX IF NOT EXISTS uk_chat_room_group
     ON chat_room (room_no)
     WHERE chat_room_type = 'GROUP' AND deleted_at IS NULL;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ALTER TABLE IF EXISTS chat_room
DROP CONSTRAINT IF EXISTS uk_chat_room_direct;
DROP INDEX IF EXISTS uk_chat_room_group;
CREATE UNIQUE INDEX IF NOT EXISTS uk_chat_room_group
ON chat_room (room_no)
WHERE chat_room_type = 'GROUP';
WHERE chat_room_type = 'GROUP' AND deleted_at IS NULL;
DROP INDEX IF EXISTS uk_chat_room_direct;
CREATE UNIQUE INDEX IF NOT EXISTS uk_chat_room_direct
ON chat_room (room_no, applicant_user_no)
WHERE chat_room_type = 'DIRECT';
WHERE chat_room_type = 'DIRECT' AND deleted_at IS NULL;
ALTER TABLE IF EXISTS chat_room
DROP CONSTRAINT IF EXISTS uk_chat_room_direct;
ALTER TABLE IF EXISTS chat_room
DROP CONSTRAINT IF EXISTS uk_chat_room_group;
DROP INDEX IF EXISTS uk_chat_room_group;
CREATE UNIQUE INDEX IF NOT EXISTS uk_chat_room_group
ON chat_room (room_no)
WHERE chat_room_type = 'GROUP' AND deleted_at IS NULL;
DROP INDEX IF EXISTS uk_chat_room_direct;
CREATE UNIQUE INDEX IF NOT EXISTS uk_chat_room_direct
ON chat_room (room_no, applicant_user_no)
WHERE chat_room_type = 'DIRECT' AND deleted_at IS NULL;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/resources/schema.sql` around lines 47 - 58, The GROUP unique must be
defensively dropped like DIRECT: add a DROP CONSTRAINT IF EXISTS
uk_chat_room_group before (or alongside) the existing DROP INDEX IF EXISTS
uk_chat_room_group in the chat_room migration SQL so both constraint and index
removal are handled (mirror the pattern used for uk_chat_room_direct); update
the block that references uk_chat_room_group to include that DROP CONSTRAINT IF
EXISTS statement to ensure consistency and safety across environments.

Copy link
Copy Markdown
Member

@KoungQ KoungQ left a comment

Choose a reason for hiding this comment

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

LGTM

@ydking0911 ydking0911 merged commit 2f9eb4e into develop May 6, 2026
4 checks passed
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.

3 participants