Refactor: 카드셋 이미지 정합성 추가#48
Conversation
Walkthrough카드셋·그룹 이미지 처리를 URL 중심에서 imageRefId 기반으로 전환. DTO/요청/응답/레코드를 imageRefId를 포함하도록 확장하고 CardSet 목록/검색은 CardSetInfo 프로젝션으로 재구성. ImageService는 ImageMeta 및 assignImageUrl을 도입하고 ReferenceType에 CARD_SET 추가. 일부 테스트가 주석 처리됨. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as Client
participant CS as CardSetService
participant IS as ImageService
participant IRS as ImageRefService
participant REPO as CardSetRepository
rect rgb(241,248,255)
note over U,CS: Create CardSet
U->>CS: createCardSet(req{..., imageRefId})
CS->>IS: assignImageUrl(CARD_SET, req.imageRefId)
IS-->>CS: url
CS->>REPO: save(CardSet{imageUrl=url, ...})
CS-->>U: CardSetDetailResponse(..., imageRefId)
end
rect rgb(245,255,246)
note over U,CS: Get CardSet (include imageRefId)
U->>CS: getCardSet(id)
CS->>IRS: findImageRefId(CARD_SET, id)
IRS-->>CS: imageRefId?
CS-->>U: CardSetDetailResponse(..., imageRefId)
end
rect rgb(255,248,240)
note over U,CS: Update CardSet Image
U->>CS: updateCardSet(id, payload{..., imageRefId})
CS->>IS: changeImage(CARD_SET, id, payload.imageRefId)
IS-->>CS: ImageMeta{imageRefId, url}
CS->>REPO: update(CardSet, imageUrl=ImageMeta.url)
CS-->>U: CardSetDetailResponse(..., ImageMeta.imageRefId)
end
sequenceDiagram
autonumber
actor U as Client
participant GS as GroupService
participant IS as ImageService
participant REPO as GroupRepository
rect rgb(241,248,255)
note over U,GS: Create Group
U->>GS: createGroup(req{..., imageRefId})
GS->>IS: assignImageUrl(GROUP, req.imageRefId)
IS-->>GS: url
GS->>REPO: save(Group{imageUrl=url})
GS-->>U: GroupPutResponse(..., imageRefId)
end
rect rgb(255,248,240)
note over U,GS: Update Group Image
U->>GS: changeGroup(id, req{..., imageRefId})
GS->>IS: changeImage(GROUP, id, imageRefId)
IS-->>GS: ImageMeta{imageRefId, url}
GS-->>U: GroupPutResponse(..., imageRefId)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (21)
src/main/java/project/flipnote/cardset/model/CreateCardSetRequest.java (2)
5-5: 불필요한 import 제거
org.hibernate.validator.constraints.URL를 더 이상 사용하지 않습니다. 정리해 주세요.-import org.hibernate.validator.constraints.URL;
27-28: imageRefId의 의미와 제약을 스키마에 명시해 주세요생성 시
null허용 여부(기본 이미지 사용?), 양수만 허용 등 API 계약을 Swagger에 드러내면 클라이언트 변경 부담이 줄어듭니다.+import io.swagger.v3.oas.annotations.media.Schema; ... - Long imageRefId + @Schema(description = "이미지 참조 ID. null이면 기본 이미지 사용", example = "123", nullable = true) + Long imageRefId또한, 기존
image(URL)→imageRefId(Long)로의 API 변경은 호환성 파괴 변경입니다. 문서/클라이언트 배포 순서를 맞췄는지 확인 부탁드립니다.src/main/java/project/flipnote/cardset/model/CardSetUpdateRequest.java (3)
5-5: 불필요한 import 제거
org.hibernate.validator.constraints.URL미사용입니다.-import org.hibernate.validator.constraints.URL;
28-29: 부분 수정(Partial Update) 시 imageRefId 의미 명확화업데이트에서
imageRefId == null은 "변경 없음"인지, "기본으로 리셋"인지 명시 필요합니다. Swagger 설명 추가를 권합니다.- Long imageRefId + @Schema(description = "변경 시에만 전달. null이면 이미지 변경 없음", example = "123", nullable = true) + Long imageRefId
31-35: 네이밍 일관성
getHashTag()대신getHashtag()혹은joinHashtags()가 더 자연스럽습니다. 오타성 케이스를 줄입니다.src/main/java/project/flipnote/user/service/UserService.java (2)
86-89: 이미지 변경 로직의 null 처리 확인/가드 권장
req.imageRefId()가 null인 경우changeImage(...)의 동작이 애매합니다(무변경 vs 기본 리셋). 명확하지 않다면 가드를 추가해 주세요.- ImageMeta imageMeta = imageService.changeImage(type, userId, req.imageRefId()); - - user.update(req.nickname(), phone, req.smsAgree(), imageMeta.url()); + String newImageUrl = user.getProfileImageUrl(); + if (req.imageRefId() != null) { + ImageMeta imageMeta = imageService.changeImage(type, userId, req.imageRefId()); + newImageUrl = imageMeta.url(); + } + user.update(req.nickname(), phone, req.smsAgree(), newImageUrl);
101-115: 오타성 네이밍: imageRedId → imageRefId가독성과 검색성을 위해
imageRedId를imageRefId로 교체해 주세요. 응답 팩토리 호출부도 함께 정정 필요합니다.- Long imageRedId = imageRef.isPresent() ? imageRef.get().getId() : null; - return MyInfoResponse.from(user, imageRedId); + Long imageRefId = imageRef.isPresent() ? imageRef.get().getId() : null; + return MyInfoResponse.from(user, imageRefId);- Long imageRedId = imageRef.isPresent() ? imageRef.get().getId() : null; - return UserInfoResponse.from(user, imageRedId); + Long imageRefId = imageRef.isPresent() ? imageRef.get().getId() : null; + return UserInfoResponse.from(user, imageRefId);src/main/java/project/flipnote/cardset/model/CardSetInfo.java (1)
7-16: 프로젝션에 엔터티(CardSet/Group) 포함은 N+1/직렬화 리스크프로젝션 DTO가 엔터티를 들고 있으면 지연 로딩, 순환 참조(JSON), 불필요한 hydration 비용이 발생하기 쉽습니다. 스칼라/ID 기반 필드만 포함하는 형태를 권장합니다.
-public record CardSetInfo( - CardSet cardSet, - Group group, - String name, - Category category, - String hashtag, - String imageUrl, - Long imageRefId -) { -} +public record CardSetInfo( + Long cardSetId, + Long groupId, + String name, + Category category, + String hashtag, + String imageUrl, + Long imageRefId +) {}QueryDSL/JPQL 프로젝션도
Projections.constructor(CardSetInfo.class, cardSet.id, group.id, ...)형태로 맞추면 됩니다.src/main/java/project/flipnote/cardset/entity/CardSet.java (1)
61-67: 이미지 URL 외부 주입 시 null 덮어쓰기 방지 제안서비스에서 항상 유효한 URL을 넘기지 않으면 기존 값이 null로 덮여 JPA 제약 위반/클라이언트 깨짐이 발생할 수 있습니다. 변경 의도가 “이미지 변경 시에만 갱신”이라면 조건부 대입을 권장합니다.
- public void update(CardSetUpdatePayload payload, String imageUrl) { + public void update(CardSetUpdatePayload payload, String imageUrl) { this.name = payload.name(); this.publicVisible = payload.publicVisible(); this.category = payload.category(); this.hashtag = payload.hashtag(); - this.imageUrl = imageUrl; + if (imageUrl != null) { + this.imageUrl = imageUrl; + } }서비스 레이어에서 “이미지 제거(초기화)”가 필요하다면 별도 명시적 플래그로 처리하거나 null을 제거 의도로 사용한다는 계약을 Javadoc에 문서화해 주세요.
src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustom.java (1)
21-21: 메서드/파라미터 네이밍 가독성 개선 제안컬렉션이 ID 집합임을 드러내도록 파라미터명을 cardSetIds로 권장합니다. (시그니처 호환성 유지)
- List<CardSetInfo> findAllByIdWithImageRefId(Set<Long> cardSets); + List<CardSetInfo> findAllByIdWithImageRefId(Set<Long> cardSetIds);src/main/java/project/flipnote/group/model/GroupPutResponse.java (1)
23-24: imageRefId 추가는 적절 — null 직렬화 및 클라이언트 호환성 확인응답 스키마 확장은 자연스럽습니다. 다만 imageRefId가 없는 경우 null이 직렬화되어 API 소비자에 영향이 있을 수 있으니 필요 시 null 필드 미포함 처리를 권장합니다. 또한 from(Group, Long) 시그니처로 변경된 호출부 전수 반영 확인 부탁드립니다.
선택적 개선(필요 시):
- Long imageRefId, + @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL) + Long imageRefId,Also applies to: 29-39
src/main/java/project/flipnote/cardset/model/CardSetDetailResponse.java (1)
16-16: null imageRefId 직렬화 제어(선택)기본 이미지 사용 등으로 imageRefId가 null일 수 있다면 클라이언트 노이즈를 줄이기 위해 null 필드 미포함을 고려해 주세요.
- Long imageRefId, + @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL) + Long imageRefId,src/main/java/project/flipnote/cardset/model/CardSetSummaryResponse.java (1)
11-12: 요약 응답에도 imageRefId 추가 OK — null 미포함 직렬화 고려리스트 응답에서 imageRefId가 자주 null일 수 있으니 필요 시 null 필드 미포함 처리 고려 바랍니다.
- String imageUrl, - Long imageRefId + String imageUrl, + @com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL) + Long imageRefIdsrc/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustomImpl.java (4)
8-8: 불필요/오타로 보이는 import 제거
org.checkerframework.checker.units.qual.C는 사용되지 않으며 오타 가능성이 큽니다. 제거하세요.-import org.checkerframework.checker.units.qual.C;
78-95: 이미지 조인 다중행 위험 — distinct 권장동일 카드셋에 이미지 참조가 1:N이 되는 경합 상황에서 중복 로우가 반환될 수 있습니다. 안전을 위해 distinct를 추가하세요.
- JPAQuery<CardSetInfo> selectQuery = queryFactory - .select( + JPAQuery<CardSetInfo> selectQuery = queryFactory + .select( Projections.constructor( CardSetInfo.class, cardSet, cardSet.group, cardSet.name, cardSet.category, cardSet.hashtag, cardSet.imageUrl, imageRef.id )) + .distinct() .from(cardSet)
115-133: ID 조회 메서드도 중복 로우 방지동일 사유로
findAllByIdWithImageRefId에도 distinct를 권장합니다.- return queryFactory.select( + return queryFactory.select( Projections.constructor( CardSetInfo.class, cardSet, cardSet.group, cardSet.name, cardSet.category, cardSet.hashtag, cardSet.imageUrl, imageRef.id )) - .from(cardSet) + .from(cardSet) + .distinct()
78-95: 프로젝션에 엔티티 자체 포함 최소화
cardSet와cardSet.group엔티티 전체를 투사하면 페치 비용이 큽니다. 요약 응답에서 필요한 필드만 선별 투사하는 DTO로 축소하세요.Also applies to: 115-133
src/main/java/project/flipnote/group/service/GroupService.java (1)
62-64: 미사용 설정 필드 제거 제안
defaultGroupImage는 현재 파일에서 사용되지 않습니다. 혼동을 줄이기 위해 제거를 권장합니다.- @Value("${image.default.group}") - private String defaultGroupImage;src/main/java/project/flipnote/cardset/service/CardSetService.java (2)
65-67: 미사용 설정 필드 제거
defaultCardSetImage는 본 서비스에서 사용되지 않습니다. ImageService에서만 관리하도록 정리하세요.- @Value("${image.default.cardSet}") - private String defaultCardSetImage;
172-179: 중복 조회 제거
imageRefService.findByTypeAndReferenceId를 두 번 호출합니다. 한 번만 호출하도록 정리하세요.- Optional<ImageRef> imageRef = imageRefService.findByTypeAndReferenceId(REFERENCE_TYPE, cardSetId); - - Long imageRefId = imageRefService.findByTypeAndReferenceId(REFERENCE_TYPE, cardSetId) - .map(ImageRef::getId) - .orElse(null); + Long imageRefId = imageRefService.findByTypeAndReferenceId(REFERENCE_TYPE, cardSetId) + .map(ImageRef::getId) + .orElse(null);src/main/java/project/flipnote/image/service/ImageService.java (1)
69-89: 민감 정보 로그 수준 하향 및 해시 파싱 보정
- Presigned PUT URL과 버킷 정보는 INFO로 남기기엔 과합니다. DEBUG로 낮추고 전체 URL은 남기지 마세요.
- 해시는 첫 점(.) 기준 split 대신 마지막 확장자 기준으로 잘라 충돌 가능성을 줄이세요.
- String hash = fileName.split("\\.")[0]; + String hash = fileName.contains(".") ? fileName.substring(0, fileName.lastIndexOf('.')) : fileName; @@ - log.info(hash); + log.debug("image upload hash={}", hash); @@ - log.info(presignedUrl.toString()); + log.debug("presigned PUT URL created ({} min TTL)", EXPIRE_MINUTES);Also applies to: 104-108
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
src/main/java/project/flipnote/cardset/entity/CardSet.java(1 hunks)src/main/java/project/flipnote/cardset/model/CardSetDetailResponse.java(2 hunks)src/main/java/project/flipnote/cardset/model/CardSetInfo.java(1 hunks)src/main/java/project/flipnote/cardset/model/CardSetSummaryResponse.java(1 hunks)src/main/java/project/flipnote/cardset/model/CardSetUpdatePayload.java(2 hunks)src/main/java/project/flipnote/cardset/model/CardSetUpdateRequest.java(1 hunks)src/main/java/project/flipnote/cardset/model/CreateCardSetRequest.java(1 hunks)src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustom.java(1 hunks)src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustomImpl.java(6 hunks)src/main/java/project/flipnote/cardset/service/CardSetService.java(10 hunks)src/main/java/project/flipnote/group/model/GroupPutResponse.java(2 hunks)src/main/java/project/flipnote/group/service/GroupService.java(4 hunks)src/main/java/project/flipnote/image/entity/ImageMeta.java(1 hunks)src/main/java/project/flipnote/image/entity/ReferenceType.java(1 hunks)src/main/java/project/flipnote/image/service/ImageService.java(3 hunks)src/main/java/project/flipnote/user/service/UserService.java(2 hunks)src/main/resources/application.yml(1 hunks)src/test/java/project/flipnote/cardset/service/CardSetServiceTest.java(1 hunks)
🔇 Additional comments (15)
src/main/java/project/flipnote/image/entity/ImageMeta.java (1)
3-7: 값 객체 도입 깔끔합니다이미지 참조/URL을 묶은 불변 record 사용 적절합니다. 추가 요구가 생기면
contentType,width/height확장도 용이합니다.src/main/java/project/flipnote/cardset/model/CardSetUpdatePayload.java (2)
10-11: imageRefId로의 전환 적절합니다서비스 계층이 URL 해석을 담당하고, Payload는 참조 ID만 들고 가는 분리가 명확해졌습니다.
13-21: Partial Update 규약 확인
req.imageRefId()가 null일 때 서비스가 이미지를 보존하는지/기본값으로 바꾸는지 규약을 명확히 해 주세요. 문서화되지 않으면 예상치 못한 이미지 변경이 발생할 수 있습니다.src/main/java/project/flipnote/image/entity/ReferenceType.java (1)
4-4: CARD_SET 처리 확인 — 문제 없음CardSet 관련 분기와 기본 이미지 매핑이 서비스/레포지토리에서 반영되어 있으며, ImageRef.referenceType은 @Enumerated(EnumType.STRING)으로 매핑되어 있어 MySQL ENUM 마이그레이션은 필요하지 않습니다.
참고 위치: src/main/java/project/flipnote/cardset/service/CardSetService.java (REFERENCE_TYPE = ReferenceType.CARD_SET, l.68), src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustomImpl.java (imageRef.referenceType.eq(ReferenceType.CARD_SET), l.93,130), src/main/java/project/flipnote/image/entity/ImageRef.java (@Enumerated(EnumType.STRING), l.33–34).src/main/resources/application.yml (1)
108-113: 기본 카드셋 이미지 프로퍼티 추가 — 키/주입 경로 확인 필요
- src/main/resources/application.yml에 image.default.cardSet 항목이 존재합니다. 자동 검색(rg)으로 코드 내 주입 지점 확인을 시도했으나 rg가 파일을 건너뛰어 주입 사용처를 확인하지 못했습니다.
- 코드에서 @value 또는 @ConfigurationProperties로
${image.default.cardSet}(또는 변형image.default.cardset,image.default.card-set)가 사용되는지 수동으로 확인하세요. (권장 검색: rg -n "image\.default\.(cardSet|cardset|card-set)" --type=java --type=yaml)- 키 명명 혼용 위험 있으니 하나의 표기법으로 통일(예: 전체 소문자 kebab-case 또는 snake_case)하세요.
- 선택적 개선: S3 직접 URL 대신 CDN(예: CloudFront) 도메인 사용 권장(캐시·보안 이점).
src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustom.java (1)
15-19: 반환 타입을 Page로 변경 — 호출부 정합성 확인 필요페이징/정렬이 CardSet → CardSetInfo 전환에 따라 동일하게 동작하는지(특히 정렬 키가 엔티티 필드에서 프로젝션 속성으로 바뀌며 이름 매칭 이슈가 없는지) 호출부 전수 점검 부탁드립니다.
src/main/java/project/flipnote/cardset/model/CardSetDetailResponse.java (2)
26-26: 팩토리 시그니처 변경 LGTM — 서비스 전달 값 검증 부탁서비스에서 조회/업데이트 경로 모두에서 유효한 imageRefId(없으면 null)가 일관되게 전달되는지 확인해 주세요.
34-34: 명시적 매핑 OK이미지 URL은 엔티티에서, imageRefId는 서비스 계산값에서 분리 주입되어 의도와 부합합니다.
src/main/java/project/flipnote/cardset/model/CardSetSummaryResponse.java (1)
15-23: CardSetInfo 기반 매핑 전환 LGTM — 프로젝션 일치 여부 확인Projections.constructor로 CardSetInfo를 생성한다면 순서/타입 불일치에 민감합니다. CardSetInfo 접근자가 기대 값(특히 group(), category(), imageRefId())을 정확히 가리키는지 통합 테스트로 확인해 주세요.
src/main/java/project/flipnote/group/service/GroupService.java (2)
170-186: 그룹 생성시 이미지 정합성 흐름 적절
assignImageUrl로 URL 선정 후 생성, 이후changeUrlStatus로 활성화하는 순서가 일관되고 안전합니다. LGTM.
294-300: 수정 플로우의 ImageMeta 적용 적절
changeImage로 ImageMeta를 받고 정책/응답에 각각 url, imageRefId를 사용하는 방식이 명확합니다. LGTM.src/main/java/project/flipnote/cardset/service/CardSetService.java (3)
105-115: 생성 플로우의 URL 할당 방식 적절
assignImageUrl→imageUrl저장은 그룹과 동일 패턴으로 일관성 있습니다. LGTM.
119-123: 이미지 활성화 타이밍 OK생성 후 ID가 있을 때만 활성화하는 조건이 적절합니다. LGTM.
이미지 업로드 후 프론트에서 전달하는
imageRefId가 실제 존재하는지 E2E에서 한 번 검증해주세요(없을 경우 404가 아닌 409/422 등을 기대하는지 확인).
197-205: 수정 플로우의 ImageMeta 적용 적절
cardSet.update(payload, imageMeta.url())후 DTO로imageRefId를 반환하는 구조 일관적입니다. LGTM.src/main/java/project/flipnote/image/service/ImageService.java (1)
219-236: 사전 URL 할당 로직 적절이미 참조된
imageRefId에 대해 CONFLICT를 발생시키는 방어가 되어 있습니다. LGTM.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/main/java/project/flipnote/image/service/ImageService.java (1)
195-202: 교차 참조 탈취 선검증 추가됨 — 이전 지적 해결
imageRefId가 이미 다른 (type, referenceId)에 묶여있으면CONFLICT_IMAGE_REF를 던지도록 선검증이 추가되었습니다. 의도대로 잘 반영되었습니다.
🧹 Nitpick comments (2)
src/main/java/project/flipnote/image/service/ImageService.java (2)
47-49: 프로퍼티 키 케이스/바인딩 보강 필요@value는 relaxed binding이 적용되지 않습니다. application.yml에 key가
image.default.cardSet가 아닌image.default.card-set/image.default.cardset등으로 들어가면 주입 실패합니다. 안전한 폴백 바인딩을 제안합니다.- @Value("${image.default.cardSet}") - private String defaultCardSetImage; + @Value("${image.default.card-set:${image.default.cardSet:${image.default.cardset:}}}") + private String defaultCardSetImage;
220-226: 기본 이미지 미설정 방어 로직 추가 제안환경변수 누락 시 빈 문자열이 그대로 반환되면 예기치 않은 NPE/클라이언트 오류가 납니다. 최소한의 방어를 추가하세요.
- private String getDefaultImage(ReferenceType type) { - return switch (type) { - case USER -> defaultUserImage; - case GROUP -> defaultGroupImage; - case CARD_SET -> defaultCardSetImage; - }; - } + private String getDefaultImage(ReferenceType type) { + String url = switch (type) { + case USER -> defaultUserImage; + case GROUP -> defaultGroupImage; + case CARD_SET -> defaultCardSetImage; + }; + if (!StringUtils.hasText(url)) { + throw new BizException(ImageErrorCode.INVALID_URL); + } + return url; + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/main/java/project/flipnote/image/service/ImageService.java(3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (1)
src/main/java/project/flipnote/image/service/ImageService.java (1)
17-17: ImageMeta 도입으로 반환 모델 명확화 👍도메인 메타를 직접 반환하도록 한 선택이 API 일관성과 확장성에 유리합니다.
There was a problem hiding this comment.
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 (2)
src/main/java/project/flipnote/image/service/ImageRefService.java (1)
33-41: 쓰기 경로 트랜잭션 누락 가능성 — imageActivate에도 @transactional 적용 필요ImageService.changeUrlStatus(라인 124-156)에서 이 메서드를 비트랜잭션으로 호출하고 있어 JPA에서 TransactionRequiredException이 발생할 수 있습니다. 메서드 자체에 @transactional을 부여해 쓰기 경로를 확실히 하세요.
제안 diff:
public void imageActivate(Long imageRefId, ReferenceType type, Long referenceId) { + @Transactional + public void imageActivate(Long imageRefId, ReferenceType type, Long referenceId) { ImageRef imageRef = findById(imageRefId).orElseThrow( () -> new BizException(ImageErrorCode.IMAGE_NOT_FOUND) ); imageRef.activateFor(type, referenceId); imageRefRepository.save(imageRef); - } + }src/main/java/project/flipnote/image/service/ImageService.java (1)
124-156: 비트랜잭션 상태에서 쓰기 수행 — changeUrlStatus에 @transactional 필요이미지 메타데이터 업데이트와 imageActivate 호출이 트랜잭션 없이 수행됩니다. 쓰기 안정성을 위해 트랜잭션 경계를 부여하세요.
제안 diff:
- public void changeUrlStatus(Long id, ReferenceType type, Long referenceId) { + @Transactional + public void changeUrlStatus(Long id, ReferenceType type, Long referenceId) {
🧹 Nitpick comments (3)
src/main/java/project/flipnote/image/service/ImageRefService.java (2)
20-23: save에 @transactional 추가: 적절한 보강입니다쓰기 경로 보장을 위해 좋은 변경입니다. 다만 동일 클래스의 다른 쓰기 메서드에도 일관 적용을 권장합니다(아래 코멘트 참고).
21-23: save가 void를 반환 — 저장 엔티티 반환으로 호출부 편의성 개선 제안호출부에서 ID/상태 접근이 빈번합니다. 반환형을 ImageRef로 변경해 save 결과를 곧바로 활용할 수 있게 해두면 편의성이 올라갑니다.
- public void save(ImageRef imageRef) { - imageRefRepository.save(imageRef); - } + public ImageRef save(ImageRef imageRef) { + return imageRefRepository.save(imageRef); + }src/main/java/project/flipnote/image/service/ImageService.java (1)
228-241: assignImageUrl 검증 강화: 좋습니다. 반환 타입 일관성 개선 제안(ImageMeta 사용)changeImage가 ImageMeta를 반환하므로 assignImageUrl도 동일 타입으로 맞추면 API 일관성이 좋아집니다. 하위 호환이 필요하면 오버로드를 추가하세요.
예시:
- public String assignImageUrl(ReferenceType type, Long imageRefId) { + public ImageMeta assignImage(ReferenceType type, Long imageRefId) { if(imageRefId==null) { - return getDefaultImage(type); + return ImageMeta.from(null, getDefaultImage(type)); } ImageRef ref = imageRefService.findById(imageRefId).orElseThrow( () -> new BizException(ImageErrorCode.IMAGE_NOT_FOUND) ); if (ref.getReferenceId() != null) { throw new BizException(ImageErrorCode.CONFLICT_IMAGE_REF); } - return generateUrl(ref.getImage().getS3Key()).toString(); + String url = generateUrl(ref.getImage().getS3Key()).toString(); + return ImageMeta.from(ref.getId(), url); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/main/java/project/flipnote/image/entity/ImageRef.java(2 hunks)src/main/java/project/flipnote/image/service/ImageRefService.java(2 hunks)src/main/java/project/flipnote/image/service/ImageService.java(3 hunks)
🔇 Additional comments (4)
src/main/java/project/flipnote/image/service/ImageService.java (3)
195-202: 교차 참조 탈취 방지 선검증 반영: 좋습니다다른 (type, referenceId)에 이미 점유된 imageRefId를 차단하는 검증이 추가되어 안전해졌습니다.
220-226: 기본 이미지 매핑 추가: 적절합니다CARD_SET 기본값 추가와 switch 표현식 구성 모두 명확합니다.
47-49: image.default.cardSet 프로퍼티 존재 및 값 확인됨 application.yml에 해당 키가 정의되어 있으며 값(https://flipnote-bucket.s3.ap-northeast-2.amazonaws.com/image/default/cardset.png)이 비어있지 않습니다.src/main/java/project/flipnote/image/entity/ImageRef.java (1)
48-50: 낙관적 락(@Version) 도입 — 운영 DB 마이그레이션(버전 컬럼) 확인 필요엔티티(src/main/java/project/flipnote/image/entity/ImageRef.java)에 @Version 필드가 존재합니다. 리포지토리에서 image_references 테이블에 version 컬럼을 추가하는 마이그레이션(.sql/.yml/.xml)은 검색되지 않았고, application.yml에서 batch_versioned_data: true 설정만 확인됐습니다 (src/main/resources/application.yml). 운영 DB에 version 컬럼이 이미 반영됐는지 확인하거나, 스키마 관리 도구용 마이그레이션을 추가해 주세요.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/main/java/project/flipnote/cardset/service/CardSetService.java (1)
65-66: 미사용 필드 제거
CardSetService에서 선언된defaultCardSetImage필드가 참조되지 않으므로 제거하세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustomImpl.java(6 hunks)src/main/java/project/flipnote/cardset/service/CardSetService.java(10 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (5)
src/main/java/project/flipnote/cardset/repository/CardSetRepositoryCustomImpl.java (2)
95-97: 이미지 참조 조인 로직 검증 완료
imageRef와의 left join 조건이 올바르게 구성되어 있습니다.ReferenceType.CARD_SET과cardSet.id매칭을 통해 정확한 이미지 참조를 가져옵니다.
81-97: Projection 필드 순서 일치 확인됨
Projections.constructor인자 순서가CardSetInfo레코드 생성자 시그니처와 정확히 일치합니다.src/main/java/project/flipnote/cardset/service/CardSetService.java (3)
192-205: ImageMeta를 통한 이미지 변경 로직 승인
changeImage메서드를 통해 이미지 참조를 변경하고 반환된ImageMeta를 사용하여 CardSet을 업데이트하는 흐름이 적절합니다. imageRefId와 URL을 함께 관리하여 일관성을 유지합니다.
248-253: Projection 기반 조회로의 전환 완료
findAllByIdWithImageRefId를 사용하여 imageRefId를 포함한 projection을 조회하는 방식으로 성공적으로 리팩토링되었습니다. N+1 문제를 방지하고 필요한 데이터만 효율적으로 가져옵니다.Also applies to: 278-284
105-122: 이미지 URL 할당 및 활성화 로직 검증 완료
assignImageUrl은 imageRefId가 null일 때 기본 이미지를 반환하고, changeUrlStatus는 null 체크 후에만 호출되며, createCardSet 메서드는 @transactional 내에서 저장과 활성화를 일관되게 처리합니다. 별도 조치 불필요합니다.
📝 변경 내용
✅ 체크리스트
💬 기타 참고 사항
Summary by CodeRabbit