Skip to content

refactor(service): replace manual caching with @Cacheable and JPQL with Criteria API#316

Merged
balazs-szucs merged 2 commits intogrimmory-tools:developfrom
balazs-szucs:refactor/spring-cacheable-and-criteria
Mar 31, 2026
Merged

refactor(service): replace manual caching with @Cacheable and JPQL with Criteria API#316
balazs-szucs merged 2 commits intogrimmory-tools:developfrom
balazs-szucs:refactor/spring-cacheable-and-criteria

Conversation

@balazs-szucs
Copy link
Copy Markdown
Member

@balazs-szucs balazs-szucs commented Mar 31, 2026

Description

Linked Issue: Fixes #

Changes

  • Replace AppSettingService double-checked locking (volatile + ReentrantLock) with Spring @Cacheable/@CacheEvict on appSettings cache
  • Add appSettings cache name to CacheConfig
  • Replace NotificationService dynamic JPQL string concatenation with type-safe JPA Criteria API (CriteriaBuilder predicates)
  • Fix CbxReaderService pre-existing compile error (transferEntryTo)

Summary by CodeRabbit

  • Refactor
    • Enhanced caching system to better manage application settings alongside public configurations
    • Optimized internal query mechanisms for user notification permissions
    • Migrated to unified Spring cache framework approach for improved consistency and maintainability

balazs-szucs and others added 2 commits April 1, 2026 00:05
…th Criteria API

- Replace AppSettingService double-checked locking (volatile + ReentrantLock)
  with Spring @Cacheable/@CacheEvict on appSettings cache
- Add appSettings cache name to CacheConfig
- Replace NotificationService dynamic JPQL string concatenation
  with type-safe JPA Criteria API (CriteriaBuilder predicates)
- Fix CbxReaderService pre-existing compile error (transferEntryTo)
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 2026

📝 Walkthrough

Walkthrough

This PR refactors the caching infrastructure by updating the Spring cache configuration to support both "publicSettings" and "appSettings" caches, modernizing query construction in NotificationService from JPQL strings to Criteria API, and removing manual in-memory caching with synchronization locks in favor of declarative Spring cache management.

Changes

Cohort / File(s) Summary
Cache Configuration Updates
booklore-api/src/main/java/org/booklore/config/CacheConfig.java, *.gradle
Updated CaffeineCacheManager to register both "publicSettings" and "appSettings" named caches with 24-hour expiration and size limit of 100. Gradle build file updated accordingly.
Cache Management Refactoring
booklore-api/src/main/java/org/booklore/service/appsettings/AppSettingService.java
Removed in-memory caching state (volatile AppSettings field) and ReentrantLock-based double-checked locking. Replaced manual cache refresh with @Cacheable("appSettings") annotation on getAppSettings(). Updated cache invalidation in write operations to use @Caching annotation for coordinated eviction of both "appSettings" and "publicSettings" caches.
Query Modernization
booklore-api/src/main/java/org/booklore/service/NotificationService.java
Migrated findUsernamesWithPermissions() from JPQL string construction to JPA Criteria API. Replaced string-based query with CriteriaBuilder/CriteriaQuery<String> pattern including entity root, permission join, and dynamic OR predicates. Removed unused Collectors import and added Criteria API type imports.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

backend, enhancement

Poem

🐰 Spring caches now dance in declared grace,
No locks to hold, no manual race,
Criteria joins where JPQL once stood,
The code flows cleaner—as it should!
✨🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title follows conventional commit format with type(scope): description, clearly summarizing the main refactoring changes.
Description check ✅ Passed The PR description includes the required template sections (Description and Changes) with detailed bullet points explaining all changes, though Linked Issue is left empty as none applies.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
booklore-api/src/main/java/org/booklore/service/appsettings/AppSettingService.java (1)

45-48: Spring proxy self-invocation bypasses the cache.

When validateOidcForceOnlyMode() (line 84) calls getAppSettings() internally, it invokes the method on this rather than through the Spring proxy, so @Cacheable won't apply. The cache is only effective for external callers.

For internal calls to also benefit from caching, consider injecting a self-reference or extracting the cached method to a separate bean.

♻️ Option: Inject self-reference for proxy-aware internal calls
 public class AppSettingService {
 
     private final AppProperties appProperties;
     private final SettingPersistenceHelper settingPersistenceHelper;
     private final AuthenticationService authenticationService;
     private final AuditService auditService;
+    private final AppSettingService self;
 
-    public AppSettingService(AppProperties appProperties, SettingPersistenceHelper settingPersistenceHelper, `@Lazy` AuthenticationService authenticationService, `@Lazy` AuditService auditService) {
+    public AppSettingService(AppProperties appProperties, SettingPersistenceHelper settingPersistenceHelper, `@Lazy` AuthenticationService authenticationService, `@Lazy` AuditService auditService, `@Lazy` AppSettingService self) {
         this.appProperties = appProperties;
         this.settingPersistenceHelper = settingPersistenceHelper;
         this.authenticationService = authenticationService;
         this.auditService = auditService;
+        this.self = self;
     }

Then in validateOidcForceOnlyMode():

-        AppSettings current = getAppSettings();
+        AppSettings current = self.getAppSettings();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@booklore-api/src/main/java/org/booklore/service/appsettings/AppSettingService.java`
around lines 45 - 48, The internal call to getAppSettings() from
validateOidcForceOnlyMode() bypasses Spring caching because it's a
self-invocation; to fix, obtain a proxy-aware reference and call through that or
move the cached method to another bean: either inject a proxy self-reference
(e.g., add a `@Lazy/`@Autowired AppSettingService selfProxy and replace calls to
this.getAppSettings() in validateOidcForceOnlyMode() with
selfProxy.getAppSettings()), or extract buildAppSettings()/getAppSettings() into
a separate `@Component` (e.g., AppSettingsProvider) annotated with `@Cacheable` and
call that provider from AppSettingService.validateOidcForceOnlyMode().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@booklore-api/src/main/java/org/booklore/service/appsettings/AppSettingService.java`:
- Around line 45-48: The internal call to getAppSettings() from
validateOidcForceOnlyMode() bypasses Spring caching because it's a
self-invocation; to fix, obtain a proxy-aware reference and call through that or
move the cached method to another bean: either inject a proxy self-reference
(e.g., add a `@Lazy/`@Autowired AppSettingService selfProxy and replace calls to
this.getAppSettings() in validateOidcForceOnlyMode() with
selfProxy.getAppSettings()), or extract buildAppSettings()/getAppSettings() into
a separate `@Component` (e.g., AppSettingsProvider) annotated with `@Cacheable` and
call that provider from AppSettingService.validateOidcForceOnlyMode().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 73e0330f-d554-4f31-b7d0-9e23cd854742

📥 Commits

Reviewing files that changed from the base of the PR and between 6908310 and a593d76.

📒 Files selected for processing (3)
  • booklore-api/src/main/java/org/booklore/config/CacheConfig.java
  • booklore-api/src/main/java/org/booklore/service/NotificationService.java
  • booklore-api/src/main/java/org/booklore/service/appsettings/AppSettingService.java
📜 Review details
⏰ 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: Packaging Smoke Test
🧰 Additional context used
📓 Path-based instructions (1)
booklore-api/src/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

booklore-api/src/**/*.java: Use 4-space indentation in Java files and match surrounding Java style
Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce @Autowired field injection
Use MapStruct for entity/DTO mapping

Files:

  • booklore-api/src/main/java/org/booklore/config/CacheConfig.java
  • booklore-api/src/main/java/org/booklore/service/NotificationService.java
  • booklore-api/src/main/java/org/booklore/service/appsettings/AppSettingService.java
🔇 Additional comments (4)
booklore-api/src/main/java/org/booklore/service/NotificationService.java (1)

71-78: No fix needed; permissions is a @OneToOne relationship.

The original concern assumes that joining permissions could produce duplicate rows per user. However, BookLoreUserEntity.permissions is annotated as @OneToOne, which guarantees exactly one related entity per user. The join will not fan out, so duplicate usernames cannot occur. The proposed distinct(true) is unnecessary.

			> Likely an incorrect or invalid review comment.
booklore-api/src/main/java/org/booklore/config/CacheConfig.java (1)

16-23: LGTM!

Clean addition of the appSettings cache. Both caches sharing the same configuration (24h TTL, max 100 entries) is appropriate for application settings data.

booklore-api/src/main/java/org/booklore/service/appsettings/AppSettingService.java (2)

50-78: LGTM!

The @Caching annotation correctly evicts both caches after successful updates. Using allEntries = true is appropriate here, and the default beforeInvocation = false ensures the cache is only evicted after successful method completion, preserving consistency with the @Transactional behavior.


201-214: LGTM!

Consistent cache eviction pattern matching updateSetting(). Both caches are properly invalidated when settings are persisted.

@balazs-szucs balazs-szucs merged commit c48298c into grimmory-tools:develop Mar 31, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant