SCRUM 236 : 최근 검색어 기능 리팩토링#100
Conversation
WalkthroughThis update introduces a recent search history feature for users in the star domain. It adds endpoints and service methods to save, retrieve, and delete recent search keywords per user using Redis, with a maximum of 10 entries and a 30-day retention policy. Minor documentation and comment updates are also included. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant StarV2Controller
participant StarCommandService
participant StarQueryService
participant Redis
User->>StarV2Controller: GET /recent-searches (with userId)
StarV2Controller->>StarQueryService: getRecentSearches(userId)
StarQueryService->>Redis: LRANGE recent_searches:{userId} 0 9
Redis-->>StarQueryService: List of recent search keywords
StarQueryService-->>StarV2Controller: List<String>
StarV2Controller-->>User: ApiResponse<List<String>>
User->>StarV2Controller: GET /searchStars (with userId, query)
StarV2Controller->>StarCommandService: saveRecentSearches(userId, query)
StarCommandService->>Redis: LREM, LPUSH, LTRIM, EXPIRE recent_searches:{userId}
StarV2Controller->>...: (proceeds with search logic)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~15–20 minutes Suggested reviewers
Poem
Note 🔌 MCP (Model Context Protocol) integration is now available in Early Access!Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context. ✨ 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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (6)
src/main/java/com/team_nebula/nebula/domain/star/search/service/ElasticsearchService.java (1)
180-180: Prefer JavaDoc over inline comment; clarify scope criteria.Replace the inline comment with JavaDoc describing how this search differs from
searchStars(e.g., fields searched, fuzziness, sort), so it stays accurate over time.src/main/java/com/team_nebula/nebula/domain/star/service/StarQueryService.java (1)
41-41: Clarify API contract for recent searches.
- Consider renaming to
getRecentSearchKeywordsfor specificity.- Add JavaDoc to document ordering (most-recent-first), max count (10), normalization (e.g., lowercased?), and empty behavior.
src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandService.java (1)
28-31: Method naming and contract clarity.
saveRecentSearcheshandles a single keyword; consider renaming tosaveRecentSearchfor accuracy.- Document that
keywordmust be non-blank and whether normalization (trim/lowercase) will occur.- Consider returning
booleanorlong(count removed) fromdeleteRecentSearchto indicate outcome, if useful to callers.Example interface adjustment:
- public void saveRecentSearches(Long userId, String keyword); + public void saveRecentSearch(Long userId, String keyword); - public void deleteRecentSearch(Long userId, String keyword); + public boolean deleteRecentSearch(Long userId, String keyword);src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandServiceImpl.java (1)
68-70: Use StringRedisTemplate (or RedisTemplate<String, String>) and extract key prefix.Currently
RedisTemplate<String, Object>+ default serializers may store JDK-serialized strings unintentionally. Prefer a String-based template and a constant key prefix.
- Inject
StringRedisTemplate(orRedisTemplate<String, String>).- Add
private static final String RECENT_SEARCH_KEY_PREFIX = "recent_search:";- Build keys via
RECENT_SEARCH_KEY_PREFIX + userId.This improves serialization correctness and maintainability.
src/main/java/com/team_nebula/nebula/domain/star/service/StarQueryServiceImpl.java (2)
12-12: Prefer StringRedisTemplate and return typed values directlyThe list stores strings; using
RedisTemplate<String, Object>forces JDK serialization andtoString()mapping.StringRedisTemplatesimplifies serialization, reduces risk of incompatible serializers, and lets you returnList<String>directly.- import org.springframework.data.redis.core.RedisTemplate; + import org.springframework.data.redis.core.StringRedisTemplate;- private final RedisTemplate<String, Object> redisTemplate; + private final StringRedisTemplate redisTemplate; private static final int MAX_RECENT_SEARCHES = 10;- List<Object> searches = redisTemplate.opsForList().range(key, 0, MAX_RECENT_SEARCHES - 1); - return searches.stream() - .map(Object::toString) - .collect(Collectors.toList()); + List<String> searches = redisTemplate.opsForList().range(key, 0, MAX_RECENT_SEARCHES - 1); + return searches == null ? Collections.emptyList() : searches;Also applies to: 36-37, 193-197
193-197: Minor: be defensive and simplify collection conversionIf you keep
RedisTemplate<String, Object>, guard against null fromrange, and usetoList()for consistency with the rest of this class.- List<Object> searches = redisTemplate.opsForList().range(key, 0, MAX_RECENT_SEARCHES - 1); - return searches.stream() - .map(Object::toString) - .collect(Collectors.toList()); + List<Object> searches = redisTemplate.opsForList().range(key, 0, MAX_RECENT_SEARCHES - 1); + if (searches == null || searches.isEmpty()) { + return Collections.emptyList(); + } + return searches.stream() + .map(Object::toString) + .toList();
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/main/java/com/team_nebula/nebula/domain/star/api/StarV2Controller.java(2 hunks)src/main/java/com/team_nebula/nebula/domain/star/search/service/ElasticsearchService.java(1 hunks)src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandService.java(2 hunks)src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandServiceImpl.java(3 hunks)src/main/java/com/team_nebula/nebula/domain/star/service/StarQueryService.java(1 hunks)src/main/java/com/team_nebula/nebula/domain/star/service/StarQueryServiceImpl.java(4 hunks)
🔇 Additional comments (1)
src/main/java/com/team_nebula/nebula/domain/star/api/StarV2Controller.java (1)
98-103: LGTM: recent searches endpointEndpoint is clear and aligns with service contract (top 10). Ensure API docs/state that results may be normalized (lowercase) if applicable.
| starCommandService.saveRecentSearches(userId, query); | ||
| SearchResultResponseDTO result = starQueryService.searchStarsV2(query, userId, page, size); | ||
| return ApiResponse.onSuccess(result); |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Avoid saving blank queries; reconsider side-effect in GET.
- Guard against blank
qto prevent storing empty recent searches. - Saving state in a GET handler is debatable REST-wise. If possible, consider emitting an async domain event instead of directly updating state here.
Suggested guard:
- starCommandService.saveRecentSearches(userId, query);
+ if (query != null && !query.isBlank()) {
+ starCommandService.saveRecentSearches(userId, query);
+ }Confirm there are no other code paths persisting blank queries:
🏁 Script executed:
#!/bin/bash
rg -n --pretty -e 'saveRecentSearch(es)?\s*\(' -e '@GetMapping\(\"/search\"' -A3Length of output: 240
🏁 Script executed:
#!/bin/bash
set -e
# Find all calls to saveRecentSearches in the codebase
rg -n --pretty "saveRecentSearches"
echo "-----"
# Find all @GetMapping annotations that include "search" in the path, with context
rg -n --pretty '@GetMapping.*search' -A3Length of output: 1778
Guard blank search queries & reconsider side-effect in GET
We searched the entire codebase and found only one direct call to saveRecentSearches in a GET handler (StarV2Controller). To prevent empty keywords from being stored—and to keep this endpoint free of side-effects—please apply the following patch:
• File: src/main/java/com/team_nebula/nebula/domain/star/api/StarV2Controller.java
--- a/src/main/java/com/team_nebula/nebula/domain/star/api/StarV2Controller.java
+++ b/src/main/java/com/team_nebula/nebula/domain/star/api/StarV2Controller.java
@@ public ApiResponse<SearchResultResponseDTO> searchStars(
- starCommandService.saveRecentSearches(userId, query);
+ if (query != null && !query.isBlank()) {
+ starCommandService.saveRecentSearches(userId, query);
+ }Also consider emitting an asynchronous domain event for “recent search” recording instead of performing this side-effect directly within a GET handler.
📝 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.
| starCommandService.saveRecentSearches(userId, query); | |
| SearchResultResponseDTO result = starQueryService.searchStarsV2(query, userId, page, size); | |
| return ApiResponse.onSuccess(result); | |
| if (query != null && !query.isBlank()) { | |
| starCommandService.saveRecentSearches(userId, query); | |
| } | |
| SearchResultResponseDTO result = starQueryService.searchStarsV2(query, userId, page, size); | |
| return ApiResponse.onSuccess(result); |
🤖 Prompt for AI Agents
In src/main/java/com/team_nebula/nebula/domain/star/api/StarV2Controller.java
around lines 83 to 85, the method saveRecentSearches is called directly in a GET
handler without checking if the search query is blank, causing empty keywords to
be stored and introducing side-effects in a GET request. Fix this by adding a
guard clause to check if the query string is not blank before calling
saveRecentSearches. Additionally, consider refactoring this side-effect to emit
an asynchronous domain event for recording recent searches instead of performing
it synchronously within the GET handler.
| import static org.springframework.data.neo4j.core.ReactiveNeo4jClient.log; | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Incorrect logger import and usage; use a proper class logger.
import static ...ReactiveNeo4jClient.log; is not appropriate. Also, current log.error(e, "...") order won’t log stack traces.
- Add Lombok
@Slf4j(or define a Logger). - Remove the static import.
- Use
log.error("...", e).
Within this file’s changed lines, remove the static import:
-import static org.springframework.data.neo4j.core.ReactiveNeo4jClient.log;And update error calls in the methods (see diffs on those ranges).
📝 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.
| import static org.springframework.data.neo4j.core.ReactiveNeo4jClient.log; |
🤖 Prompt for AI Agents
In
src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandServiceImpl.java
around lines 48 to 49, remove the incorrect static import of
ReactiveNeo4jClient.log. Instead, add the Lombok @Slf4j annotation to the class
to define a proper logger. Then, update all log.error calls in the file to use
the correct parameter order: pass the log message first and the exception
second, like log.error("message", e), to ensure stack traces are logged
properly.
| @Override | ||
| @Async | ||
| public void saveRecentSearches(Long userId, String keyword){ | ||
| try { | ||
| String key = "recent_search:" + userId; | ||
| String normalizedKeyword = keyword.trim().toLowerCase(); | ||
|
|
||
| // 중복 제거 | ||
| redisTemplate.opsForList().remove(key, 0, normalizedKeyword); | ||
| redisTemplate.opsForList().leftPush(key, normalizedKeyword); | ||
| redisTemplate.opsForList().trim(key, 0, MAX_RECENT_SEARCHES - 1); | ||
| redisTemplate.expire(key, Duration.ofDays(30)); | ||
| } | ||
| catch (Exception e) { | ||
| log.error(e, "Failed to save recent search"); | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Harden save logic: null/blank guard, locale-safe lowercasing, correct logging, and avoid unnecessary DB transactions.
- Guard against null/blank to avoid storing empty entries.
- Use
Locale.ROOTfor consistent lowercasing. - Fix
log.errorargument order. - Since class-level
@Transactionalapplies, consider overriding with@Transactional(propagation = Propagation.NOT_SUPPORTED)here to avoid opening a JPA transaction for Redis I/O.
Proposed changes:
@Override
@Async
public void saveRecentSearches(Long userId, String keyword){
try {
- String key = "recent_search:" + userId;
- String normalizedKeyword = keyword.trim().toLowerCase();
+ if (keyword == null) return;
+ String trimmed = keyword.trim();
+ if (trimmed.isEmpty()) return;
+ String key = "recent_search:" + userId;
+ String normalizedKeyword = trimmed.toLowerCase(java.util.Locale.ROOT);
// 중복 제거
redisTemplate.opsForList().remove(key, 0, normalizedKeyword);
redisTemplate.opsForList().leftPush(key, normalizedKeyword);
redisTemplate.opsForList().trim(key, 0, MAX_RECENT_SEARCHES - 1);
redisTemplate.expire(key, java.time.Duration.ofDays(30));
}
catch (Exception e) {
- log.error(e, "Failed to save recent search");
+ log.error("Failed to save recent search", e);
}
}📝 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.
| @Override | |
| @Async | |
| public void saveRecentSearches(Long userId, String keyword){ | |
| try { | |
| String key = "recent_search:" + userId; | |
| String normalizedKeyword = keyword.trim().toLowerCase(); | |
| // 중복 제거 | |
| redisTemplate.opsForList().remove(key, 0, normalizedKeyword); | |
| redisTemplate.opsForList().leftPush(key, normalizedKeyword); | |
| redisTemplate.opsForList().trim(key, 0, MAX_RECENT_SEARCHES - 1); | |
| redisTemplate.expire(key, Duration.ofDays(30)); | |
| } | |
| catch (Exception e) { | |
| log.error(e, "Failed to save recent search"); | |
| } | |
| } | |
| @Override | |
| @Async | |
| public void saveRecentSearches(Long userId, String keyword){ | |
| try { | |
| if (keyword == null) return; | |
| String trimmed = keyword.trim(); | |
| if (trimmed.isEmpty()) return; | |
| String key = "recent_search:" + userId; | |
| String normalizedKeyword = trimmed.toLowerCase(java.util.Locale.ROOT); | |
| // 중복 제거 | |
| redisTemplate.opsForList().remove(key, 0, normalizedKeyword); | |
| redisTemplate.opsForList().leftPush(key, normalizedKeyword); | |
| redisTemplate.opsForList().trim(key, 0, MAX_RECENT_SEARCHES - 1); | |
| redisTemplate.expire(key, java.time.Duration.ofDays(30)); | |
| } | |
| catch (Exception e) { | |
| log.error("Failed to save recent search", e); | |
| } | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandServiceImpl.java
around lines 326 to 342, enhance saveRecentSearches by adding a null or blank
check for keyword to prevent storing empty entries, use
keyword.toLowerCase(Locale.ROOT) for locale-safe lowercasing, correct the
log.error call to have the message first and exception second, and add
@Transactional(propagation = Propagation.NOT_SUPPORTED) to avoid opening
unnecessary JPA transactions during Redis operations.
| @Override | ||
| @Async | ||
| public void saveRecentSearches(Long userId, String keyword){ | ||
| try { | ||
| String key = "recent_search:" + userId; | ||
| String normalizedKeyword = keyword.trim().toLowerCase(); | ||
|
|
||
| // 중복 제거 | ||
| redisTemplate.opsForList().remove(key, 0, normalizedKeyword); | ||
| redisTemplate.opsForList().leftPush(key, normalizedKeyword); | ||
| redisTemplate.opsForList().trim(key, 0, MAX_RECENT_SEARCHES - 1); | ||
| redisTemplate.expire(key, Duration.ofDays(30)); | ||
| } | ||
| catch (Exception e) { | ||
| log.error(e, "Failed to save recent search"); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void deleteRecentSearch(Long userId, String keyword) { | ||
| try { | ||
| String key = "recent_search:" + userId; | ||
| redisTemplate.opsForList().remove(key, 0, keyword.trim().toLowerCase()); | ||
| } catch (Exception e) { | ||
| log.error(e, "Failed to delete recent search"); | ||
| } | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Optional: prevent JPA transaction for Redis-only methods and add @slf4j.
Outside these changed lines, consider:
- Annotate class with
@Slf4j. - Annotate methods with
@Transactional(propagation = Propagation.NOT_SUPPORTED)to avoid opening a JPA transaction for Redis ops.
Example (outside selected lines):
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Propagation;
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class StarCommandServiceImpl implements StarCommandService {
@Override
@Async
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void saveRecentSearches(Long userId, String keyword) { ... }
@Override
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void deleteRecentSearch(Long userId, String keyword) { ... }
}Check that async execution is enabled and key usage is consistent:
🏁 Script executed:
#!/bin/bash
echo "Search for @EnableAsync (required for @Async to work via proxies):"
rg -n '@EnableAsync'
echo ""
echo "Check consistent Redis key usage for recent searches:"
rg -n --pretty 'recent_search:'Length of output: 604
Add @slf4j and mark Redis‐only methods as non‐transactional
To avoid opening unnecessary JPA transactions for Redis operations and enable logging in this class:
• Annotate StarCommandServiceImpl with @Slf4j (import lombok.extern.slf4j.Slf4j).
• Add @Transactional(propagation = Propagation.NOT_SUPPORTED) (import org.springframework.transaction.annotation.*) to both Redis‐only methods.
Suggested diff in src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandServiceImpl.java:
@@
-import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class StarCommandServiceImpl implements StarCommandService {
@Override
@Async
- public void saveRecentSearches(Long userId, String keyword){
+ @Transactional(propagation = Propagation.NOT_SUPPORTED)
+ public void saveRecentSearches(Long userId, String keyword) {
try {
String key = "recent_search:" + userId;
String normalizedKeyword = keyword.trim().toLowerCase();
@@
@Override
- public void deleteRecentSearch(Long userId, String keyword) {
+ @Transactional(propagation = Propagation.NOT_SUPPORTED)
+ public void deleteRecentSearch(Long userId, String keyword) {
try {
String key = "recent_search:" + userId;
redisTemplate.opsForList().remove(key, 0, keyword.trim().toLowerCase());📝 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.
| @Override | |
| @Async | |
| public void saveRecentSearches(Long userId, String keyword){ | |
| try { | |
| String key = "recent_search:" + userId; | |
| String normalizedKeyword = keyword.trim().toLowerCase(); | |
| // 중복 제거 | |
| redisTemplate.opsForList().remove(key, 0, normalizedKeyword); | |
| redisTemplate.opsForList().leftPush(key, normalizedKeyword); | |
| redisTemplate.opsForList().trim(key, 0, MAX_RECENT_SEARCHES - 1); | |
| redisTemplate.expire(key, Duration.ofDays(30)); | |
| } | |
| catch (Exception e) { | |
| log.error(e, "Failed to save recent search"); | |
| } | |
| } | |
| @Override | |
| public void deleteRecentSearch(Long userId, String keyword) { | |
| try { | |
| String key = "recent_search:" + userId; | |
| redisTemplate.opsForList().remove(key, 0, keyword.trim().toLowerCase()); | |
| } catch (Exception e) { | |
| log.error(e, "Failed to delete recent search"); | |
| } | |
| } | |
| import org.springframework.transaction.annotation.Propagation; | |
| import org.springframework.transaction.annotation.Transactional; | |
| import lombok.extern.slf4j.Slf4j; | |
| @Slf4j | |
| @Service | |
| @RequiredArgsConstructor | |
| @Transactional | |
| public class StarCommandServiceImpl implements StarCommandService { | |
| @Override | |
| @Async | |
| @Transactional(propagation = Propagation.NOT_SUPPORTED) | |
| public void saveRecentSearches(Long userId, String keyword) { | |
| try { | |
| String key = "recent_search:" + userId; | |
| String normalizedKeyword = keyword.trim().toLowerCase(); | |
| // 중복 제거 | |
| redisTemplate.opsForList().remove(key, 0, normalizedKeyword); | |
| redisTemplate.opsForList().leftPush(key, normalizedKeyword); | |
| redisTemplate.opsForList().trim(key, 0, MAX_RECENT_SEARCHES - 1); | |
| redisTemplate.expire(key, Duration.ofDays(30)); | |
| } catch (Exception e) { | |
| log.error(e, "Failed to save recent search"); | |
| } | |
| } | |
| @Override | |
| @Transactional(propagation = Propagation.NOT_SUPPORTED) | |
| public void deleteRecentSearch(Long userId, String keyword) { | |
| try { | |
| String key = "recent_search:" + userId; | |
| redisTemplate.opsForList().remove(key, 0, keyword.trim().toLowerCase()); | |
| } catch (Exception e) { | |
| log.error(e, "Failed to delete recent search"); | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandServiceImpl.java
around lines 326 to 352, add the @Slf4j annotation to the class to enable
logging and import it from lombok.extern.slf4j.Slf4j. Also, add
@Transactional(propagation = Propagation.NOT_SUPPORTED) annotation to the
saveRecentSearches and deleteRecentSearch methods to mark them as
non-transactional for Redis operations, importing the necessary classes from
org.springframework.transaction.annotation. This prevents unnecessary JPA
transactions during Redis calls and enables proper logging.
| @Override | ||
| public void deleteRecentSearch(Long userId, String keyword) { | ||
| try { | ||
| String key = "recent_search:" + userId; | ||
| redisTemplate.opsForList().remove(key, 0, keyword.trim().toLowerCase()); | ||
| } catch (Exception e) { | ||
| log.error(e, "Failed to delete recent search"); | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Normalize with Locale.ROOT and fix logging; mirror save behavior.
Ensure delete uses the same normalization and correct logging:
@Override
public void deleteRecentSearch(Long userId, String keyword) {
try {
String key = "recent_search:" + userId;
- redisTemplate.opsForList().remove(key, 0, keyword.trim().toLowerCase());
+ if (keyword == null) return;
+ String trimmed = keyword.trim();
+ if (trimmed.isEmpty()) return;
+ redisTemplate.opsForList().remove(key, 0, trimmed.toLowerCase(java.util.Locale.ROOT));
} catch (Exception e) {
- log.error(e, "Failed to delete recent search");
+ log.error("Failed to delete recent search", e);
}
}📝 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.
| @Override | |
| public void deleteRecentSearch(Long userId, String keyword) { | |
| try { | |
| String key = "recent_search:" + userId; | |
| redisTemplate.opsForList().remove(key, 0, keyword.trim().toLowerCase()); | |
| } catch (Exception e) { | |
| log.error(e, "Failed to delete recent search"); | |
| } | |
| } | |
| @Override | |
| public void deleteRecentSearch(Long userId, String keyword) { | |
| try { | |
| String key = "recent_search:" + userId; | |
| if (keyword == null) return; | |
| String trimmed = keyword.trim(); | |
| if (trimmed.isEmpty()) return; | |
| redisTemplate.opsForList().remove(key, 0, trimmed.toLowerCase(java.util.Locale.ROOT)); | |
| } catch (Exception e) { | |
| log.error("Failed to delete recent search", e); | |
| } | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/com/team_nebula/nebula/domain/star/service/StarCommandServiceImpl.java
around lines 344 to 352, the deleteRecentSearch method normalizes the keyword
using toLowerCase() without specifying Locale.ROOT, unlike the save method.
Update the normalization to use keyword.trim().toLowerCase(Locale.ROOT) to
ensure consistent behavior. Also, fix the logging statement to correctly log the
exception by swapping the parameters so the exception is logged properly with
the message.
|
|
||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| import static org.springframework.data.neo4j.core.ReactiveNeo4jClient.log; |
There was a problem hiding this comment.
Fix logger: invalid static import and wrong error(...) signature
- Static-importing
ReactiveNeo4jClient.logis incorrect here and likely won’t compile. - The call
log.error(e, "Failed to get recent searches");has reversed parameters.
Apply this diff and add Lombok logger:
- import static org.springframework.data.neo4j.core.ReactiveNeo4jClient.log;
+ import lombok.extern.slf4j.Slf4j;- log.error(e, "Failed to get recent searches");
+ log.error("Failed to get recent searches for userId={}", userId, e);Outside this hunk, annotate the class:
// add above class declaration
@Slf4jAlso applies to: 199-199
🤖 Prompt for AI Agents
In
src/main/java/com/team_nebula/nebula/domain/star/service/StarQueryServiceImpl.java
at line 24 and line 199, remove the incorrect static import of
ReactiveNeo4jClient.log and instead add the Lombok @Slf4j annotation above the
class declaration to enable logging. Then, fix the log.error call by swapping
the parameters to have the message first and the exception second, i.e.,
log.error("Failed to get recent searches", e).
| } | ||
|
|
||
| @Override | ||
| @Cacheable(value = "recent_searches", key = "#userId") |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Avoid stale results: @Cacheable without eviction on writes
@Cacheable("recent_searches") will cache results, but unless corresponding save/delete methods evict/refresh this cache, users can see stale lists.
- Ensure write paths (save/delete recent search) use
@CacheEvict(value = "recent_searches", key = "#userId")or@CachePutaccordingly. - Confirm cache TTL aligns with the stated 30-day retention or choose a shorter TTL to reduce staleness risk.
Run this to check for cache eviction on write paths and cache TTL config:
🏁 Script executed:
#!/bin/bash
# Find recent searches write operations and verify cache eviction
rg -n --hidden --glob '!**/node_modules/**' '@CacheEvict\([^)]*value\s*=\s*"recent_searches"' -A 2
rg -n --hidden --glob '!**/node_modules/**' 'saveRecentSearch|saveRecentSearches|deleteRecentSearch' -A 3
# Look for CacheManager/RedisCacheManager TTL config
rg -n --hidden --glob '!**/node_modules/**' 'RedisCacheManager|CacheManager|cacheNames|ttl|timeToLive' -A 3
# Check for any other usage of "recent_searches" to avoid key/cachename collisions
rg -n --hidden --glob '!**/node_modules/**' '"recent_searches"'Length of output: 6160
Ensure cache invalidation and correct TTL for recent_searches
- No
@CacheEvictfound on the write methods inStarCommandServiceImpl—addto@CacheEvict(value = "recent_searches", key = "#userId")
•saveRecentSearches(Long userId, String keyword)(StarCommandServiceImpl.java:328)
•deleteRecentSearch(Long userId, String keyword)(StarCommandServiceImpl.java:345) - Global cache TTL is set to 5 minutes in
CacheConfig.java:27–28. If you intend to retain “recent_searches” for 30 days, add an initial cache configuration for that cache, e.g.:cacheConfigurations.put( "recent_searches", RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofDays(30)) … );
- The Redis list key
"recent_search:<userId>"used byRedisTemplatehas no expiration. If you need automatic data cleanup, apply an expire on that key when you write to it.
🤖 Prompt for AI Agents
In
src/main/java/com/team_nebula/nebula/domain/star/service/StarQueryServiceImpl.java
at line 189, the @Cacheable annotation for "recent_searches" lacks corresponding
cache eviction on write operations. To fix this, add @CacheEvict(value =
"recent_searches", key = "#userId") annotations to the methods
saveRecentSearches(Long userId, String keyword) and deleteRecentSearch(Long
userId, String keyword) in StarCommandServiceImpl.java at lines 328 and 345
respectively. Additionally, update CacheConfig.java around lines 27-28 to
configure a specific TTL of 30 days for the "recent_searches" cache by adding a
RedisCacheConfiguration with entryTtl(Duration.ofDays(30)). Finally, ensure that
the Redis list key "recent_search:<userId>" used by RedisTemplate has an
expiration set when writing to it to enable automatic cleanup.
| public List<String> getRecentSearches(Long userId) { | ||
| try { | ||
| String key = "recent_searches"+userId.toString(); | ||
| List<Object> searches = redisTemplate.opsForList().range(key, 0, MAX_RECENT_SEARCHES - 1); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Validate userId and improve Redis key format
- Add a null-guard consistent with other methods.
- Use a namespaced key with a delimiter to avoid ambiguous keys and to align with Redis key best practices.
public List<String> getRecentSearches(Long userId) {
- try {
- String key = "recent_searches"+userId.toString();
+ if (userId == null) {
+ throw new GeneralException(ErrorStatus._USER_NOT_FOUND);
+ }
+ try {
+ final String key = String.format("star:recent-searches:%d", userId);📝 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.
| public List<String> getRecentSearches(Long userId) { | |
| try { | |
| String key = "recent_searches"+userId.toString(); | |
| List<Object> searches = redisTemplate.opsForList().range(key, 0, MAX_RECENT_SEARCHES - 1); | |
| public List<String> getRecentSearches(Long userId) { | |
| if (userId == null) { | |
| throw new GeneralException(ErrorStatus._USER_NOT_FOUND); | |
| } | |
| try { | |
| final String key = String.format("star:recent-searches:%d", userId); | |
| List<Object> searches = redisTemplate.opsForList().range(key, 0, MAX_RECENT_SEARCHES - 1); |
🤖 Prompt for AI Agents
In
src/main/java/com/team_nebula/nebula/domain/star/service/StarQueryServiceImpl.java
around lines 190 to 193, add a null check for userId at the start of the
getRecentSearches method to prevent null pointer exceptions, consistent with
other methods. Also, update the Redis key format to include a clear namespace
and delimiter, such as "recent_searches:userId", to avoid ambiguous keys and
follow Redis key naming best practices.
💡 개요
최근 검색어 조회 및 검색 시 검색어 캐시 저장 기능 구현
🔨작업 사항
Summary by CodeRabbit
New Features
Bug Fixes