Fix/94/search#95
Conversation
📝 WalkthroughWalkthroughThis PR introduces a comprehensive Redis-based caching infrastructure for book metadata, implements a new home screen API endpoint returning real-time rankings and bestsellers, adds asynchronous metadata enrichment from Kakao API, and refactors search controller authentication. Build configuration updated with cache and Redis dependencies. Extensive documentation added covering deployment, caching strategy, and Redis installation. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant HomeController
participant HomeServiceImpl
participant BookMetadataService
participant BooksRepository
participant RedisCache as Redis/Cache
participant KakaoAPI as Kakao API
participant Database
Client->>HomeController: GET /api/v1/home
HomeController->>HomeServiceImpl: getHomeData()
alt Cache Hit
HomeServiceImpl->>RedisCache: lookup homeBooks
RedisCache-->>HomeServiceImpl: return cached HomeResponse
HomeServiceImpl-->>HomeController: HomeResponse
else Cache Miss
HomeServiceImpl->>HomeServiceImpl: collectAllBookInfos()<br/>(from PM mapping)
HomeServiceImpl->>BookMetadataService: getBookSummaries(List<BookInfo>)
BookMetadataService->>BooksRepository: findAllByTitleIn(titles)
BooksRepository->>Database: query Books with<br/>fetch-join Authors
Database-->>BooksRepository: Books list
BooksRepository-->>BookMetadataService: Books list
loop For each book needing enrichment
BookMetadataService->>BookMetadataService: enrichMetadataAsync()<br/>(`@Async`)
BookMetadataService->>KakaoAPI: fetch metadata (title)
KakaoAPI-->>BookMetadataService: thumbnail, ISBN, publisher
BookMetadataService->>Database: update Books entity
Database-->>BookMetadataService: ack
end
BookMetadataService-->>HomeServiceImpl: List<BookSummary>
HomeServiceImpl->>HomeServiceImpl: buildRealTimeRanking()<br/>buildMoodBestsellers()<br/>buildWritingStyleBestsellers()<br/>buildImmersionBestsellers()
HomeServiceImpl->>RedisCache: cache HomeResponse<br/>(1 hour TTL)
HomeServiceImpl-->>HomeController: HomeResponse
end
HomeController-->>Client: 200 OK + HomeResponse
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Tip 🧪 Unit Test Generation v2 is now available!We have significantly improved our unit test generation capabilities. To enable: Add this to your reviews:
finishing_touches:
unit_tests:
enabled: trueTry it out by using the Have feedback? Share your thoughts on our Discord thread! 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: 18
🤖 Fix all issues with AI agents
In `@booklog/build.gradle`:
- Around line 74-77: The build.gradle currently disables the test task
unconditionally via tasks.named('test') { enabled = false }, which prevents
local test runs; change this to respect a skip flag instead: remove the
unconditional enabled = false and use a conditional check on a Gradle project
property (e.g., project.hasProperty('skipTests') or
!project.hasProperty('runTests')) inside the tasks.named('test') block so tests
run by default and only skip when the -PskipTests flag is passed; ensure
useJUnitPlatform() remains configured in the same tasks.named('test') block.
In `@booklog/docs/BOOK_METADATA_CACHING_STRATEGY.md`:
- Around line 102-106: The table rows (e.g., entries for `homeBooks` /
`home:all` and `bookMetadata` / `book:{bookId}`) use pipe characters without
surrounding spaces which triggers MD060; update every markdown table in the
document so each column separator has a single space on both sides of the pipe
(e.g., " | " between columns) and ensure header separator rows match that
spacing style; apply the same fix consistently to all other tables in
BOOK_METADATA_CACHING_STRATEGY.md.
- Around line 11-29: This MD040 lint error is from unlabeled fenced code blocks
in BOOK_METADATA_CACHING_STRATEGY.md; update each triple-backtick fence (e.g.,
the block that starts with ``` before the flow diagram) to include an explicit
language identifier like text (```text) or java/bash where appropriate so
markdownlint passes and readability improves.
In `@booklog/docs/CACHING_IMPLEMENTATION_SUMMARY.md`:
- Around line 52-115: Update the Markdown by adding language identifiers to all
fenced code blocks (e.g., ```text for plain text blocks, ```bash or ```java
where snippets show commands or code) so markdownlint stops flagging them, and
normalize the performance table’s pipe spacing so each column separator has a
single space on both sides (fix the header and all rows), including the code
blocks under the "시나리오" sections and the performance table titled "📊 성능 개선 효과".
In `@booklog/docs/FINAL_DEPLOYMENT_GUIDE.md`:
- Around line 5-7: Fix the markdownlint warnings by labeling unlabeled code
fences (e.g., change the fence containing "BUILD SUCCESSFUL in 4s" to ```text
... ```), convert bare URLs like "https://www.memurai.com/get-memurai" to
angle-bracket form "<https://www.memurai.com/get-memurai>", and normalize table
separator rows to include spaces around cells (e.g., change
"|---------|----------|-------------|" to "| -------- | --------- |
------------- |"); apply the same fixes to the other occurrences flagged (lines
referenced in the review).
In `@booklog/docs/HOME_API_IMPLEMENTATION_SUMMARY.md`:
- Around line 19-25: The fenced code block listing DTO names (BookSummary,
RealTimeRankingSection, TaggedBooksSection, HomeResponse) lacks a language
specifier; update the triple-backtick fence to include a language token (e.g.,
```text) so markdownlint MD040 is satisfied and the block is treated as plain
text.
In `@booklog/docs/HOME_API_SPECIFICATION.md`:
- Around line 826-836: The spec currently shows `@Cacheable`(value = "homeData",
key = "'home'") and recommends a 1-hour TTL, but the runtime cache is configured
as homeBooks with a 6-hour TTL; reconcile by updating the spec to reference the
actual cache name and TTL used in the codebase (e.g., change the `@Cacheable`
value to "homeBooks" in examples or explicitly state that homeBooks is used) and
set the documented TTL to 6 hours so the documentation matches the cache
configuration (or alternately change the cache config to 1 hour if you prefer
the doc behavior).
In `@booklog/docs/REDIS_FAULT_TOLERANCE.md`:
- Around line 15-27: The markdown has unlabeled fenced code blocks and
improperly padded table separator rows causing MD040/MD060; update each affected
fenced block (e.g., the flow diagram blocks shown around the snippet and at
ranges 35-51, 59-64, 145-149, 175-179, 203-213, 226-234) by adding a language
tag such as "text" (e.g., ```text) and ensure any Markdown table separator rows
include spaces around pipes and dashes for padding; apply these changes across
all listed ranges so linting warnings are resolved.
In `@booklog/docs/REDIS_INSTALLATION_GUIDE.md`:
- Line 53: Replace the bare URL "https://github.com/tporadowski/redis/releases"
with a Markdown link for consistency (e.g. [tporadowski/redis
Releases](https://github.com/tporadowski/redis/releases)) so the URL is
displayed as a clickable link in REDIS_INSTALLATION_GUIDE.md; locate the plain
URL string and convert it to the bracketed link form.
- Line 24: Replace the bare URL
"https://www.docker.com/products/docker-desktop/" with a markdown link for
consistency; e.g., change the line containing that URL to use link syntax such
as [Docker Desktop](https://www.docker.com/products/docker-desktop/) so the URL
is rendered as a proper markdown link.
- Line 6: Replace the bare URL "https://www.memurai.com/get-memurai" with a
Markdown link by enclosing the URL in square brackets for link text and
parentheses for the URL (for example: [Memurai -
Get](https://www.memurai.com/get-memurai)); update the line containing that
exact URL so it becomes a proper Markdown link.
- Around line 99-102: The fenced code block showing Redis/Lettuce logs should
include a language identifier for proper rendering; locate the block containing
the lines "INFO ... - Lettuce connection initialized" and "INFO ... - Created
new connection to localhost:6379" and change the opening fence from ``` to
```text (or another appropriate language like ```log) so the block becomes
```text and closing fence remains ```; ensure there are no extra backticks or
spacing changes around the block.
In `@booklog/docs/UPDATE_NOTES_20260127.md`:
- Around line 93-96: The markdown code fence around the build output in
UPDATE_NOTES_20260127.md is missing a language tag (MD040); update the opening
triple-backtick fence for the block containing "BUILD SUCCESSFUL in 15s" and "5
actionable tasks: 5 executed" to use a language hint (e.g., change ``` to
```text) so the block is explicitly marked as plain text.
In
`@booklog/src/main/java/com/example/booklog/domain/home/service/BookMetadataService.java`:
- Around line 43-64: The methods getBookSummary(...) and getBookSummaries(...)
are annotated with `@Transactional`(readOnly = true) but call
booksRepository.save(...) (e.g., save(newBook) in getBookSummary and save(...)
in getBookSummaries), which can suppress writes; remove readOnly = true from the
`@Transactional` annotation on both methods (leave `@Transactional` active or
replace with `@Transactional` without readOnly) so the write operations
(booksRepository.save(...), enrichMetadataAsync triggers that expect
persistence) execute correctly and transaction semantics are preserved.
- Around line 91-95: booksRepository.findAllByTitleIn can return multiple Books
with the same title and Collectors.toMap(Books::getTitle, b -> b) will throw
IllegalStateException on duplicate keys; modify the mapping that builds bookMap
(the stream collecting into a Map) to handle duplicate titles by providing a
merge function (or by grouping and selecting one record) so duplicates are
resolved deterministically (e.g., keep first/last or choose by id/date) instead
of letting toMap fail; update the code around Books,
booksRepository.findAllByTitleIn and bookMap to use Collectors.toMap with a
(existing, incoming) merge rule or use Collectors.groupingBy followed by
pick-one to ensure no exception is thrown.
- Around line 43-58: The current getBookSummary caches incomplete BookSummary
results (null author/publisher/thumbnail) so enrichMetadataAsync updates DB but
the cache remains stale; change approach by either preventing caching of
incomplete metadata in getBookSummary (e.g., extend the `@Cacheable` condition to
skip when core fields are missing) or—preferred—ensure enrichMetadataAsync
evicts/refreshes the cache after a successful DB update by adding a cache
eviction (e.g., `@CacheEvict`(value="bookMetadata", key="'book:' + `#bookId`") or
programmatic eviction) in the enrichMetadataAsync method immediately after
updating the Books entity so subsequent calls to getBookSummary use fresh data
from createBookSummary.
- Around line 145-147: The Kakao API call in BookMetadataService currently uses
kakaoBookClient.search(title, 1, 1).block() without a timeout which can hang
async threads; change this to use a bounded wait (e.g., call
block(Duration.ofSeconds(...)) or configure a request timeout on the WebClient
in KakaoBookClient) and add handling for timeout-related exceptions (e.g., catch
TimeoutException/RuntimeException and log/translate to your domain exception).
Locate the call site in BookMetadataService (the kakaoBookClient.search(...)
invocation) and either add a Duration parameter to block(...) or adjust the
KakaoBookClient WebClient timeout settings, and ensure downstream code handles
the timeout case gracefully.
In
`@booklog/src/main/java/com/example/booklog/domain/library/books/repository/BooksRepository.java`:
- Around line 19-30: The repository exposes findByTitle(String) but Books.title
has no uniqueness constraint and findAllByTitleIn(List<String>) lacks an
empty-list guard; either enforce title uniqueness in the Books entity and DB
migration (add `@Column`(unique=true) or equivalent) if title must be unique, or
change the repository API to reflect possible multiples by replacing
Optional<Books> findByTitle(String) with List<Books> findByTitle(String) (and
update callers). Also add an empty-list guard for the bulk lookup by adding a
safe wrapper on the repository (e.g., a default method in BooksRepository like
findAllByTitleInSafe(List<String> titles) that returns an empty list if titles
is null/empty and delegates to findAllByTitleIn otherwise) or validate and
short-circuit in callers such as HomeServiceImpl.
🧹 Nitpick comments (4)
booklog/src/main/java/com/example/booklog/global/config/AsyncConfig.java (1)
20-27: Consider graceful shutdown + explicit rejection policy for the async pool.Without shutdown settings, queued enrichment tasks can be dropped on shutdown, and the default rejection policy can throw when the queue is full. A small config tweak improves resiliency.
♻️ Suggested config
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; @@ public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(100); executor.setThreadNamePrefix("BookMetadata-Async-"); + executor.setWaitForTasksToCompleteOnShutdown(true); + executor.setAwaitTerminationSeconds(30); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; }booklog/src/main/java/com/example/booklog/global/config/RedisCacheConfig.java (1)
75-79: In-memory fallback doesn’t expire entries; consider a TTL-capable cache.
ConcurrentMapCacheManagerdoesn’t enforce TTLs, so if Redis is unavailable at startup, cached data can persist indefinitely. If freshness matters, consider a TTL-capable in-memory cache (e.g., Caffeine) to preserve the intended expiration behavior.♻️ Example (requires Caffeine dependency)
+import com.github.benmanes.caffeine.cache.Caffeine; +import org.springframework.cache.caffeine.CaffeineCache; +import org.springframework.cache.support.SimpleCacheManager; +import java.util.List; @@ `@Bean` public CacheManager inMemoryCacheManager() { log.info("📦 인메모리 캐시 활성화 (ConcurrentMap) - Redis 미사용"); - return new ConcurrentMapCacheManager("homeBooks", "bookMetadata"); + CaffeineCache homeBooks = new CaffeineCache( + "homeBooks", + Caffeine.newBuilder().expireAfterWrite(Duration.ofHours(6)).build() + ); + CaffeineCache bookMetadata = new CaffeineCache( + "bookMetadata", + Caffeine.newBuilder().expireAfterWrite(Duration.ofDays(7)).build() + ); + SimpleCacheManager manager = new SimpleCacheManager(); + manager.setCaches(List.of(homeBooks, bookMetadata)); + return manager; }booklog/src/main/resources/application.yaml (1)
50-55: Align cache TTL configuration between YAML andRedisCacheConfig.
spring.cache.redis.time-to-liveis 6 hours here, butRedisCacheConfigsets a 24-hour default and per-cache TTLs. If the customCacheManageris used, this YAML value is likely ignored—consider removing it or aligning values to avoid confusion.✅ Simplify to avoid conflicting defaults
cache: type: redis redis: - time-to-live: 21600000 # 기본 TTL 6시간 (ms) # Redis 연결 실패 시 캐시 비활성화 (애플리케이션은 정상 작동) enable-statistics: truebooklog/src/main/java/com/example/booklog/domain/home/service/HomeServiceImpl.java (1)
27-72: Avoid hard-coded fallback IDs; derive fromBOOK_ID_MAPPING.
BOOK_ID_MAPPINGalready defines the source of truth, butbuildRealTimeRankingre-hardcodes IDs, which can drift. Consider a helper that derives IDs from the mapping.♻️ Possible refactor
+ private BookSummary getOrFallback(Map<String, BookSummary> bookMap, String title, int ranking) { + Long bookId = BOOK_ID_MAPPING.getOrDefault(title, 0L); + return bookMap.getOrDefault(title, createFallback(bookId, title, ranking)); + } ... - bookMap.getOrDefault("트렌드 코리아 2026", createFallback(1L, "트렌드 코리아 2026", 1)), + getOrFallback(bookMap, "트렌드 코리아 2026", 1),Also applies to: 139-161
| tasks.named('test') { | ||
| useJUnitPlatform() | ||
| enabled = false // 테스트 비활성화 -> 에러가 너무 많아서 잠시 비활성화 | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "build.gradle" -type f | head -20Repository: Project-BookLog/BookLog-BackEnd
Length of output: 97
🏁 Script executed:
wc -l ./booklog/build.gradleRepository: Project-BookLog/BookLog-BackEnd
Length of output: 100
🏁 Script executed:
cat -n ./booklog/build.gradleRepository: Project-BookLog/BookLog-BackEnd
Length of output: 3554
🏁 Script executed:
# Verify if the suggested conditional approach is valid Gradle syntax
# by checking Gradle documentation patterns and examples
web_search "Gradle conditional disable test task project property -P"Repository: Project-BookLog/BookLog-BackEnd
Length of output: 125
🌐 Web query:
Gradle how to conditionally disable test task using project property skipTests
💡 Result:
Use a project property and make the Test tasks conditional.
Groovy DSL (build.gradle)
tasks.withType(Test).configureEach {
onlyIf { !project.hasProperty("skipTests") }
}Run with:
./gradlew build -PskipTestsThis skips tests when skipTests is present. [1]
If you want to disable the task (instead of conditional execution):
tasks.withType(Test).configureEach {
enabled = !project.hasProperty("skipTests")
}(Every task has an enabled flag.) [2]
Kotlin DSL (build.gradle.kts)
tasks.withType<Test>().configureEach {
onlyIf { !project.hasProperty("skipTests") }
}or:
tasks.withType<Test>().configureEach {
enabled = !project.hasProperty("skipTests")
}Alternative (one-off from CLI)
./gradlew build -x testThis excludes the test task for that invocation. [1]
Sources: Gradle docs on skipping tests via onlyIf / -x [1] and task enabled flag [2].
[1] https://docs.gradle.org/current/userguide/java_testing.html
[2] https://docs.gradle.org/current/userguide/controlling_task_execution.html
🏁 Script executed:
find . -name "*.yml" -o -name "*.yaml" | grep -E "(github|gitlab|ci|workflow)" | head -10Repository: Project-BookLog/BookLog-BackEnd
Length of output: 109
🏁 Script executed:
cat -n ./.github/workflows/dev_deploy.ymlRepository: Project-BookLog/BookLog-BackEnd
Length of output: 2503
Don't disable tests globally in the build script.
Line 76 unconditionally disables tests, which prevents developers from running tests locally by default and can let regressions ship. While your CI uses -x test to skip tests during deployment, the root issue is the blanket disable in the build.gradle configuration. Prefer a conditional skip flag (e.g., -PskipTests) or remove the disable entirely and use CI-only flags.
Suggested fix (conditional skip)
tasks.named('test') {
useJUnitPlatform()
- enabled = false // 테스트 비활성화 -> 에러가 너무 많아서 잠시 비활성화
+ enabled = !project.hasProperty("skipTests")
}This allows ./gradlew build to run tests by default, and ./gradlew build -PskipTests to skip them when needed.
🤖 Prompt for AI Agents
In `@booklog/build.gradle` around lines 74 - 77, The build.gradle currently
disables the test task unconditionally via tasks.named('test') { enabled = false
}, which prevents local test runs; change this to respect a skip flag instead:
remove the unconditional enabled = false and use a conditional check on a Gradle
project property (e.g., project.hasProperty('skipTests') or
!project.hasProperty('runTests')) inside the tasks.named('test') block so tests
run by default and only skip when the -PskipTests flag is passed; ensure
useJUnitPlatform() remains configured in the same tasks.named('test') block.
| ``` | ||
| [사용자 요청] | ||
| ↓ | ||
| [HomeController] → GET /api/v1/home | ||
| ↓ | ||
| [HomeServiceImpl] → @Cacheable("homeBooks") | ||
| ↓ | ||
| [BookMetadataService] → DB 우선 조회 + @Cacheable("bookMetadata") | ||
| ↓ | ||
| ┌────────────────┐ | ||
| │ 1. DB 조회 │ → Books 엔티티 (author/publisher/thumbnail 포함) | ||
| └────────────────┘ | ||
| │ | ||
| ├─ 있음 → 즉시 응답 (지연 없음) | ||
| │ └─ 메타데이터 미보강? → 비동기로 카카오 API 호출 (@Async) | ||
| │ | ||
| └─ 없음 → 빈 Books 생성 후 즉시 응답 | ||
| └─ 비동기로 카카오 API 호출 + DB 업데이트 | ||
| ``` |
There was a problem hiding this comment.
Add language identifiers to fenced code blocks (MD040).
markdownlint reports missing languages on multiple fences; please label them (e.g., text, java, bash) for lint compliance and readability.
✅ Example fix (apply similarly to other fenced blocks)
-```
+```text
[사용자 요청]
↓
[HomeController] → GET /api/v1/home
@@
-```
+```📝 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.
| ``` | |
| [사용자 요청] | |
| ↓ | |
| [HomeController] → GET /api/v1/home | |
| ↓ | |
| [HomeServiceImpl] → @Cacheable("homeBooks") | |
| ↓ | |
| [BookMetadataService] → DB 우선 조회 + @Cacheable("bookMetadata") | |
| ↓ | |
| ┌────────────────┐ | |
| │ 1. DB 조회 │ → Books 엔티티 (author/publisher/thumbnail 포함) | |
| └────────────────┘ | |
| │ | |
| ├─ 있음 → 즉시 응답 (지연 없음) | |
| │ └─ 메타데이터 미보강? → 비동기로 카카오 API 호출 (@Async) | |
| │ | |
| └─ 없음 → 빈 Books 생성 후 즉시 응답 | |
| └─ 비동기로 카카오 API 호출 + DB 업데이트 | |
| ``` |
🧰 Tools
🪛 markdownlint-cli2 (0.20.0)
[warning] 11-11: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
In `@booklog/docs/BOOK_METADATA_CACHING_STRATEGY.md` around lines 11 - 29, This
MD040 lint error is from unlabeled fenced code blocks in
BOOK_METADATA_CACHING_STRATEGY.md; update each triple-backtick fence (e.g., the
block that starts with ``` before the flow diagram) to include an explicit
language identifier like text (```text) or java/bash where appropriate so
markdownlint passes and readability improves.
| | 캐시 이름 | Key | TTL | 용도 | | ||
| |----------|-----|-----|------| | ||
| | `homeBooks` | `home:all` | 6시간 | 홈 화면 전체 응답 | | ||
| | `bookMetadata` | `book:{bookId}` | 7일 | 개별 도서 메타데이터 | | ||
|
|
There was a problem hiding this comment.
Fix table spacing style to satisfy MD060.
The table pipes lack surrounding spaces, which triggers MD060. Apply the same spacing fix to other tables in the doc.
✅ Example fix
-| 캐시 이름 | Key | TTL | 용도 |
-|----------|-----|-----|------|
-| `homeBooks` | `home:all` | 6시간 | 홈 화면 전체 응답 |
-| `bookMetadata` | `book:{bookId}` | 7일 | 개별 도서 메타데이터 |
+| 캐시 이름 | Key | TTL | 용도 |
+| ---------- | ----- | ----- | ------ |
+| `homeBooks` | `home:all` | 6시간 | 홈 화면 전체 응답 |
+| `bookMetadata` | `book:{bookId}` | 7일 | 개별 도서 메타데이터 |📝 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.
| | 캐시 이름 | Key | TTL | 용도 | | |
| |----------|-----|-----|------| | |
| | `homeBooks` | `home:all` | 6시간 | 홈 화면 전체 응답 | | |
| | `bookMetadata` | `book:{bookId}` | 7일 | 개별 도서 메타데이터 | | |
| | 캐시 이름 | Key | TTL | 용도 | | |
| | ---------- | ----- | ----- | ------ | | |
| | `homeBooks` | `home:all` | 6시간 | 홈 화면 전체 응답 | | |
| | `bookMetadata` | `book:{bookId}` | 7일 | 개별 도서 메타데이터 | |
🧰 Tools
🪛 markdownlint-cli2 (0.20.0)
[warning] 103-103: Table column style
Table pipe is missing space to the right for style "compact"
(MD060, table-column-style)
[warning] 103-103: Table column style
Table pipe is missing space to the left for style "compact"
(MD060, table-column-style)
[warning] 103-103: Table column style
Table pipe is missing space to the right for style "compact"
(MD060, table-column-style)
[warning] 103-103: Table column style
Table pipe is missing space to the left for style "compact"
(MD060, table-column-style)
[warning] 103-103: Table column style
Table pipe is missing space to the right for style "compact"
(MD060, table-column-style)
[warning] 103-103: Table column style
Table pipe is missing space to the left for style "compact"
(MD060, table-column-style)
[warning] 103-103: Table column style
Table pipe is missing space to the right for style "compact"
(MD060, table-column-style)
[warning] 103-103: Table column style
Table pipe is missing space to the left for style "compact"
(MD060, table-column-style)
🤖 Prompt for AI Agents
In `@booklog/docs/BOOK_METADATA_CACHING_STRATEGY.md` around lines 102 - 106, The
table rows (e.g., entries for `homeBooks` / `home:all` and `bookMetadata` /
`book:{bookId}`) use pipe characters without surrounding spaces which triggers
MD060; update every markdown table in the document so each column separator has
a single space on both sides of the pipe (e.g., " | " between columns) and
ensure header separator rows match that spacing style; apply the same fix
consistently to all other tables in BOOK_METADATA_CACHING_STRATEGY.md.
| ### 시나리오 1: 최초 진입 (DB 없음) | ||
| ``` | ||
| 사용자 요청 (t=0) | ||
| ↓ | ||
| DB 조회 → 없음 (50ms) | ||
| ↓ | ||
| 빈 Books 생성 + 응답 (200ms) ✅ 사용자에게 즉시 반환 | ||
| ↓ | ||
| [별도 스레드] 카카오 API 호출 (500ms) | ||
| ↓ | ||
| DB 업데이트 완료 (t=700ms) | ||
| ``` | ||
| **사용자 체감**: 200ms (빠름) | ||
|
|
||
| ### 시나리오 2: 2번째 진입 (Redis 캐시) | ||
| ``` | ||
| 사용자 요청 (t=0) | ||
| ↓ | ||
| Redis 조회 → 히트! (10ms) ✅ | ||
| ``` | ||
| **사용자 체감**: 10ms (매우 빠름) | ||
|
|
||
| ### 시나리오 3: 캐시 만료 (DB 조회) | ||
| ``` | ||
| 사용자 요청 (t=0) | ||
| ↓ | ||
| Redis 조회 → 미스 (5ms) | ||
| ↓ | ||
| DB 조회 → 히트 (100ms) ✅ | ||
| ↓ | ||
| Redis 캐시 갱신 | ||
| ``` | ||
| **사용자 체감**: 100ms (빠름) | ||
|
|
||
| --- | ||
|
|
||
| ## 📊 성능 개선 효과 | ||
|
|
||
| | 항목 | Before (동기) | After (캐싱+비동기) | 개선율 | | ||
| |-----|-------------|------------------|-------| | ||
| | 최초 응답 시간 | 10초 | 200ms | **50배 개선** | | ||
| | 2번째 응답 시간 | 10초 | 10ms | **1000배 개선** | | ||
| | 외부 API 호출 | 매번 | 최초 1회만 | **99% 감소** | | ||
| | 캐시 히트율 | 0% | 95%+ | - | | ||
|
|
||
| --- | ||
|
|
||
| ## 🗂️ 파일 목록 | ||
|
|
||
| ### 신규 생성 | ||
| ``` | ||
| global/config/ | ||
| ├─ RedisCacheConfig.java # Redis 캐시 설정 | ||
| └─ AsyncConfig.java # 비동기 처리 설정 | ||
|
|
||
| domain/home/service/ | ||
| └─ BookMetadataService.java # 메타데이터 관리 서비스 | ||
|
|
||
| docs/ | ||
| └─ BOOK_METADATA_CACHING_STRATEGY.md # 전략 문서 | ||
| ``` | ||
|
|
||
| ### 수정 | ||
| ``` |
There was a problem hiding this comment.
Add language identifiers to fenced blocks and fix table pipe spacing.
markdownlint flags several fenced blocks without a language and the performance table’s pipe spacing. Please add language tags (e.g., text, bash, java) and normalize the table formatting.
📝 Example fixes (apply similarly to all flagged blocks)
-```
+```text-|항목|Before (동기)|After (캐싱+비동기)|개선율|
+| 항목 | Before (동기) | After (캐싱+비동기) | 개선율 |Also applies to: 244-246
🧰 Tools
🪛 markdownlint-cli2 (0.20.0)
[warning] 53-53: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 67-67: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 75-75: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 91-91: Table column style
Table pipe is missing space to the right for style "compact"
(MD060, table-column-style)
[warning] 91-91: Table column style
Table pipe is missing space to the left for style "compact"
(MD060, table-column-style)
[warning] 91-91: Table column style
Table pipe is missing space to the right for style "compact"
(MD060, table-column-style)
[warning] 91-91: Table column style
Table pipe is missing space to the left for style "compact"
(MD060, table-column-style)
[warning] 91-91: Table column style
Table pipe is missing space to the right for style "compact"
(MD060, table-column-style)
[warning] 91-91: Table column style
Table pipe is missing space to the left for style "compact"
(MD060, table-column-style)
[warning] 91-91: Table column style
Table pipe is missing space to the right for style "compact"
(MD060, table-column-style)
[warning] 91-91: Table column style
Table pipe is missing space to the left for style "compact"
(MD060, table-column-style)
[warning] 102-102: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 115-115: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
In `@booklog/docs/CACHING_IMPLEMENTATION_SUMMARY.md` around lines 52 - 115, Update
the Markdown by adding language identifiers to all fenced code blocks (e.g.,
```text for plain text blocks, ```bash or ```java where snippets show commands
or code) so markdownlint stops flagging them, and normalize the performance
table’s pipe spacing so each column separator has a single space on both sides
(fix the header and all rows), including the code blocks under the "시나리오"
sections and the performance table titled "📊 성능 개선 효과".
| ``` | ||
| BUILD SUCCESSFUL in 4s | ||
| ``` |
There was a problem hiding this comment.
Address markdownlint warnings (code fence language, bare URL, table separator spacing).
These items are flagged by markdownlint (MD040/MD034/MD060). Consider applying the same fixes to all unlabeled fences and separator rows in this file.
📝 Suggested fixes
-```
+```text
BUILD SUCCESSFUL in 4s...
-- https://www.memurai.com/get-memurai
+- https://www.memurai.com/get-memurai
...
-|---------|----------|-------------|
+| -------- | --------- | ------------- |
</details>
Also applies to: 44-45, 126-130, 136-138, 207-209, 217-219
<details>
<summary>🧰 Tools</summary>
<details>
<summary>🪛 markdownlint-cli2 (0.20.0)</summary>
[warning] 5-5: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
</details>
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
In @booklog/docs/FINAL_DEPLOYMENT_GUIDE.md around lines 5 - 7, Fix the
markdownlint warnings by labeling unlabeled code fences (e.g., change the fence
containing "BUILD SUCCESSFUL in 4s" to text ... ), convert bare URLs like
"https://www.memurai.com/get-memurai" to angle-bracket form
"https://www.memurai.com/get-memurai", and normalize table separator rows to
include spaces around cells (e.g., change "|---------|----------|-------------|"
to "| -------- | --------- | ------------- |"); apply the same fixes to the
other occurrences flagged (lines referenced in the review).
</details>
<!-- fingerprinting:phantom:medusa:eagle -->
<!-- This is an auto-generated comment by CodeRabbit -->
| @Cacheable(value = "bookMetadata", key = "'book:' + #bookId", unless = "#result == null") | ||
| @Transactional(readOnly = true) | ||
| public BookSummary getBookSummary(Long bookId, String title, Integer ranking) { | ||
| // 1. DB에서 조회 | ||
| Optional<Books> bookOpt = booksRepository.findByTitle(title); | ||
|
|
||
| if (bookOpt.isPresent()) { | ||
| Books book = bookOpt.get(); | ||
|
|
||
| // 2. 메타데이터 보강 필요 시 비동기로 처리 | ||
| if (needsMetadataEnrichment(book)) { | ||
| enrichMetadataAsync(book.getId(), title); | ||
| } | ||
|
|
||
| // 3. 현재 DB 데이터로 응답 (즉시 반환) | ||
| return createBookSummary(bookId, book, ranking); | ||
| } | ||
|
|
||
| // 4. DB에 없으면 빈 Books 생성 후 비동기 보강 | ||
| Books newBook = createEmptyBook(title); | ||
| Books savedBook = booksRepository.save(newBook); | ||
| enrichMetadataAsync(savedBook.getId(), title); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "BookMetadataService.java" -type fRepository: Project-BookLog/BookLog-BackEnd
Length of output: 163
🏁 Script executed:
cat -n ./booklog/src/main/java/com/example/booklog/domain/home/service/BookMetadataService.javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 9976
Change to read‑write transactions where writes occur.
Both getBookSummary (line 44) and getBookSummaries (line 83) are marked @Transactional(readOnly = true) but call booksRepository.save(...) at lines 63 and 110 respectively. In read-only mode, writes may be suppressed or delayed depending on JPA configuration, causing data loss or inconsistent state.
Remove readOnly = true from both methods since they perform write operations:
Suggested fix
- `@Transactional`(readOnly = true)
+ `@Transactional`
public BookSummary getBookSummary(Long bookId, String title, Integer ranking) {- `@Transactional`(readOnly = true)
+ `@Transactional`
public List<BookSummary> getBookSummaries(List<BookInfo> bookInfoList) {🤖 Prompt for AI Agents
In
`@booklog/src/main/java/com/example/booklog/domain/home/service/BookMetadataService.java`
around lines 43 - 64, The methods getBookSummary(...) and getBookSummaries(...)
are annotated with `@Transactional`(readOnly = true) but call
booksRepository.save(...) (e.g., save(newBook) in getBookSummary and save(...)
in getBookSummaries), which can suppress writes; remove readOnly = true from the
`@Transactional` annotation on both methods (leave `@Transactional` active or
replace with `@Transactional` without readOnly) so the write operations
(booksRepository.save(...), enrichMetadataAsync triggers that expect
persistence) execute correctly and transaction semantics are preserved.
| @Cacheable(value = "bookMetadata", key = "'book:' + #bookId", unless = "#result == null") | ||
| @Transactional(readOnly = true) | ||
| public BookSummary getBookSummary(Long bookId, String title, Integer ranking) { | ||
| // 1. DB에서 조회 | ||
| Optional<Books> bookOpt = booksRepository.findByTitle(title); | ||
|
|
||
| if (bookOpt.isPresent()) { | ||
| Books book = bookOpt.get(); | ||
|
|
||
| // 2. 메타데이터 보강 필요 시 비동기로 처리 | ||
| if (needsMetadataEnrichment(book)) { | ||
| enrichMetadataAsync(book.getId(), title); | ||
| } | ||
|
|
||
| // 3. 현재 DB 데이터로 응답 (즉시 반환) | ||
| return createBookSummary(bookId, book, ranking); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "BookMetadataService.java"Repository: Project-BookLog/BookLog-BackEnd
Length of output: 163
🏁 Script executed:
cat -n ./booklog/src/main/java/com/example/booklog/domain/home/service/BookMetadataService.javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 9976
The cache will serve stale null metadata until TTL expires; enrichMetadataAsync doesn't evict or refresh the cached entry.
When a BookSummary with null author/publisher/thumbnail is cached, subsequent calls within 6 hours will return that cached stale state even after enrichment completes and the database is updated. The enrichMetadataAsync method updates the Books entity but has no cache invalidation (@CacheEvict or @CachePut).
To fix this, either skip caching incomplete metadata or evict the cache after enrichment. The conditional unless approach is a partial mitigation:
Option 1: Skip caching when core fields are missing
- `@Cacheable`(value = "bookMetadata", key = "'book:' + `#bookId`", unless = "#result == null")
+ `@Cacheable`(
+ value = "bookMetadata",
+ key = "'book:' + `#bookId`",
+ unless = "#result == null || `#result.author` == null || `#result.publisher` == null || `#result.coverImageUrl` == null"
+ )Better: Add @CacheEvict(value = "bookMetadata", key = "'book:' + #bookId") in enrichMetadataAsync after successfully updating the database (after line 178) to immediately refresh the cache.
🤖 Prompt for AI Agents
In
`@booklog/src/main/java/com/example/booklog/domain/home/service/BookMetadataService.java`
around lines 43 - 58, The current getBookSummary caches incomplete BookSummary
results (null author/publisher/thumbnail) so enrichMetadataAsync updates DB but
the cache remains stale; change approach by either preventing caching of
incomplete metadata in getBookSummary (e.g., extend the `@Cacheable` condition to
skip when core fields are missing) or—preferred—ensure enrichMetadataAsync
evicts/refreshes the cache after a successful DB update by adding a cache
eviction (e.g., `@CacheEvict`(value="bookMetadata", key="'book:' + `#bookId`") or
programmatic eviction) in the enrichMetadataAsync method immediately after
updating the Books entity so subsequent calls to getBookSummary use fresh data
from createBookSummary.
| // 2. 일괄 조회 (IN 쿼리 1번) | ||
| List<Books> books = booksRepository.findAllByTitleIn(titles); | ||
| Map<String, Books> bookMap = books.stream() | ||
| .collect(Collectors.toMap(Books::getTitle, b -> b)); | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "Books.java" -o -name "Book.java" | head -20Repository: Project-BookLog/BookLog-BackEnd
Length of output: 223
🏁 Script executed:
find . -type f -name "*Entity*.java" | grep -i book | head -20Repository: Project-BookLog/BookLog-BackEnd
Length of output: 245
🏁 Script executed:
fd -e java | grep -iE "(books|book)" | head -30Repository: Project-BookLog/BookLog-BackEnd
Length of output: 2726
🏁 Script executed:
cat -n ./booklog/src/main/java/com/example/booklog/domain/library/books/entity/Books.javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 5718
🏁 Script executed:
cat -n ./booklog/src/main/java/com/example/booklog/domain/home/service/BookMetadataService.java | head -120Repository: Project-BookLog/BookLog-BackEnd
Length of output: 4917
Guard against duplicate titles when building the lookup map.
Collectors.toMap throws IllegalStateException on duplicate keys. The Books.title column has no unique constraint, so multiple records with the same title can exist. When findAllByTitleIn(titles) returns duplicates, the toMap on line 93–94 will fail with a 500 error. Add a merge function or enforce uniqueness.
🛠️ Example safeguard
- Map<String, Books> bookMap = books.stream()
- .collect(Collectors.toMap(Books::getTitle, b -> b));
+ Map<String, Books> bookMap = books.stream()
+ .collect(Collectors.toMap(Books::getTitle, b -> b, (existing, duplicate) -> existing));🤖 Prompt for AI Agents
In
`@booklog/src/main/java/com/example/booklog/domain/home/service/BookMetadataService.java`
around lines 91 - 95, booksRepository.findAllByTitleIn can return multiple Books
with the same title and Collectors.toMap(Books::getTitle, b -> b) will throw
IllegalStateException on duplicate keys; modify the mapping that builds bookMap
(the stream collecting into a Map) to handle duplicate titles by providing a
merge function (or by grouping and selecting one record) so duplicates are
resolved deterministically (e.g., keep first/last or choose by id/date) instead
of letting toMap fail; update the code around Books,
booksRepository.findAllByTitleIn and bookMap to use Collectors.toMap with a
(existing, incoming) merge rule or use Collectors.groupingBy followed by
pick-one to ensure no exception is thrown.
| // 카카오 API 호출 | ||
| KakaoBookSearchResponse response = kakaoBookClient.search(title, 1, 1).block(); | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "BookMetadataService.java" | head -5Repository: Project-BookLog/BookLog-BackEnd
Length of output: 163
🏁 Script executed:
find . -type f -name "KakaoBookClient.java" | head -5Repository: Project-BookLog/BookLog-BackEnd
Length of output: 175
🏁 Script executed:
find . -type f -name "*KakaoBookClient*" -o -name "*KakaoBook*" | grep -E "\.(java|kt)$" | head -10Repository: Project-BookLog/BookLog-BackEnd
Length of output: 273
🏁 Script executed:
cat -n ./booklog/src/main/java/com/example/booklog/domain/home/service/BookMetadataService.java | sed -n '140,155p'Repository: Project-BookLog/BookLog-BackEnd
Length of output: 766
🏁 Script executed:
wc -l ./booklog/src/main/java/com/example/booklog/domain/library/books/service/client/KakaoBookClient.javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 178
🏁 Script executed:
cat -n ./booklog/src/main/java/com/example/booklog/domain/library/books/service/client/KakaoBookClient.javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 1201
🏁 Script executed:
find . -type f -name "*Config*.java" | xargs grep -l "kakaoWebClient\|WebClient" | head -10Repository: Project-BookLog/BookLog-BackEnd
Length of output: 153
🏁 Script executed:
rg "kakaoWebClient" -t java -B 3 -A 3Repository: Project-BookLog/BookLog-BackEnd
Length of output: 2280
🏁 Script executed:
rg "WebClientConfig\|@Bean.*WebClient\|@Bean.*kakao" -t java -B 2 -A 10Repository: Project-BookLog/BookLog-BackEnd
Length of output: 57
🏁 Script executed:
cat -n ./booklog/src/main/java/com/example/booklog/global/config/WebClientConfig.javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 1196
Add a timeout to the Kakao API call to avoid hung async threads.
block() without a timeout can pin async threads during network stalls. The WebClient bean has no timeout configuration, and block() is called without a timeout parameter. Consider block(Duration) or configure WebClient timeouts in KakaoBookClient, and handle timeout exceptions.
⏱️ Example timeout
+import java.time.Duration;
@@
- KakaoBookSearchResponse response = kakaoBookClient.search(title, 1, 1).block();
+ KakaoBookSearchResponse response =
+ kakaoBookClient.search(title, 1, 1).block(Duration.ofSeconds(3));📝 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.
| // 카카오 API 호출 | |
| KakaoBookSearchResponse response = kakaoBookClient.search(title, 1, 1).block(); | |
| // 카카오 API 호출 | |
| KakaoBookSearchResponse response = | |
| kakaoBookClient.search(title, 1, 1).block(Duration.ofSeconds(3)); |
🤖 Prompt for AI Agents
In
`@booklog/src/main/java/com/example/booklog/domain/home/service/BookMetadataService.java`
around lines 145 - 147, The Kakao API call in BookMetadataService currently uses
kakaoBookClient.search(title, 1, 1).block() without a timeout which can hang
async threads; change this to use a bounded wait (e.g., call
block(Duration.ofSeconds(...)) or configure a request timeout on the WebClient
in KakaoBookClient) and add handling for timeout-related exceptions (e.g., catch
TimeoutException/RuntimeException and log/translate to your domain exception).
Locate the call site in BookMetadataService (the kakaoBookClient.search(...)
invocation) and either add a Duration parameter to block(...) or adjust the
KakaoBookClient WebClient timeout settings, and ensure downstream code handles
the timeout case gracefully.
| Optional<Books> findByTitle(String title); | ||
|
|
||
| /** | ||
| * 홈 화면용 title 일괄 조회 | ||
| * Fetch Join으로 BookAuthors와 Authors를 함께 조회 | ||
| */ | ||
| @Query("SELECT DISTINCT b FROM Books b " + | ||
| "LEFT JOIN FETCH b.bookAuthors ba " + | ||
| "LEFT JOIN FETCH ba.author a " + | ||
| "WHERE b.title IN :titles") | ||
| @QueryHints(@QueryHint(name = "hibernate.query.passDistinctThrough", value = "false")) | ||
| List<Books> findAllByTitleIn(@Param("titles") List<String> titles); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find and read the Books entity to check for uniqueness constraints on title
find . -name "Books.java" -type f | head -5Repository: Project-BookLog/BookLog-BackEnd
Length of output: 157
🏁 Script executed:
# Also find the full BooksRepository interface
find . -name "BooksRepository.java" -type f | head -5Repository: Project-BookLog/BookLog-BackEnd
Length of output: 171
🏁 Script executed:
# Search for usage of findAllByTitleIn and findByTitle methods
rg "findAllByTitleIn|findByTitle" --type java -B 2 -A 2Repository: Project-BookLog/BookLog-BackEnd
Length of output: 2709
🏁 Script executed:
# Read the Books entity to check for title uniqueness constraints
cat -n ./booklog/src/main/java/com/example/booklog/domain/library/books/entity/Books.javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 5718
🏁 Script executed:
# Read the relevant section of BookMetadataService to see context of findAllByTitleIn usage
cat -n ./booklog/src/main/java/com/example/booklog/domain/home/service/BookMetadataService.java | head -80Repository: Project-BookLog/BookLog-BackEnd
Length of output: 3081
🏁 Script executed:
# Continue reading BookMetadataService to see the full context of findAllByTitleIn usage
cat -n ./booklog/src/main/java/com/example/booklog/domain/home/service/BookMetadataService.java | sed -n '75,150p'Repository: Project-BookLog/BookLog-BackEnd
Length of output: 3101
🏁 Script executed:
# Search for any guards or validation around empty titles list in BookMetadataService
rg "isEmpty|isNull|isEmpty|titles" ./booklog/src/main/java/com/example/booklog/domain/home/service/BookMetadataService.java -B 2 -A 2Repository: Project-BookLog/BookLog-BackEnd
Length of output: 1139
🏁 Script executed:
# Search for callers of getBookSummaries to see if they validate input lists
rg "getBookSummaries" --type java -B 3 -A 3Repository: Project-BookLog/BookLog-BackEnd
Length of output: 1836
🏁 Script executed:
# Check if there are any test cases that might reveal empty list behavior
rg "findAllByTitleIn|getBookSummaries" --type java ./booklog/src/test -B 2 -A 2 2>/dev/null || echo "No test files found with these patterns"Repository: Project-BookLog/BookLog-BackEnd
Length of output: 114
🏁 Script executed:
# Search for any existing validation in the controller/service layer that calls getBookSummaries
rg "bookInfoList|BookInfo" --type java -B 2 -A 2 | head -40Repository: Project-BookLog/BookLog-BackEnd
Length of output: 4849
🏁 Script executed:
# Check if title is used as a unique identifier elsewhere in the codebase
rg "findByTitle|getTitle" --type java | grep -i "unique\|constraint\|key" | head -20Repository: Project-BookLog/BookLog-BackEnd
Length of output: 57
🏁 Script executed:
# Look at the full BooksRepository interface to see all methods and their assumptions
cat -n ./booklog/src/main/java/com/example/booklog/domain/library/books/repository/BooksRepository.javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 4936
Add empty-list guard for findAllByTitleIn() and reconsider title-uniqueness assumptions.
The Books entity does not enforce a unique constraint on the title column (only a non-unique index for query performance). This means findByTitle(String) can return arbitrary results if multiple books share the same title—the method signature Optional<Books> is misleading. Additionally, findAllByTitleIn() receives no validation for empty lists; while the current caller (HomeServiceImpl) always provides titles, a defensive check is recommended to prevent silent failures if the method is called with an empty list elsewhere.
Consider:
- Adding a uniqueness constraint on the title column if title should be unique by design, or documenting that this method returns the first matching book.
- Adding a guard method in BooksRepository (or validating in callers) to safely handle empty lists.
🤖 Prompt for AI Agents
In
`@booklog/src/main/java/com/example/booklog/domain/library/books/repository/BooksRepository.java`
around lines 19 - 30, The repository exposes findByTitle(String) but Books.title
has no uniqueness constraint and findAllByTitleIn(List<String>) lacks an
empty-list guard; either enforce title uniqueness in the Books entity and DB
migration (add `@Column`(unique=true) or equivalent) if title must be unique, or
change the repository API to reflect possible multiples by replacing
Optional<Books> findByTitle(String) with List<Books> findByTitle(String) (and
update callers). Also add an empty-list guard for the bulk lookup by adding a
safe wrapper on the repository (e.g., a default method in BooksRepository like
findAllByTitleInSafe(List<String> titles) that returns an empty list if titles
is null/empty and delegates to findAllByTitleIn otherwise) or validate and
short-circuit in callers such as HomeServiceImpl.
Summary by CodeRabbit
New Features
Bug Fixes
Chores
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.