Skip to content

refactor: BianryContent 엔티티 및 Tag 추출 로직 리펙토링#305

Merged
plzslp merged 7 commits into
devfrom
refactor/271-binary-content-tag-refactor
Jul 7, 2026
Merged

refactor: BianryContent 엔티티 및 Tag 추출 로직 리펙토링#305
plzslp merged 7 commits into
devfrom
refactor/271-binary-content-tag-refactor

Conversation

@plzslp

@plzslp plzslp commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

관련 이슈

작업 내용

  • BinaryContent의 생성용 정적 팩토리 메서드를 통일했습니다.
  • Tag 추출 기능을 TagResolver 클래스로 분리하였습니다.
  • TagResolver 테스트 코드를 작성하였습니다.
  • 패키지 구조 변경 및 JavaDoc 수정
  • ImageUrl이 null일때 반환하는 placeholder-movie.png 파일 추가

변경 사항

  • TagResolver 클래스 추가
  • content.service.util 패키지 > content.batch.util 로 이동
  • static 패키지 내부 placeholder-movie.png 파일 추가

테스트 내용

  • 테스트 코드 작성
  • 로컬 테스트 완료
  • API 테스트 완료
  • 기타 테스트 완료

체크리스트

  • 코드 컨벤션을 지켰습니다.
  • 불필요한 코드를 제거했습니다.
  • 주석이 필요한 부분에 추가했습니다.
  • 관련 문서를 수정했습니다.
  • 리뷰어가 이해하기 쉽도록 작성했습니다.

리뷰 포인트

  • 중점적으로 봐줬으면 하는 부분을 작성해주세요.

스크린샷 / 참고 자료

  • 필요한 경우 첨부해주세요.

🔗 관련 이슈

  • close #측정하지 않음

📝 작업 내용

  • BinaryContent 생성 방식과 태그 처리 흐름에서 중복되던 책임을 정리해, 콘텐츠 저장 로직이 더 일관된 공용 경로를 사용하도록 개선했습니다.
  • 태그 생성/조회/정규화가 여러 곳에 흩어져 있던 구조를 TagResolver로 모아, ContentService와 배치 writer가 같은 규칙으로 태그를 처리하게 했습니다.
  • 썸네일이 null인 경우를 포함한 이미지 처리 흐름도 함께 정리해, BinaryContent 생성 계약을 더 명확하게 맞췄습니다.

주요 변경사항

  1. BinaryContent의 중복 팩토리 경로를 정리하고, 썸네일/외부 URL 생성 흐름을 하나의 기준 메서드로 통합했습니다.
  2. 태그 정규화, 기존 태그 조회, 신규 태그 생성 로직을 TagResolver로 분리해 데이터 흐름을 단순화했습니다.
  3. ContentService와 배치 writer가 동일한 태그 해석 규칙을 사용하도록 바꾸고, 관련 JavaDoc도 현재 동작에 맞게 갱신했습니다.

📊 변경 효과 요약

  • TagResolver 테스트 1개 클래스 추가
  • 기존 테스트/코드에서 BinaryContent 생성 기대값을 새 팩토리 기준으로 갱신
  • 정량 지표는 이번 PR에서 측정하지 않음

@plzslp plzslp self-assigned this Jul 6, 2026
@plzslp plzslp added the refactor label Jul 6, 2026
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

BinaryContent 엔티티에서 externalUrl 팩토리 메서드를 제거하고 completed로 통합했으며, 이를 사용하던 ContentCollectionUtils와 ContentItemWriter 및 관련 테스트를 수정했습니다. 태그명 정규화 및 조회/생성 로직을 신규 TagResolver 클래스로 추출해 ContentService와 ContentItemWriter의 중복 로직을 대체하고, ContentCollectionUtils 패키지를 service.util에서 batch.util로 이동했습니다. TagResolver에 대한 단위 테스트가 추가되었습니다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • sb10-part4-team5/sb10-mopl-team5#140: 동일하게 BinaryContent 썸네일 생성 호출부를 externalUrl에서 completed로 전환하는 등 BinaryContent API 변경 영역이 겹칩니다.
  • sb10-part4-team5/sb10-mopl-team5#157: ContentService의 태그 정규화/검증 및 삽입 흐름(10개 제한 예외 포함)을 함께 수정하여 코드 레벨로 겹칩니다.
  • sb10-part4-team5/sb10-mopl-team5#244: ContentItemWriter를 포함한 배치 콘텐츠 파이프라인을 함께 수정하여, 이번 PR의 썸네일/태그 변경이 해당 PR이 도입한 코드를 직접 수정합니다.

리뷰 시 참고할 만한 몇 가지 포인트를 짚어드릴게요.

  1. ContentItemWriter의 태그 매칭 로직 (range_8abde8bab6c7): 정규화된 이름으로 resolve 맵을 만든 뒤, 각 아이템의 원문 태그명은 TagResolver.normalize(rawTagName) 결과가 있을 때만 매칭하도록 바뀌었는데요, 이 경우 정규화 결과가 null(공백만 있는 태그명)이면 해당 태그는 조용히 스킵됩니다. 기존 로직과 동작이 동일한지, 혹은 이 케이스에 대한 로그/검증이 필요한지 확인해보시면 좋겠습니다. 예외를 던지는 대신 조용히 무시하는 게 의도된 동작이라면 주석으로 명시해두면 다음 리뷰어가 헷갈리지 않을 것 같아요.

  2. 패키지 이동만으로 끝난 import 변경 (range_1b158ba12d93, range_e317fece29b6, range_fcc8fd0bebf4): 단순 import 경로 변경이라 로직상 문제는 없어 보이지만, ContentCollectionUtilsservice.util이 아니라 batch.util에 있다는 것은 이 클래스가 배치 처리 전용임을 명확히 한다는 뜻이겠죠. 혹시 서비스 레이어에서도 이 유틸을 참조하는 코드가 남아있다면 패키지 경계가 흐려질 수 있으니, 그런 잔여 참조가 없는지 한 번 검색해보시는 걸 추천드립니다.

  3. TagResolver.resolve의 saveAll 조건부 호출: 새 태그가 없을 때는 saveAll을 호출하지 않는 최적화가 들어가 있는데, 이건 불필요한 DB 왕복을 줄이는 좋은 패턴입니다. 다만 이 유틸이 여러 곳(ContentService, ContentItemWriter)에서 공유되므로, 동시성 이슈(같은 태그명이 동시에 생성 시도될 때 유니크 제약 위반 등)에 대한 처리가 필요한지 확인이 필요합니다. 배치 처리에서는 병렬 처리 가능성이 있으니 이 부분은 한 번 짚고 넘어가면 좋을 것 같습니다.

Comments failed to post (3)

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 BinaryContent와 Tag 추출 리팩토링이라는 핵심 변경을 요약하고 있어 관련성은 충분합니다.
Description check ✅ Passed 필수 섹션과 주요 항목이 모두 채워져 있어 템플릿 요구를 대부분 충족합니다.
Linked Issues check ✅ Passed BinaryContent 정리, TagResolver 도입, 연관 코드·주석 수정이 이슈 #271의 요구와 일치합니다.
Out of Scope Changes check ✅ Passed 추가 변경도 placeholder 이미지와 태그/팩토리 리팩토링 범위에 맞아, 명백한 무관 변경은 보이지 않습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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/271-binary-content-tag-refactor

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 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/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriter.java`:
- Around line 101-115: 태그 처리 로직에서 정규화가 `TagResolver.normalizeNames`와
`deduplicatedItems.forEach` 내부에 중복 구현되어 있고, item 단위 중복 제거도 빠져 있습니다.
`ContentItemWriter`의 태그 저장 블록에서 `tagName.trim().toLowerCase()` 재구현을 제거하고
`TagResolver.normalizeNames(item.tagNames())`를 재사용해 정규화 정책을 한 곳으로 통일하세요. 또한 각
item의 태그 목록은 distinct된 결과만 순회하도록 바꿔 `ContentTag.create(...)`가 동일 태그에 대해 여러 번
호출되지 않게 처리하세요.

In `@src/main/java/com/codeit/team5/mopl/tag/util/TagResolver.java`:
- Around line 33-47: `TagResolver.resolve()`는 정규화된 중복 없는 입력을 전제로 하지만 이를 코드상으로
보장하지 않아 동일 태그가 반복 생성될 수 있습니다. `resolve`, `Tag.create`,
`tagRepository.findByNameIn` 주변에서 `tagNames`에 대해 방어적으로 중복 제거를 추가하거나, 메서드
Javadoc에 입력 전제(정규화/중복 제거 필요)를 명시해 `ContentService`와 `ContentItemWriter` 외의 호출에서도
안전하게 사용되도록 수정하세요.
- Around line 33-47: TagResolver.resolve() currently does a read-then-save flow
for missing tag names, which can race with the tags.name unique constraint under
concurrent use. Update the resolve method so new tag creation is atomic in the
TagResolver/TagRepository path, either by using a DB upsert-style save or by
catching saveAll() conflicts and reloading the existing tags before merging them
into existingTags. Keep the existing resolve() contract intact while ensuring
duplicate tag names from overlapping controller/scheduler/job executions do not
fail with DataIntegrityViolationException.

In
`@src/test/java/com/codeit/team5/mopl/content/service/util/ContentCollectionUtilsTest.java`:
- Line 9: The test for ContentCollectionUtils is still located under the old
content.service.util package path even though the utility moved to
content.batch.util. Move ContentCollectionUtilsTest to the matching
content.batch.util test package, update its package declaration to align with
the new location, and keep the ContentCollectionUtils import consistent so the
test structure mirrors the production class.

In `@src/test/java/com/codeit/team5/mopl/tag/util/TagResolverTest.java`:
- Around line 87-102: TagResolverTest의 resolve() 커버리지에 중복된 신규 입력 시나리오가 빠져 있습니다.
TagResolver.resolve(List<String>, TagRepository)로 List.of("액션", "액션") 같은 중복 이름을
넣었을 때 saveAll에 동일 이름의 Tag가 2건 전달되는지, 그리고 반환된 Map이 기대대로 동작하는지 검증하는 테스트 케이스를
추가하세요. 기존 resolve_allNew_createsAndSavesAll 패턴을 참고하되,
tagRepository.findByNameIn과 tagRepository.saveAll의 동작을 중복 입력 기준으로 설정하고
ArgumentCaptor로 전달된 Tag 목록을 확인하면 됩니다.
🪄 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 Plus

Run ID: 21b85262-483e-4c6f-8792-c37ac6c3e8c4

📥 Commits

Reviewing files that changed from the base of the PR and between 8c4ef48 and 3e7a878.

📒 Files selected for processing (16)
  • src/main/java/com/codeit/team5/mopl/binarycontent/entity/BinaryContent.java
  • src/main/java/com/codeit/team5/mopl/binarycontent/service/BinaryContentService.java
  • src/main/java/com/codeit/team5/mopl/content/batch/processor/SportsDbEventItemProcessor.java
  • src/main/java/com/codeit/team5/mopl/content/batch/processor/TmdbMovieItemProcessor.java
  • src/main/java/com/codeit/team5/mopl/content/batch/processor/TmdbTvSeriesItemProcessor.java
  • src/main/java/com/codeit/team5/mopl/content/batch/util/ContentCollectionUtils.java
  • src/main/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriter.java
  • src/main/java/com/codeit/team5/mopl/content/service/ContentService.java
  • src/main/java/com/codeit/team5/mopl/tag/util/TagResolver.java
  • src/test/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriterTest.java
  • src/test/java/com/codeit/team5/mopl/content/controller/ContentControllerIntegrationTest.java
  • src/test/java/com/codeit/team5/mopl/content/repository/ContentRepositoryTest.java
  • src/test/java/com/codeit/team5/mopl/content/service/ContentServiceTest.java
  • src/test/java/com/codeit/team5/mopl/content/service/util/ContentCollectionUtilsTest.java
  • src/test/java/com/codeit/team5/mopl/tag/util/TagResolverTest.java
  • src/test/java/com/codeit/team5/mopl/user/service/UserServiceTest.java

Comment thread src/main/java/com/codeit/team5/mopl/tag/util/TagResolver.java
Comment thread src/test/java/com/codeit/team5/mopl/tag/util/TagResolverTest.java
@plzslp plzslp force-pushed the refactor/271-binary-content-tag-refactor branch from 3e7a878 to 36bb0a7 Compare July 6, 2026 08:37
@plzslp

plzslp commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai 지금은 리뷰 가능해?

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

@plzslp 네, 지금 리뷰 가능합니다! PR 변경 사항을 리뷰하겠습니다.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/main/java/com/codeit/team5/mopl/tag/util/TagResolver.java (1)

47-63: 🎯 Functional Correctness | 🔴 Critical

신규 태그 생성 원자성 문제, 여전히 미해결입니다.

resolve()는 조회 후 없는 이름만 saveAll()하는 구조를 그대로 유지하고 있어, 이전 리뷰에서 지적된 tags.name 유니크 제약 위반 위험이 그대로 남아있습니다. 이 프로젝트는 TaskExecutorJobLauncher로 배치를 비동기 실행하고 컨트롤러/스케줄러도 같은 경로를 재사용하므로, 겹친 실행에서 동일 태그명이 동시에 들어오면 한쪽이 DataIntegrityViolationException으로 실패할 수 있습니다.

Javadoc에 정규화/중복 제거 전제를 명시하고 내부 distinct()를 추가한 부분은 잘 반영됐지만, 이 원자성 문제 자체는 이번 커밋 범위에 포함되지 않은 것으로 보입니다. saveAll() 실패를 캐치해 재조회 후 병합하거나, DB 레벨 upsert로 원자화하는 방향을 권장합니다.

🤖 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/java/com/codeit/team5/mopl/tag/util/TagResolver.java` around lines
47 - 63, The atomicity issue in TagResolver.resolve is still present: it checks
existing tags and then calls saveAll on only the missing ones, which can still
race on the unique tags.name constraint. Update resolve to handle concurrent
inserts safely by either using a DB-level upsert/merge path or catching the
persistence conflict from TagRepository.saveAll, then re-querying and merging
the persisted tags back into the existingTags map. Keep the duplicate filtering
in resolve, but make the TagResolver flow resilient so concurrent callers don’t
fail with DataIntegrityViolationException.
🤖 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.

Duplicate comments:
In `@src/main/java/com/codeit/team5/mopl/tag/util/TagResolver.java`:
- Around line 47-63: The atomicity issue in TagResolver.resolve is still
present: it checks existing tags and then calls saveAll on only the missing
ones, which can still race on the unique tags.name constraint. Update resolve to
handle concurrent inserts safely by either using a DB-level upsert/merge path or
catching the persistence conflict from TagRepository.saveAll, then re-querying
and merging the persisted tags back into the existingTags map. Keep the
duplicate filtering in resolve, but make the TagResolver flow resilient so
concurrent callers don’t fail with DataIntegrityViolationException.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bc2b7dd2-b7e7-4d3f-aabe-74c61b98d3f2

📥 Commits

Reviewing files that changed from the base of the PR and between 3e7a878 and 36bb0a7.

⛔ Files ignored due to path filters (1)
  • src/main/resources/static/placeholder-movie.png is excluded by !**/*.png
📒 Files selected for processing (16)
  • src/main/java/com/codeit/team5/mopl/binarycontent/entity/BinaryContent.java
  • src/main/java/com/codeit/team5/mopl/binarycontent/service/BinaryContentService.java
  • src/main/java/com/codeit/team5/mopl/content/batch/processor/SportsDbEventItemProcessor.java
  • src/main/java/com/codeit/team5/mopl/content/batch/processor/TmdbMovieItemProcessor.java
  • src/main/java/com/codeit/team5/mopl/content/batch/processor/TmdbTvSeriesItemProcessor.java
  • src/main/java/com/codeit/team5/mopl/content/batch/util/ContentCollectionUtils.java
  • src/main/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriter.java
  • src/main/java/com/codeit/team5/mopl/content/service/ContentService.java
  • src/main/java/com/codeit/team5/mopl/tag/util/TagResolver.java
  • src/test/java/com/codeit/team5/mopl/content/batch/util/ContentCollectionUtilsTest.java
  • src/test/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriterTest.java
  • src/test/java/com/codeit/team5/mopl/content/controller/ContentControllerIntegrationTest.java
  • src/test/java/com/codeit/team5/mopl/content/repository/ContentRepositoryTest.java
  • src/test/java/com/codeit/team5/mopl/content/service/ContentServiceTest.java
  • src/test/java/com/codeit/team5/mopl/tag/util/TagResolverTest.java
  • src/test/java/com/codeit/team5/mopl/user/service/UserServiceTest.java

@plzslp plzslp requested a review from metDaisy July 6, 2026 09:03
Comment thread src/main/java/com/codeit/team5/mopl/binarycontent/entity/BinaryContent.java Outdated
metDaisy
metDaisy previously approved these changes Jul 7, 2026

@metDaisy metDaisy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

수고하셨습니다

@plzslp plzslp force-pushed the refactor/271-binary-content-tag-refactor branch from 36bb0a7 to b75b619 Compare July 7, 2026 00:45

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
src/main/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriter.java (1)

101-116: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

정규화 로직 통일은 됐지만, item 내부 중복 태그명 dedup은 여전히 빠져있어요.

이전 리뷰에서 지적된 두 가지 중 "정규화 로직 중복 구현"은 TagResolver.normalize()로 잘 통일됐습니다. 하지만 두 번째 지적사항인 item 단위 dedup 누락은 그대로 남아있습니다.

item.tagNames().forEach(...)distinct() 없이 원본 리스트를 그대로 순회합니다. 만약 한 item의 태그 목록에 "Action"" action "처럼 정규화 후 동일한 값이 되는 항목이 섞여 있으면, 같은 (content, tag) 쌍에 대해 ContentTag.create()가 두 번 호출되고, 두 개의 ContentTagcontent.addTag()로 중복 추가됩니다. 저장 시점에 ContentTagId 복합키 유니크 제약을 위반해 청크 전체가 실패할 위험이 있습니다.

TagResolver.normalizeNames(item.tagNames())로 바꾸면 정규화와 dedup을 한 번에 해결할 수 있습니다.

♻️ 제안하는 수정
             deduplicatedItems.forEach(item -> item.tagNames().forEach(rawTagName -> {
-                String normalized = TagResolver.normalize(rawTagName);
-                Tag tag = normalized == null ? null : existingTags.get(normalized);
-                if (tag != null) {
+                normalized -> {
+                    Tag tag = existingTags.get(normalized);
+                    if (tag != null) {
                     item.content().addTag(ContentTag.create(item.content(), tag));
                 }
             }));

또는 더 간단히:

-            deduplicatedItems.forEach(item -> item.tagNames().forEach(rawTagName -> {
-                String normalized = TagResolver.normalize(rawTagName);
-                Tag tag = normalized == null ? null : existingTags.get(normalized);
-                if (tag != null) {
-                    item.content().addTag(ContentTag.create(item.content(), tag));
-                }
-            }));
+            deduplicatedItems.forEach(item -> TagResolver.normalizeNames(item.tagNames()).forEach(normalized -> {
+                Tag tag = existingTags.get(normalized);
+                if (tag != null) {
+                    item.content().addTag(ContentTag.create(item.content(), tag));
+                }
+            }));

item별 tagNames에 실제로 정규화 후 중복될 수 있는 값이 유입되는지는 TMDB/SportsDB 응답 구조에 달려있으니, 한 번 확인해보시는 걸 권장합니다.

🤖 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/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriter.java`
around lines 101 - 116, The per-item tag loop in ContentItemWriter still
iterates raw tagNames without deduplicating after normalization, so the same tag
can be added twice to one content item. Update the inner processing in
ContentItemWriter to normalize and dedupe each item’s tags before calling
ContentTag.create() and content.addTag(), preferably by reusing
TagResolver.normalizeNames(item.tagNames()) or an equivalent distinct pass over
normalized names, while keeping TagResolver.resolve() for existing tag lookup.
src/main/java/com/codeit/team5/mopl/tag/util/TagResolver.java (1)

35-63: 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

동시 저장 시 유니크 제약 위반 위험이 아직 남아있습니다.

resolve()는 여전히 "조회 → 없는 것만 생성/저장"하는 read-then-write 패턴입니다. 이전 리뷰에서 이미 지적된 것처럼, 이 프로젝트는 배치 잡을 비동기로 띄우고(TaskExecutorJobLauncher) 컨트롤러/스케줄러가 같은 경로를 공유하므로, 두 실행이 겹쳐서 같은 신규 태그명을 동시에 처리하면 한쪽의 saveAll()tags.name 유니크 제약에 걸려 DataIntegrityViolationException으로 실패할 수 있습니다.

선택지는 두 가지입니다:

  • 재조회 후 병합: saveAll()에서 제약 위반이 발생하면 캐치하고, 실패한 이름들을 다시 findByNameIn으로 조회해 기존 태그로 병합. 구현이 상대적으로 간단하지만 예외 기반 흐름 제어라 다소 지저분함.
  • DB 레벨 upsert(예: INSERT ... ON CONFLICT DO NOTHING 후 재조회): 원자적으로 처리 가능하지만 벤더 종속 네이티브 쿼리가 필요해 구현 비용이 더 큼.

지금 당장 재현 빈도가 낮을 수 있지만, 배치 동시 실행 시나리오가 실제로 존재한다면 반드시 짚고 넘어가야 할 데이터 정합성 문제입니다.

🤖 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/java/com/codeit/team5/mopl/tag/util/TagResolver.java` around lines
35 - 63, `TagResolver.resolve()` still uses a non-atomic read-then-write flow,
so concurrent callers can both miss the same new tag and then fail on the
`tags.name` unique constraint during `saveAll()`. Update the resolution logic to
be concurrency-safe: either catch `DataIntegrityViolationException` around
`tagRepository.saveAll(...)`, re-query the conflicting names with
`findByNameIn`, and merge the existing entities, or replace the current
lookup/create path with an atomic upsert-based approach. Keep the existing
`resolve`, `findByNameIn`, and `Tag::create` flow as the entry points, but
ensure duplicate concurrent inserts cannot surface as a failure.
🤖 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.

Duplicate comments:
In
`@src/main/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriter.java`:
- Around line 101-116: The per-item tag loop in ContentItemWriter still iterates
raw tagNames without deduplicating after normalization, so the same tag can be
added twice to one content item. Update the inner processing in
ContentItemWriter to normalize and dedupe each item’s tags before calling
ContentTag.create() and content.addTag(), preferably by reusing
TagResolver.normalizeNames(item.tagNames()) or an equivalent distinct pass over
normalized names, while keeping TagResolver.resolve() for existing tag lookup.

In `@src/main/java/com/codeit/team5/mopl/tag/util/TagResolver.java`:
- Around line 35-63: `TagResolver.resolve()` still uses a non-atomic
read-then-write flow, so concurrent callers can both miss the same new tag and
then fail on the `tags.name` unique constraint during `saveAll()`. Update the
resolution logic to be concurrency-safe: either catch
`DataIntegrityViolationException` around `tagRepository.saveAll(...)`, re-query
the conflicting names with `findByNameIn`, and merge the existing entities, or
replace the current lookup/create path with an atomic upsert-based approach.
Keep the existing `resolve`, `findByNameIn`, and `Tag::create` flow as the entry
points, but ensure duplicate concurrent inserts cannot surface as a failure.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3ed9973c-c32a-4700-8919-a61976faee8e

📥 Commits

Reviewing files that changed from the base of the PR and between 36bb0a7 and b75b619.

⛔ Files ignored due to path filters (1)
  • src/main/resources/static/placeholder-movie.png is excluded by !**/*.png
📒 Files selected for processing (11)
  • src/main/java/com/codeit/team5/mopl/binarycontent/entity/BinaryContent.java
  • src/main/java/com/codeit/team5/mopl/content/batch/processor/SportsDbEventItemProcessor.java
  • src/main/java/com/codeit/team5/mopl/content/batch/processor/TmdbMovieItemProcessor.java
  • src/main/java/com/codeit/team5/mopl/content/batch/processor/TmdbTvSeriesItemProcessor.java
  • src/main/java/com/codeit/team5/mopl/content/batch/util/ContentCollectionUtils.java
  • src/main/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriter.java
  • src/main/java/com/codeit/team5/mopl/content/service/ContentService.java
  • src/main/java/com/codeit/team5/mopl/tag/util/TagResolver.java
  • src/test/java/com/codeit/team5/mopl/content/batch/util/ContentCollectionUtilsTest.java
  • src/test/java/com/codeit/team5/mopl/content/batch/writer/ContentItemWriterTest.java
  • src/test/java/com/codeit/team5/mopl/tag/util/TagResolverTest.java
💤 Files with no reviewable changes (1)
  • src/main/java/com/codeit/team5/mopl/binarycontent/entity/BinaryContent.java

@plzslp plzslp merged commit d05b3e2 into dev Jul 7, 2026
2 checks passed
@plzslp plzslp deleted the refactor/271-binary-content-tag-refactor branch July 7, 2026 00:58
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.

[Refactor] BinaryContent엔티티, ContentTag 생성 로직 리펙토링

2 participants