From df1c58481ee9f167d41be97ab13963c2d44097a8 Mon Sep 17 00:00:00 2001 From: ivanmilevtues Date: Thu, 27 Aug 2026 02:35:06 +0200 Subject: [PATCH 1/3] feat: keep owner directory cache coherent --- .../petclinic/owner/OwnerController.java | 7 ++- .../owner/OwnerDirectorySynchronizer.java | 37 ++++++++++++ .../petclinic/owner/OwnerRepository.java | 3 + .../petclinic/owner/PetController.java | 8 ++- .../petclinic/owner/VisitController.java | 6 +- .../petclinic/system/CacheConfiguration.java | 5 +- .../system/CacheDiagnosticsController.java | 49 +++++++++++++++ .../petclinic/system/CacheOperations.java | 59 ++++++++++++++++++ .../petclinic/owner/OwnerControllerTests.java | 3 + .../OwnerDirectorySynchronizerTests.java | 36 +++++++++++ .../petclinic/owner/PetControllerTests.java | 3 + .../petclinic/owner/VisitControllerTests.java | 3 + .../CacheDiagnosticsControllerTests.java | 60 +++++++++++++++++++ .../system/CacheOperationsTests.java | 51 ++++++++++++++++ 14 files changed, 326 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/springframework/samples/petclinic/owner/OwnerDirectorySynchronizer.java create mode 100644 src/main/java/org/springframework/samples/petclinic/system/CacheDiagnosticsController.java create mode 100644 src/main/java/org/springframework/samples/petclinic/system/CacheOperations.java create mode 100644 src/test/java/org/springframework/samples/petclinic/owner/OwnerDirectorySynchronizerTests.java create mode 100644 src/test/java/org/springframework/samples/petclinic/system/CacheDiagnosticsControllerTests.java create mode 100644 src/test/java/org/springframework/samples/petclinic/system/CacheOperationsTests.java diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java index b4b614559e5..644a20fc5ca 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java @@ -52,8 +52,11 @@ class OwnerController { private final OwnerRepository owners; - public OwnerController(OwnerRepository owners) { + private final OwnerDirectorySynchronizer ownerDirectorySynchronizer; + + public OwnerController(OwnerRepository owners, OwnerDirectorySynchronizer ownerDirectorySynchronizer) { this.owners = owners; + this.ownerDirectorySynchronizer = ownerDirectorySynchronizer; } @InitBinder @@ -82,6 +85,7 @@ public String processCreationForm(@Valid Owner owner, BindingResult result, Redi } this.owners.save(owner); + this.ownerDirectorySynchronizer.ownerAggregateChanged(); redirectAttributes.addFlashAttribute("message", "New Owner Created"); return "redirect:/owners/" + owner.getId(); } @@ -157,6 +161,7 @@ public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result, @ owner.setId(ownerId); this.owners.save(owner); + this.ownerDirectorySynchronizer.ownerAggregateChanged(); redirectAttributes.addFlashAttribute("message", "Owner Values Updated"); return "redirect:/owners/{ownerId}"; } diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerDirectorySynchronizer.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerDirectorySynchronizer.java new file mode 100644 index 00000000000..4760f4dd86e --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerDirectorySynchronizer.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.owner; + +import org.springframework.samples.petclinic.system.CacheOperations; +import org.springframework.stereotype.Service; + +/** + * Keeps front-desk owner-directory projections synchronized after aggregate changes. + */ +@Service +public class OwnerDirectorySynchronizer { + + private final CacheOperations cacheOperations; + + public OwnerDirectorySynchronizer(CacheOperations cacheOperations) { + this.cacheOperations = cacheOperations; + } + + public void ownerAggregateChanged() { + this.cacheOperations.evictOwnerSearch(); + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java index d2b3dde40f8..9097eb44dfa 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java @@ -20,6 +20,8 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.samples.petclinic.system.CacheOperations; /** * Repository class for Owner domain objects. All method names are compliant @@ -42,6 +44,7 @@ public interface OwnerRepository extends JpaRepository { * @return a Collection of matching {@link Owner}s (or an empty Collection if none * found) */ + @Cacheable(CacheOperations.OWNER_SEARCH_CACHE) Page findByLastNameStartingWith(String lastName, Pageable pageable); /** diff --git a/src/main/java/org/springframework/samples/petclinic/owner/PetController.java b/src/main/java/org/springframework/samples/petclinic/owner/PetController.java index 845c116633b..b10e156a602 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/PetController.java @@ -54,9 +54,13 @@ class PetController { private final PetTypeRepository types; - public PetController(OwnerRepository owners, PetTypeRepository types) { + private final OwnerDirectorySynchronizer ownerDirectorySynchronizer; + + public PetController(OwnerRepository owners, PetTypeRepository types, + OwnerDirectorySynchronizer ownerDirectorySynchronizer) { this.owners = owners; this.types = types; + this.ownerDirectorySynchronizer = ownerDirectorySynchronizer; } @ModelAttribute("types") @@ -132,6 +136,7 @@ public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult res result.rejectValue("name", "duplicate", "already exists"); return VIEWS_PETS_CREATE_OR_UPDATE_FORM; } + this.ownerDirectorySynchronizer.ownerAggregateChanged(); redirectAttributes.addFlashAttribute("message", "New Pet has been Added"); return "redirect:/owners/{ownerId}"; } @@ -174,6 +179,7 @@ public String processUpdateForm(Owner owner, @Valid Pet pet, BindingResult resul result.rejectValue("name", "duplicate", "already exists"); return VIEWS_PETS_CREATE_OR_UPDATE_FORM; } + this.ownerDirectorySynchronizer.ownerAggregateChanged(); redirectAttributes.addFlashAttribute("message", "Pet details has been edited"); return "redirect:/owners/{ownerId}"; } diff --git a/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java b/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java index b8b270073e8..a39a21371ab 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java @@ -44,8 +44,11 @@ class VisitController { private final OwnerRepository owners; - public VisitController(OwnerRepository owners) { + private final OwnerDirectorySynchronizer ownerDirectorySynchronizer; + + public VisitController(OwnerRepository owners, OwnerDirectorySynchronizer ownerDirectorySynchronizer) { this.owners = owners; + this.ownerDirectorySynchronizer = ownerDirectorySynchronizer; } @InitBinder @@ -107,6 +110,7 @@ public String processNewVisitForm(@ModelAttribute Owner owner, @PathVariable int owner.addVisit(petId, visit); this.owners.save(owner); + this.ownerDirectorySynchronizer.ownerAggregateChanged(); redirectAttributes.addFlashAttribute("message", "Your visit has been booked"); return "redirect:/owners/{ownerId}"; } diff --git a/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java b/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java index 13cb743012d..d8ca882083d 100644 --- a/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java +++ b/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java @@ -34,7 +34,10 @@ class CacheConfiguration { @Bean public JCacheManagerCustomizer petclinicCacheConfigurationCustomizer() { - return cm -> cm.createCache("vets", cacheConfiguration()); + return cm -> { + cm.createCache("vets", cacheConfiguration()); + cm.createCache(CacheOperations.OWNER_SEARCH_CACHE, cacheConfiguration()); + }; } /** diff --git a/src/main/java/org/springframework/samples/petclinic/system/CacheDiagnosticsController.java b/src/main/java/org/springframework/samples/petclinic/system/CacheDiagnosticsController.java new file mode 100644 index 00000000000..df71acf3734 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/system/CacheDiagnosticsController.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.system; + +import java.util.Map; + +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Lightweight operational endpoints for checking and refreshing owner-search data. + */ +@RestController +@RequestMapping("/manage/cache/owner-search") +class CacheDiagnosticsController { + + private final CacheOperations cacheOperations; + + CacheDiagnosticsController(CacheOperations cacheOperations) { + this.cacheOperations = cacheOperations; + } + + @GetMapping + CacheOperations.CacheStatus status() { + return this.cacheOperations.ownerSearchStatus(); + } + + @DeleteMapping + Map evict() { + boolean evicted = this.cacheOperations.evictOwnerSearch(); + return Map.of("cache", CacheOperations.OWNER_SEARCH_CACHE, "evicted", evicted); + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/system/CacheOperations.java b/src/main/java/org/springframework/samples/petclinic/system/CacheOperations.java new file mode 100644 index 00000000000..aaf4e66d519 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/system/CacheOperations.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.system; + +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Service; + +/** + * Operational boundary for inspecting and invalidating application caches. + */ +@Service +public class CacheOperations { + + public static final String OWNER_SEARCH_CACHE = "ownerSearch"; + + private final CacheManager cacheManager; + + public CacheOperations(CacheManager cacheManager) { + this.cacheManager = cacheManager; + } + + /** + * Invalidate all cached owner-directory searches. + * @return {@code true} when the configured cache was available + */ + public boolean evictOwnerSearch() { + Cache cache = this.cacheManager.getCache(OWNER_SEARCH_CACHE); + if (cache == null) { + return false; + } + cache.clear(); + return true; + } + + /** + * Report whether the owner-search cache is currently available. + */ + public CacheStatus ownerSearchStatus() { + return new CacheStatus(OWNER_SEARCH_CACHE, this.cacheManager.getCache(OWNER_SEARCH_CACHE) != null); + } + + public record CacheStatus(String name, boolean available) { + } + +} diff --git a/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java b/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java index dd379a5cbd3..355c2147481 100644 --- a/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java @@ -70,6 +70,9 @@ class OwnerControllerTests { @MockitoBean private OwnerRepository owners; + @MockitoBean + private OwnerDirectorySynchronizer ownerDirectorySynchronizer; + private Owner george() { Owner george = new Owner(); george.setId(TEST_OWNER_ID); diff --git a/src/test/java/org/springframework/samples/petclinic/owner/OwnerDirectorySynchronizerTests.java b/src/test/java/org/springframework/samples/petclinic/owner/OwnerDirectorySynchronizerTests.java new file mode 100644 index 00000000000..54332cb3db0 --- /dev/null +++ b/src/test/java/org/springframework/samples/petclinic/owner/OwnerDirectorySynchronizerTests.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.owner; + +import org.junit.jupiter.api.Test; +import org.springframework.samples.petclinic.system.CacheOperations; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class OwnerDirectorySynchronizerTests { + + @Test + void ownerAggregateChangesInvalidateCachedSearches() { + CacheOperations cacheOperations = mock(CacheOperations.class); + OwnerDirectorySynchronizer synchronizer = new OwnerDirectorySynchronizer(cacheOperations); + + synchronizer.ownerAggregateChanged(); + + verify(cacheOperations).evictOwnerSearch(); + } + +} diff --git a/src/test/java/org/springframework/samples/petclinic/owner/PetControllerTests.java b/src/test/java/org/springframework/samples/petclinic/owner/PetControllerTests.java index b45ffd48d1e..43ed6ec3116 100644 --- a/src/test/java/org/springframework/samples/petclinic/owner/PetControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/owner/PetControllerTests.java @@ -67,6 +67,9 @@ class PetControllerTests { @MockitoBean private PetTypeRepository types; + @MockitoBean + private OwnerDirectorySynchronizer ownerDirectorySynchronizer; + @BeforeEach void setup() { PetType cat = new PetType(); diff --git a/src/test/java/org/springframework/samples/petclinic/owner/VisitControllerTests.java b/src/test/java/org/springframework/samples/petclinic/owner/VisitControllerTests.java index b608caa6711..20557832823 100644 --- a/src/test/java/org/springframework/samples/petclinic/owner/VisitControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/owner/VisitControllerTests.java @@ -56,6 +56,9 @@ class VisitControllerTests { @MockitoBean private OwnerRepository owners; + @MockitoBean + private OwnerDirectorySynchronizer ownerDirectorySynchronizer; + @BeforeEach void init() { Owner owner = new Owner(); diff --git a/src/test/java/org/springframework/samples/petclinic/system/CacheDiagnosticsControllerTests.java b/src/test/java/org/springframework/samples/petclinic/system/CacheDiagnosticsControllerTests.java new file mode 100644 index 00000000000..fd5217cdc22 --- /dev/null +++ b/src/test/java/org/springframework/samples/petclinic/system/CacheDiagnosticsControllerTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.system; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(CacheDiagnosticsController.class) +class CacheDiagnosticsControllerTests { + + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private CacheOperations cacheOperations; + + @Test + void reportsOwnerSearchCacheStatus() throws Exception { + given(this.cacheOperations.ownerSearchStatus()) + .willReturn(new CacheOperations.CacheStatus(CacheOperations.OWNER_SEARCH_CACHE, true)); + + this.mockMvc.perform(get("/manage/cache/owner-search")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value(CacheOperations.OWNER_SEARCH_CACHE)) + .andExpect(jsonPath("$.available").value(true)); + } + + @Test + void evictsOwnerSearchCache() throws Exception { + given(this.cacheOperations.evictOwnerSearch()).willReturn(true); + + this.mockMvc.perform(delete("/manage/cache/owner-search")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.cache").value(CacheOperations.OWNER_SEARCH_CACHE)) + .andExpect(jsonPath("$.evicted").value(true)); + } + +} diff --git a/src/test/java/org/springframework/samples/petclinic/system/CacheOperationsTests.java b/src/test/java/org/springframework/samples/petclinic/system/CacheOperationsTests.java new file mode 100644 index 00000000000..6a2ea66cf19 --- /dev/null +++ b/src/test/java/org/springframework/samples/petclinic/system/CacheOperationsTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.system; + +import org.junit.jupiter.api.Test; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class CacheOperationsTests { + + @Test + void evictsTheOwnerSearchCache() { + CacheManager cacheManager = mock(CacheManager.class); + Cache cache = mock(Cache.class); + given(cacheManager.getCache(CacheOperations.OWNER_SEARCH_CACHE)).willReturn(cache); + CacheOperations operations = new CacheOperations(cacheManager); + + assertThat(operations.evictOwnerSearch()).isTrue(); + + verify(cache).clear(); + } + + @Test + void reportsWhenOwnerSearchCacheIsUnavailable() { + CacheManager cacheManager = mock(CacheManager.class); + CacheOperations operations = new CacheOperations(cacheManager); + + assertThat(operations.evictOwnerSearch()).isFalse(); + assertThat(operations.ownerSearchStatus()) + .isEqualTo(new CacheOperations.CacheStatus(CacheOperations.OWNER_SEARCH_CACHE, false)); + } + +} From 82af9dd0545064bc842ac55494308dc430d2200d Mon Sep 17 00:00:00 2001 From: ivanmilevtues Date: Thu, 27 Aug 2026 02:40:23 +0200 Subject: [PATCH 2/3] refactor: keep cache operations cohesive --- .../samples/petclinic/system/CacheOperations.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/system/CacheOperations.java b/src/main/java/org/springframework/samples/petclinic/system/CacheOperations.java index aaf4e66d519..6f5ea01c8ab 100644 --- a/src/main/java/org/springframework/samples/petclinic/system/CacheOperations.java +++ b/src/main/java/org/springframework/samples/petclinic/system/CacheOperations.java @@ -38,7 +38,10 @@ public CacheOperations(CacheManager cacheManager) { * @return {@code true} when the configured cache was available */ public boolean evictOwnerSearch() { - Cache cache = this.cacheManager.getCache(OWNER_SEARCH_CACHE); + return evict(ownerSearchCache()); + } + + private boolean evict(Cache cache) { if (cache == null) { return false; } @@ -50,7 +53,11 @@ public boolean evictOwnerSearch() { * Report whether the owner-search cache is currently available. */ public CacheStatus ownerSearchStatus() { - return new CacheStatus(OWNER_SEARCH_CACHE, this.cacheManager.getCache(OWNER_SEARCH_CACHE) != null); + return new CacheStatus(OWNER_SEARCH_CACHE, ownerSearchCache() != null); + } + + private Cache ownerSearchCache() { + return this.cacheManager.getCache(OWNER_SEARCH_CACHE); } public record CacheStatus(String name, boolean available) { From c6f246547b20c675b841a6c61d6ca40924ff1a92 Mon Sep 17 00:00:00 2001 From: ivanmilevtues Date: Thu, 27 Aug 2026 02:45:25 +0200 Subject: [PATCH 3/3] refactor: expose explicit owner cache boundary --- .../petclinic/owner/OwnerDirectorySynchronizer.java | 2 +- .../samples/petclinic/system/CacheOperations.java | 12 ++++++++++-- .../owner/OwnerDirectorySynchronizerTests.java | 6 +++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerDirectorySynchronizer.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerDirectorySynchronizer.java index 4760f4dd86e..13793c82b2e 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerDirectorySynchronizer.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerDirectorySynchronizer.java @@ -31,7 +31,7 @@ public OwnerDirectorySynchronizer(CacheOperations cacheOperations) { } public void ownerAggregateChanged() { - this.cacheOperations.evictOwnerSearch(); + this.cacheOperations.evict(this.cacheOperations.ownerSearchCache()); } } diff --git a/src/main/java/org/springframework/samples/petclinic/system/CacheOperations.java b/src/main/java/org/springframework/samples/petclinic/system/CacheOperations.java index 6f5ea01c8ab..1868cfc4697 100644 --- a/src/main/java/org/springframework/samples/petclinic/system/CacheOperations.java +++ b/src/main/java/org/springframework/samples/petclinic/system/CacheOperations.java @@ -41,7 +41,12 @@ public boolean evictOwnerSearch() { return evict(ownerSearchCache()); } - private boolean evict(Cache cache) { + /** + * Evict every entry from the supplied cache. + * @param cache the cache selected by the caller + * @return {@code true} when a cache was supplied + */ + public boolean evict(Cache cache) { if (cache == null) { return false; } @@ -56,7 +61,10 @@ public CacheStatus ownerSearchStatus() { return new CacheStatus(OWNER_SEARCH_CACHE, ownerSearchCache() != null); } - private Cache ownerSearchCache() { + /** + * Resolve the cache used by the owner directory. + */ + public Cache ownerSearchCache() { return this.cacheManager.getCache(OWNER_SEARCH_CACHE); } diff --git a/src/test/java/org/springframework/samples/petclinic/owner/OwnerDirectorySynchronizerTests.java b/src/test/java/org/springframework/samples/petclinic/owner/OwnerDirectorySynchronizerTests.java index 54332cb3db0..b7e497ac85a 100644 --- a/src/test/java/org/springframework/samples/petclinic/owner/OwnerDirectorySynchronizerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/owner/OwnerDirectorySynchronizerTests.java @@ -16,8 +16,10 @@ package org.springframework.samples.petclinic.owner; import org.junit.jupiter.api.Test; +import org.springframework.cache.Cache; import org.springframework.samples.petclinic.system.CacheOperations; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -26,11 +28,13 @@ class OwnerDirectorySynchronizerTests { @Test void ownerAggregateChangesInvalidateCachedSearches() { CacheOperations cacheOperations = mock(CacheOperations.class); + Cache ownerSearchCache = mock(Cache.class); + given(cacheOperations.ownerSearchCache()).willReturn(ownerSearchCache); OwnerDirectorySynchronizer synchronizer = new OwnerDirectorySynchronizer(cacheOperations); synchronizer.ownerAggregateChanged(); - verify(cacheOperations).evictOwnerSearch(); + verify(cacheOperations).evict(ownerSearchCache); } }