Conversation
- Implemented caching in SQLManager using ConcurrentHashMap - Added MedalPlayerListener to clear cache on player quit - Refactored SQLManager for testability (dependency injection) - Added unit tests verifying cache hits, misses, and null handling - Updated pom.xml with JUnit 5 and Mockito dependencies - Updated PlaceholderAPI repository to helpch.at Co-authored-by: acsoto <59144459+acsoto@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
There was a problem hiding this comment.
Pull request overview
This PR implements a caching mechanism in the SQLManager to optimize the performance of PlaceholderAPI (PAPI) queries by eliminating redundant database calls. The getMainMedal method previously queried the database on every invocation; with this change, results are cached in memory and only queried once per player until they disconnect.
Changes:
- Added a
ConcurrentHashMap-based cache with anEMPTY_MEDALsentinel pattern to distinguish between "no result" and "not cached" - Implemented a
PlayerQuitEventlistener to clear cache entries when players disconnect - Added JUnit 5 and Mockito dependencies and created unit tests for the caching behavior
- Updated PlaceholderAPI version from 2.11.1 to 2.11.5 and added the helpch.at repository
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| modules/MedalCabinet/src/main/java/com/mcatk/medalcabinet/sql/SQLManager.java | Implements caching with ConcurrentHashMap, adds cache management methods, and updates setMainMedal to maintain cache consistency |
| modules/MedalCabinet/src/main/java/com/mcatk/medalcabinet/listener/MedalPlayerListener.java | New listener to clear player cache on disconnect |
| modules/MedalCabinet/src/main/java/com/mcatk/medalcabinet/MedalCabinet.java | Registers the new MedalPlayerListener |
| modules/MedalCabinet/src/test/java/com/mcatk/medalcabinet/sql/SQLManagerTest.java | Unit tests for cache hit/miss behavior and null value caching |
| modules/MedalCabinet/pom.xml | Adds test dependencies (JUnit 5, Mockito) and updates PlaceholderAPI version and repository |
Comments suppressed due to low confidence (1)
modules/MedalCabinet/src/main/java/com/mcatk/medalcabinet/sql/SQLManager.java:181
- When a SQLException occurs during the database update, the cache is not invalidated. This could lead to inconsistent state where the cache contains data that may not match the database. If the update partially succeeded or if there's uncertainty about the database state, the cache should be cleared to ensure data consistency on the next read.
} catch (SQLException e) {
e.printStackTrace();
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (newMain != null) { | ||
| mainMedalCache.put(playerID, newMain); | ||
| } | ||
|
|
There was a problem hiding this comment.
After successfully updating the main medal in the database, the cache should be cleared or updated regardless of whether getMedal returns null. Currently, if getMedal(medalID) returns null (e.g., if the medal was deleted from the medal table), the cache retains stale data. This causes getMainMedal to return outdated information until the player quits.
Consider either:
- Always clearing the cache after a successful database update: mainMedalCache.remove(playerID);
- Or updating the cache even when newMain is null: mainMedalCache.put(playerID, newMain != null ? newMain : EMPTY_MEDAL);
| if (newMain != null) { | |
| mainMedalCache.put(playerID, newMain); | |
| } | |
| mainMedalCache.put(playerID, newMain != null ? newMain : EMPTY_MEDAL); |
| @Test | ||
| void testGetMainMedalCached() throws SQLException { | ||
| // Setup mock to return a medal ID "medal1" for player "player1" | ||
|
|
||
| // Simulating: | ||
| // First query (get main medal ID) returns "medal1" | ||
| // Second query (get medal details) returns medal details | ||
|
|
||
| when(resultSet.next()).thenReturn(true, true); // Sufficient for 1 call (2 queries) | ||
| when(resultSet.getString("medal_id")).thenReturn("medal1"); | ||
| when(resultSet.getString("medal_name")).thenReturn("Medal One"); | ||
|
|
||
| // Call 1 - Should hit DB | ||
| Medal m1 = sqlManager.getMainMedal("player1"); | ||
| assertNotNull(m1); | ||
| assertEquals("Medal One", m1.getName()); | ||
|
|
||
| // Call 2 - Should NOT hit DB (cached) | ||
| Medal m2 = sqlManager.getMainMedal("player1"); | ||
| assertNotNull(m2); | ||
| assertEquals("Medal One", m2.getName()); | ||
| assertEquals(m1, m2); // Should be same object reference if cached | ||
|
|
||
| // Verify that prepareStatement was called exactly 2 times (for the first call only) | ||
| verify(connection, times(2)).prepareStatement(anyString()); | ||
|
|
||
| // Clear cache | ||
| sqlManager.clearCache("player1"); | ||
|
|
||
| // Setup mock for another call | ||
| when(resultSet.next()).thenReturn(true, true); | ||
| when(resultSet.getString("medal_id")).thenReturn("medal1"); | ||
| when(resultSet.getString("medal_name")).thenReturn("Medal One"); | ||
|
|
||
| // Call 3 - Should hit DB again | ||
| Medal m3 = sqlManager.getMainMedal("player1"); | ||
| assertNotNull(m3); | ||
|
|
||
| // Verify prepareStatement called 2 more times (total 4) | ||
| verify(connection, times(4)).prepareStatement(anyString()); | ||
| } | ||
|
|
||
| @Test | ||
| void testGetMainMedalNullCaching() throws SQLException { | ||
| // Setup mock to return no medal (false on first next()) | ||
| when(resultSet.next()).thenReturn(false); | ||
|
|
||
| // Call 1 - Should hit DB (1 query) | ||
| Medal m1 = sqlManager.getMainMedal("noplayer"); | ||
| assertNull(m1); | ||
|
|
||
| // Call 2 - Should NOT hit DB (cached empty) | ||
| Medal m2 = sqlManager.getMainMedal("noplayer"); | ||
| assertNull(m2); | ||
|
|
||
| // Verify query count. | ||
| // Logic: | ||
| // 1. SELECT medal_id ... returns empty (resultSet.next() -> false). | ||
| // 2. Returns null. | ||
| // 3. Cache put(noplayer, EMPTY_MEDAL). | ||
|
|
||
| // So only 1 query "SELECT medal_id..." executed. | ||
| verify(connection, times(1)).prepareStatement(anyString()); | ||
| } | ||
| } |
There was a problem hiding this comment.
The test suite doesn't cover the setMainMedal method's cache update behavior. Consider adding tests for:
- Verifying cache is updated when setMainMedal succeeds
- Verifying cache behavior when setMainMedal succeeds but getMedal returns null
- Verifying cache state when setMainMedal fails due to SQLException
These scenarios are critical for ensuring the caching implementation works correctly in all cases.
| sqlManager = new SQLManager(connection); | ||
| SQLManager.setInstance(sqlManager); | ||
|
|
||
| lenient().when(connection.prepareStatement(anyString())).thenReturn(preparedStatement); |
There was a problem hiding this comment.
This PreparedStatement is not always closed on method exit.
| SQLManager.setInstance(sqlManager); | ||
|
|
||
| lenient().when(connection.prepareStatement(anyString())).thenReturn(preparedStatement); | ||
| lenient().when(preparedStatement.executeQuery()).thenReturn(resultSet); |
There was a problem hiding this comment.
This ResultSet is not always closed on method exit.
SQLManagerusingConcurrentHashMapand added aPlayerQuitEventlistener to clear cache.MedalPapi.java, which are frequent and synchronous.getMainMedalquerying DB every time. Optimized tests confirmed DB query happens only once per player until cache clear, significantly reducing DB load for PAPI requests.helpch.at.PR created automatically by Jules for task 10773958192654320631 started by @acsoto