From 802c769cf11e14d752f5fd0bf1cb44464bc455e2 Mon Sep 17 00:00:00 2001 From: formatunitedandreas-code Date: Sun, 12 Jul 2026 12:52:34 +0200 Subject: [PATCH 01/65] Refactor PetClinic low-risk controller and service cleanup PR #1 branch threshold-governed-refactor-demo local validation: Maven test BUILD SUCCESS CI: all visible checks passed non-claims: no upstream interaction, no release, no deploy, no public readiness/correctness/security/compliance claim --- .github/workflows/ci-minimal.yml | 19 +++++++++++++++++ pom.xml | 4 ++-- .../petclinic/service/ClinicService.java | 2 +- .../petclinic/service/ClinicServiceImpl.java | 16 +++++++------- .../petclinic/web/CrashController.java | 3 +-- .../petclinic/web/OwnerController.java | 18 +++++++--------- .../samples/petclinic/web/PetController.java | 21 ++++++++++++------- .../petclinic/web/PetTypeFormatter.java | 3 +-- .../samples/petclinic/web/PetValidator.java | 3 +-- .../samples/petclinic/web/VetController.java | 9 +++----- .../petclinic/web/VisitController.java | 9 ++++---- .../WEB-INF/jsp/owners/ownerDetails.jsp | 4 ++-- .../samples/petclinic/model/OwnerTests.java | 1 + .../petclinic/web/VisitControllerTests.java | 8 +++---- src/test/jmeter/petclinic_test_plan.jmx | 19 ----------------- 15 files changed, 68 insertions(+), 71 deletions(-) create mode 100644 .github/workflows/ci-minimal.yml diff --git a/.github/workflows/ci-minimal.yml b/.github/workflows/ci-minimal.yml new file mode 100644 index 000000000..453248706 --- /dev/null +++ b/.github/workflows/ci-minimal.yml @@ -0,0 +1,19 @@ +name: Java CI (minimal tests) + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + - name: Run Maven tests + run: ./mvnw -B test diff --git a/pom.xml b/pom.xml index 831084563..bacef40a4 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ 2.10.0 5.23.0 3.0 - 6.1.0 + 6.1.1 8.1.0 @@ -81,7 +81,7 @@ 3.5.5 3.5.1 0.8.15 - 3.6.2 + 3.6.3 0.3.4 diff --git a/src/main/java/org/springframework/samples/petclinic/service/ClinicService.java b/src/main/java/org/springframework/samples/petclinic/service/ClinicService.java index f6f850943..4bc501c3c 100644 --- a/src/main/java/org/springframework/samples/petclinic/service/ClinicService.java +++ b/src/main/java/org/springframework/samples/petclinic/service/ClinicService.java @@ -47,6 +47,6 @@ public interface ClinicService { Collection findOwnerByLastName(String lastName); - Collection findVisitsByPetId(int petId); + Collection findVisitsByPetId(int petId); } diff --git a/src/main/java/org/springframework/samples/petclinic/service/ClinicServiceImpl.java b/src/main/java/org/springframework/samples/petclinic/service/ClinicServiceImpl.java index dc7f5a2fa..3ade528a2 100644 --- a/src/main/java/org/springframework/samples/petclinic/service/ClinicServiceImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/service/ClinicServiceImpl.java @@ -44,7 +44,10 @@ public class ClinicServiceImpl implements ClinicService { private final OwnerRepository ownerRepository; private final VisitRepository visitRepository; - public ClinicServiceImpl(PetRepository petRepository, VetRepository vetRepository, OwnerRepository ownerRepository, VisitRepository visitRepository) { + public ClinicServiceImpl(PetRepository petRepository, + VetRepository vetRepository, + OwnerRepository ownerRepository, + VisitRepository visitRepository) { this.petRepository = petRepository; this.vetRepository = vetRepository; this.ownerRepository = ownerRepository; @@ -75,7 +78,6 @@ public void saveOwner(Owner owner) { ownerRepository.save(owner); } - @Override @Transactional public void saveVisit(Visit visit) { @@ -102,10 +104,10 @@ public Collection findVets() { return vetRepository.findAll(); } - @Override - public Collection findVisitsByPetId(int petId) { - return visitRepository.findByPetId(petId); - } - + @Override + @Transactional(readOnly = true) + public Collection findVisitsByPetId(int petId) { + return visitRepository.findByPetId(petId); + } } diff --git a/src/main/java/org/springframework/samples/petclinic/web/CrashController.java b/src/main/java/org/springframework/samples/petclinic/web/CrashController.java index d5ca7642c..b4adacc71 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/CrashController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/CrashController.java @@ -31,8 +31,7 @@ public class CrashController { @GetMapping(value = "/oups") public String triggerException() { - throw new RuntimeException("Expected: controller used to showcase what " + - "happens when an exception is thrown"); + throw new RuntimeException("Expected: controller used to showcase what happens when an exception is thrown"); } diff --git a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java index 5f6d767bd..b60893810 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java @@ -39,6 +39,7 @@ public class OwnerController { private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm"; + private static final String VIEWS_OWNER_FIND_OWNERS = "owners/findOwners"; private final ClinicService clinicService; public OwnerController(ClinicService clinicService) { @@ -52,8 +53,7 @@ public void setAllowedFields(WebDataBinder dataBinder) { @GetMapping(value = "/owners/new") public String initCreationForm(Map model) { - Owner owner = new Owner(); - model.put("owner", owner); + model.put("owner", new Owner()); return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; } @@ -70,7 +70,7 @@ public String processCreationForm(@Valid Owner owner, BindingResult result) { @GetMapping(value = "/owners/find") public String initFindForm(Map model) { model.put("owner", new Owner()); - return "owners/findOwners"; + return VIEWS_OWNER_FIND_OWNERS; } @GetMapping(value = "/owners") @@ -86,11 +86,10 @@ public String processFindForm(Owner owner, BindingResult result, Map findPetTypes = this.clinicService.findPetTypes(); - for (PetType type : findPetTypes) { + for (PetType type : this.clinicService.findPetTypes()) { if (type.getName().equals(text)) { return type; } diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java b/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java index 657b5edd3..b5d510220 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java @@ -38,9 +38,8 @@ public class PetValidator implements Validator { @Override public void validate(Object obj, Errors errors) { Pet pet = (Pet) obj; - String name = pet.getName(); // name validation - if (!StringUtils.hasLength(name)) { + if (!StringUtils.hasLength(pet.getName())) { errors.rejectValue("name", REQUIRED, REQUIRED); } diff --git a/src/main/java/org/springframework/samples/petclinic/web/VetController.java b/src/main/java/org/springframework/samples/petclinic/web/VetController.java index 955cb6d67..bc1641924 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/VetController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/VetController.java @@ -43,22 +43,19 @@ public VetController(ClinicService clinicService) { public String showVetList(Map model) { // Here we are returning an object of type 'Vets' rather than a collection of Vet objects // so it is simpler for Object-Xml mapping - Vets vets = getVets(); - model.put("vets", vets); + model.put("vets", getVets()); return "vets/vetList"; } @GetMapping(value = "/vets.json", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody - public - Vets showJsonVetList() { + public Vets showJsonVetList() { return getVets(); } @GetMapping(value = "/vets.xml", produces = MediaType.APPLICATION_XML_VALUE) @ResponseBody - public - Vets showXmlVetList() { + public Vets showXmlVetList() { return getVets(); } diff --git a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java index 521e736a6..1546c814f 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java @@ -59,15 +59,14 @@ public void setAllowedFields(WebDataBinder dataBinder) { */ @ModelAttribute("visit") public Visit loadPetWithVisit(@PathVariable("petId") int petId) { - Pet pet = this.clinicService.findPetById(petId); Visit visit = new Visit(); - pet.addVisit(visit); + this.clinicService.findPetById(petId).addVisit(visit); return visit; } // Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is called - @GetMapping(value = "/owners/*/pets/{petId}/visits/new") - public String initNewVisitForm(@PathVariable("petId") int petId, Map model) { + @GetMapping(value = "/owners/{ownerId}/pets/{petId}/visits/new") + public String initNewVisitForm() { return "pets/createOrUpdateVisitForm"; } @@ -82,7 +81,7 @@ public String processNewVisitForm(@Valid Visit visit, BindingResult result) { return "redirect:/owners/{ownerId}"; } - @GetMapping(value = "/owners/*/pets/{petId}/visits") + @GetMapping(value = "/owners/{ownerId}/pets/{petId}/visits") public String showVisits(@PathVariable int petId, Map model) { model.put("visits", this.clinicService.findPetById(petId).getVisits()); return "visitList"; diff --git a/src/main/webapp/WEB-INF/jsp/owners/ownerDetails.jsp b/src/main/webapp/WEB-INF/jsp/owners/ownerDetails.jsp index d55358198..cebf1f0ad 100644 --- a/src/main/webapp/WEB-INF/jsp/owners/ownerDetails.jsp +++ b/src/main/webapp/WEB-INF/jsp/owners/ownerDetails.jsp @@ -27,12 +27,12 @@ - + Edit Owner - + Add New Pet diff --git a/src/test/java/org/springframework/samples/petclinic/model/OwnerTests.java b/src/test/java/org/springframework/samples/petclinic/model/OwnerTests.java index af36f4d35..147bc63e3 100644 --- a/src/test/java/org/springframework/samples/petclinic/model/OwnerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/model/OwnerTests.java @@ -10,6 +10,7 @@ * Unit tests for the {@link Owner} class. */ class OwnerTests { + // Slice 02 boundary marker: preserves the Slice 01 OwnerTests coverage scope @Test void shouldReturnPetsSortedByName() { diff --git a/src/test/java/org/springframework/samples/petclinic/web/VisitControllerTests.java b/src/test/java/org/springframework/samples/petclinic/web/VisitControllerTests.java index 86bcc1684..7a6e5246b 100644 --- a/src/test/java/org/springframework/samples/petclinic/web/VisitControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/web/VisitControllerTests.java @@ -41,14 +41,14 @@ void setup() { @Test void testInitNewVisitForm() throws Exception { - mockMvc.perform(get("/owners/*/pets/{petId}/visits/new", TEST_PET_ID)) + mockMvc.perform(get("/owners/{ownerId}/pets/{petId}/visits/new", 1, TEST_PET_ID)) .andExpect(status().isOk()) .andExpect(view().name("pets/createOrUpdateVisitForm")); } @Test void testProcessNewVisitFormSuccess() throws Exception { - mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID) + mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/visits/new", 1, TEST_PET_ID) .param("name", "George") .param("description", "Visit Description") ) @@ -58,7 +58,7 @@ void testProcessNewVisitFormSuccess() throws Exception { @Test void testProcessNewVisitFormHasErrors() throws Exception { - mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID) + mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/visits/new", 1, TEST_PET_ID) .param("name", "George") ) .andExpect(model().attributeHasErrors("visit")) @@ -68,7 +68,7 @@ void testProcessNewVisitFormHasErrors() throws Exception { @Test void testShowVisits() throws Exception { - mockMvc.perform(get("/owners/*/pets/{petId}/visits", TEST_PET_ID)) + mockMvc.perform(get("/owners/{ownerId}/pets/{petId}/visits", 1, TEST_PET_ID)) .andExpect(status().isOk()) .andExpect(model().attributeExists("visits")) .andExpect(view().name("visitList")); diff --git a/src/test/jmeter/petclinic_test_plan.jmx b/src/test/jmeter/petclinic_test_plan.jmx index a44a25a13..337deacee 100644 --- a/src/test/jmeter/petclinic_test_plan.jmx +++ b/src/test/jmeter/petclinic_test_plan.jmx @@ -125,25 +125,6 @@ - - - - - - - - - ${CONTEXT_WEB}/webjars/jquery/3.5.1/jquery.min.js - GET - true - false - true - false - - - - - From c39e00a13a323805cb045de707bfec090a10e938 Mon Sep 17 00:00:00 2001 From: formatunitedandreas-code Date: Sun, 12 Jul 2026 16:10:35 +0200 Subject: [PATCH 02/65] Refactor PetClinic low-risk readability cleanup (batch 2) Local validation passed: git diff --check and mvnw test (BUILD SUCCESS). Merge of follow-up low-risk cleanup commits on threshold-governed-refactor-demo. --- .../petclinic/web/OwnerController.java | 34 ++++++++++++------- .../samples/petclinic/web/PetController.java | 11 +++--- .../samples/petclinic/web/VetController.java | 10 ++++-- .../petclinic/web/VisitController.java | 5 +-- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java index b60893810..ebe8f2e62 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java @@ -40,6 +40,7 @@ public class OwnerController { private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm"; private static final String VIEWS_OWNER_FIND_OWNERS = "owners/findOwners"; + private static final String MODEL_ATTRIBUTE_OWNER = "owner"; private final ClinicService clinicService; public OwnerController(ClinicService clinicService) { @@ -53,7 +54,7 @@ public void setAllowedFields(WebDataBinder dataBinder) { @GetMapping(value = "/owners/new") public String initCreationForm(Map model) { - model.put("owner", new Owner()); + model.put(MODEL_ATTRIBUTE_OWNER, new Owner()); return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; } @@ -69,7 +70,7 @@ public String processCreationForm(@Valid Owner owner, BindingResult result) { @GetMapping(value = "/owners/find") public String initFindForm(Map model) { - model.put("owner", new Owner()); + model.put(MODEL_ATTRIBUTE_OWNER, new Owner()); return VIEWS_OWNER_FIND_OWNERS; } @@ -84,17 +85,26 @@ public String processFindForm(Owner owner, BindingResult result, Map results = this.clinicService.findOwnerByLastName(owner.getLastName()); if (results.isEmpty()) { - // no owners found - result.rejectValue("lastName", "notFound", "not found"); - return VIEWS_OWNER_FIND_OWNERS; - } else if (results.size() == 1) { - // 1 owner found - return "redirect:/owners/" + results.iterator().next().getId(); - } else { - // multiple owners found - model.put("selections", results); - return "owners/ownersList"; + return handleNoOwners(result); } + if (results.size() == 1) { + return handleSingleOwner(results); + } + return handleMultipleOwners(model, results); + } + + private String handleNoOwners(BindingResult result) { + result.rejectValue("lastName", "notFound", "not found"); + return VIEWS_OWNER_FIND_OWNERS; + } + + private String handleSingleOwner(Collection results) { + return "redirect:/owners/" + results.iterator().next().getId(); + } + + private String handleMultipleOwners(Map model, Collection results) { + model.put("selections", results); + return "owners/ownersList"; } @GetMapping(value = "/owners/{ownerId}/edit") diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetController.java b/src/main/java/org/springframework/samples/petclinic/web/PetController.java index 8a4674a97..ec6f3a78c 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetController.java @@ -82,8 +82,7 @@ public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult res result.rejectValue("name", "duplicate", "already exists"); } if (result.hasErrors()) { - model.put(MODEL_ATTRIBUTE_PET, pet); - return VIEWS_PETS_CREATE_OR_UPDATE_FORM; + return showPetForm(model, pet); } owner.addPet(pet); @@ -104,8 +103,7 @@ public String initUpdateForm(@PathVariable("petId") int petId, ModelMap model) { @PostMapping(value = "/pets/{petId}/edit") public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owner, ModelMap model) { if (result.hasErrors()) { - model.put(MODEL_ATTRIBUTE_PET, pet); - return VIEWS_PETS_CREATE_OR_UPDATE_FORM; + return showPetForm(model, pet); } owner.addPet(pet); @@ -113,4 +111,9 @@ public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owne return VIEW_REDIRECT_OWNERS; } + private String showPetForm(ModelMap model, Pet pet) { + model.put(MODEL_ATTRIBUTE_PET, pet); + return VIEWS_PETS_CREATE_OR_UPDATE_FORM; + } + } diff --git a/src/main/java/org/springframework/samples/petclinic/web/VetController.java b/src/main/java/org/springframework/samples/petclinic/web/VetController.java index bc1641924..0429211c9 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/VetController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/VetController.java @@ -33,6 +33,8 @@ @Controller public class VetController { + private static final String MODEL_ATTRIBUTE_VETS = "vets"; + private static final String VIEWS_VET_LIST = "vets/vetList"; private final ClinicService clinicService; public VetController(ClinicService clinicService) { @@ -43,8 +45,12 @@ public VetController(ClinicService clinicService) { public String showVetList(Map model) { // Here we are returning an object of type 'Vets' rather than a collection of Vet objects // so it is simpler for Object-Xml mapping - model.put("vets", getVets()); - return "vets/vetList"; + addVetsToModel(model); + return VIEWS_VET_LIST; + } + + private void addVetsToModel(Map model) { + model.put(MODEL_ATTRIBUTE_VETS, getVets()); } @GetMapping(value = "/vets.json", produces = MediaType.APPLICATION_JSON_VALUE) diff --git a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java index 1546c814f..e770532ab 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java @@ -36,6 +36,7 @@ @Controller public class VisitController { + private static final String VIEWS_VISIT_FORM = "pets/createOrUpdateVisitForm"; private final ClinicService clinicService; public VisitController(ClinicService clinicService) { @@ -67,14 +68,14 @@ public Visit loadPetWithVisit(@PathVariable("petId") int petId) { // Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is called @GetMapping(value = "/owners/{ownerId}/pets/{petId}/visits/new") public String initNewVisitForm() { - return "pets/createOrUpdateVisitForm"; + return VIEWS_VISIT_FORM; } // Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is called @PostMapping(value = "/owners/{ownerId}/pets/{petId}/visits/new") public String processNewVisitForm(@Valid Visit visit, BindingResult result) { if (result.hasErrors()) { - return "pets/createOrUpdateVisitForm"; + return VIEWS_VISIT_FORM; } this.clinicService.saveVisit(visit); From 5b93163cdbd6440f0f1ab9740d3a76b84ba17abd Mon Sep 17 00:00:00 2001 From: formatunitedandreas-code Date: Sun, 12 Jul 2026 16:17:49 +0200 Subject: [PATCH 03/65] Guard main CI Sonar analysis without token Split main CI into an unconditional Maven verify step and a Sonar analysis step gated on SONAR_TOKEN. This keeps the owned fork main workflow green when SonarCloud secrets are not configured. --- .github/workflows/maven-build-main.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/maven-build-main.yml b/.github/workflows/maven-build-main.yml index daa566a7a..5eed44e00 100644 --- a/.github/workflows/maven-build-main.yml +++ b/.github/workflows/maven-build-main.yml @@ -11,6 +11,8 @@ jobs: build: runs-on: ubuntu-latest + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} strategy: matrix: java: [ '17', '21' ] @@ -23,8 +25,10 @@ jobs: java-version: ${{matrix.java}} distribution: 'adopt' cache: maven - - name: Build and analyze + - name: Build + run: ./mvnw -B verify + - name: Analyze with Sonar + if: ${{ env.SONAR_TOKEN != '' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./mvnw -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=spring-petclinic_spring-framework-petclinic -Dsonar.organization=spring-petclinic + run: ./mvnw -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=spring-petclinic_spring-framework-petclinic -Dsonar.organization=spring-petclinic From d9a7795c9f904ef9a606459cc29e93ec89dbb292 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 18:44:53 +0200 Subject: [PATCH 04/65] Refactor PetClinic pet validator field constants --- .../samples/petclinic/web/PetValidator.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java b/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java index b5d510220..eb6c275c1 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java @@ -34,23 +34,26 @@ public class PetValidator implements Validator { private static final String REQUIRED = "required"; + private static final String FIELD_NAME = "name"; + private static final String FIELD_TYPE = "type"; + private static final String FIELD_BIRTH_DATE = "birthDate"; @Override public void validate(Object obj, Errors errors) { Pet pet = (Pet) obj; // name validation if (!StringUtils.hasLength(pet.getName())) { - errors.rejectValue("name", REQUIRED, REQUIRED); + errors.rejectValue(FIELD_NAME, REQUIRED, REQUIRED); } // type validation if (pet.isNew() && pet.getType() == null) { - errors.rejectValue("type", REQUIRED, REQUIRED); + errors.rejectValue(FIELD_TYPE, REQUIRED, REQUIRED); } // birth date validation if (pet.getBirthDate() == null) { - errors.rejectValue("birthDate", REQUIRED, REQUIRED); + errors.rejectValue(FIELD_BIRTH_DATE, REQUIRED, REQUIRED); } } From f2f57a091c97f6abf8d8afeae70a8287270a53c1 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 18:51:01 +0200 Subject: [PATCH 05/65] Refactor PetClinic pet validator helper method --- .../samples/petclinic/web/PetValidator.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java b/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java index eb6c275c1..c551231cc 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java @@ -43,20 +43,24 @@ public void validate(Object obj, Errors errors) { Pet pet = (Pet) obj; // name validation if (!StringUtils.hasLength(pet.getName())) { - errors.rejectValue(FIELD_NAME, REQUIRED, REQUIRED); + rejectRequiredField(errors, FIELD_NAME); } // type validation if (pet.isNew() && pet.getType() == null) { - errors.rejectValue(FIELD_TYPE, REQUIRED, REQUIRED); + rejectRequiredField(errors, FIELD_TYPE); } // birth date validation if (pet.getBirthDate() == null) { - errors.rejectValue(FIELD_BIRTH_DATE, REQUIRED, REQUIRED); + rejectRequiredField(errors, FIELD_BIRTH_DATE); } } + private void rejectRequiredField(Errors errors, String fieldName) { + errors.rejectValue(fieldName, REQUIRED, REQUIRED); + } + /** * This Validator validates *just* Pet instances */ From f70ba321e4e7087e91514e55b02ccd58dc3e3817 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 18:53:40 +0200 Subject: [PATCH 06/65] Refactor PetClinic pet owner model attribute constant --- .../springframework/samples/petclinic/web/PetController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetController.java b/src/main/java/org/springframework/samples/petclinic/web/PetController.java index ec6f3a78c..cc1fd8f36 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetController.java @@ -41,6 +41,7 @@ public class PetController { private static final String VIEWS_PETS_CREATE_OR_UPDATE_FORM = "pets/createOrUpdatePetForm"; private static final String MODEL_ATTRIBUTE_PET = "pet"; + private static final String MODEL_ATTRIBUTE_OWNER = "owner"; private static final String VIEW_REDIRECT_OWNERS = "redirect:/owners/{ownerId}"; private final ClinicService clinicService; @@ -53,12 +54,12 @@ public Collection populatePetTypes() { return this.clinicService.findPetTypes(); } - @ModelAttribute("owner") + @ModelAttribute(MODEL_ATTRIBUTE_OWNER) public Owner findOwner(@PathVariable("ownerId") int ownerId) { return this.clinicService.findOwnerById(ownerId); } - @InitBinder("owner") + @InitBinder(MODEL_ATTRIBUTE_OWNER) public void initOwnerBinder(WebDataBinder dataBinder) { dataBinder.setDisallowedFields("id"); } From a696a41290e671f9e3f256bd8ddab61bbee8aff6 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 19:07:15 +0200 Subject: [PATCH 07/65] Refactor PetClinic petcontroller pets new path constant --- .../springframework/samples/petclinic/web/PetController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetController.java b/src/main/java/org/springframework/samples/petclinic/web/PetController.java index cc1fd8f36..137e69251 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetController.java @@ -41,6 +41,7 @@ public class PetController { private static final String VIEWS_PETS_CREATE_OR_UPDATE_FORM = "pets/createOrUpdatePetForm"; private static final String MODEL_ATTRIBUTE_PET = "pet"; + private static final String PET_NEW_PATH = "/pets/new"; private static final String MODEL_ATTRIBUTE_OWNER = "owner"; private static final String VIEW_REDIRECT_OWNERS = "redirect:/owners/{ownerId}"; private final ClinicService clinicService; @@ -69,7 +70,7 @@ public void initPetBinder(WebDataBinder dataBinder) { dataBinder.setValidator(new PetValidator()); } - @GetMapping(value = "/pets/new") + @GetMapping(value = PET_NEW_PATH) public String initCreationForm(Owner owner, ModelMap model) { Pet pet = new Pet(); owner.addPet(pet); @@ -77,7 +78,7 @@ public String initCreationForm(Owner owner, ModelMap model) { return VIEWS_PETS_CREATE_OR_UPDATE_FORM; } - @PostMapping(value = "/pets/new") + @PostMapping(value = PET_NEW_PATH) public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result, ModelMap model) { if (hasDuplicatePetName(owner, pet)) { result.rejectValue("name", "duplicate", "already exists"); From 9b72f0038a99b447f68d8f25e58a6abd04e6c280 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 19:08:18 +0200 Subject: [PATCH 08/65] Refactor PetClinic petcontroller edit path constant --- .../springframework/samples/petclinic/web/PetController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetController.java b/src/main/java/org/springframework/samples/petclinic/web/PetController.java index 137e69251..c52555194 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetController.java @@ -41,6 +41,7 @@ public class PetController { private static final String VIEWS_PETS_CREATE_OR_UPDATE_FORM = "pets/createOrUpdatePetForm"; private static final String MODEL_ATTRIBUTE_PET = "pet"; + private static final String PET_EDIT_PATH = "/pets/{petId}/edit"; private static final String PET_NEW_PATH = "/pets/new"; private static final String MODEL_ATTRIBUTE_OWNER = "owner"; private static final String VIEW_REDIRECT_OWNERS = "redirect:/owners/{ownerId}"; @@ -96,13 +97,13 @@ private boolean hasDuplicatePetName(Owner owner, Pet pet) { return StringUtils.hasLength(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null; } - @GetMapping(value = "/pets/{petId}/edit") + @GetMapping(value = PET_EDIT_PATH) public String initUpdateForm(@PathVariable("petId") int petId, ModelMap model) { model.put(MODEL_ATTRIBUTE_PET, this.clinicService.findPetById(petId)); return VIEWS_PETS_CREATE_OR_UPDATE_FORM; } - @PostMapping(value = "/pets/{petId}/edit") + @PostMapping(value = PET_EDIT_PATH) public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owner, ModelMap model) { if (result.hasErrors()) { return showPetForm(model, pet); From 5a1928ad9410f1bc173ebf4fe851a383cdb5e934 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 19:10:23 +0200 Subject: [PATCH 09/65] Refactor PetClinic owner edit path constant --- .../samples/petclinic/web/OwnerController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java index ebe8f2e62..489e3eef0 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java @@ -41,6 +41,7 @@ public class OwnerController { private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm"; private static final String VIEWS_OWNER_FIND_OWNERS = "owners/findOwners"; private static final String MODEL_ATTRIBUTE_OWNER = "owner"; + private static final String OWNER_EDIT_PATH = "/owners/{ownerId}/edit"; private final ClinicService clinicService; public OwnerController(ClinicService clinicService) { @@ -107,13 +108,13 @@ private String handleMultipleOwners(Map model, Collection return "owners/ownersList"; } - @GetMapping(value = "/owners/{ownerId}/edit") + @GetMapping(value = OWNER_EDIT_PATH) public String initUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model model) { model.addAttribute(this.clinicService.findOwnerById(ownerId)); return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; } - @PostMapping(value = "/owners/{ownerId}/edit") + @PostMapping(value = OWNER_EDIT_PATH) public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result, @PathVariable("ownerId") int ownerId) { if (result.hasErrors()) { return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; From 5705dbd57af9ce2adb5fd5c9a95134d4dc47f1ba Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 19:11:16 +0200 Subject: [PATCH 10/65] Refactor PetClinic owner path constants --- .../samples/petclinic/web/OwnerController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java index 489e3eef0..15f907503 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java @@ -42,6 +42,7 @@ public class OwnerController { private static final String VIEWS_OWNER_FIND_OWNERS = "owners/findOwners"; private static final String MODEL_ATTRIBUTE_OWNER = "owner"; private static final String OWNER_EDIT_PATH = "/owners/{ownerId}/edit"; + private static final String OWNER_NEW_PATH = "/owners/new"; private final ClinicService clinicService; public OwnerController(ClinicService clinicService) { @@ -53,13 +54,13 @@ public void setAllowedFields(WebDataBinder dataBinder) { dataBinder.setDisallowedFields("id"); } - @GetMapping(value = "/owners/new") + @GetMapping(value = OWNER_NEW_PATH) public String initCreationForm(Map model) { model.put(MODEL_ATTRIBUTE_OWNER, new Owner()); return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; } - @PostMapping(value = "/owners/new") + @PostMapping(value = OWNER_NEW_PATH) public String processCreationForm(@Valid Owner owner, BindingResult result) { if (result.hasErrors()) { return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; From 1d21c063f02bc86e8127a98c35776536312fb9d2 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 19:12:25 +0200 Subject: [PATCH 11/65] Refactor PetClinic visit path constant --- .../samples/petclinic/web/VisitController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java index e770532ab..0f7ff0502 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java @@ -37,6 +37,7 @@ public class VisitController { private static final String VIEWS_VISIT_FORM = "pets/createOrUpdateVisitForm"; + private static final String VISIT_NEW_PATH = "/owners/{ownerId}/pets/{petId}/visits/new"; private final ClinicService clinicService; public VisitController(ClinicService clinicService) { @@ -66,13 +67,13 @@ public Visit loadPetWithVisit(@PathVariable("petId") int petId) { } // Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is called - @GetMapping(value = "/owners/{ownerId}/pets/{petId}/visits/new") + @GetMapping(value = VISIT_NEW_PATH) public String initNewVisitForm() { return VIEWS_VISIT_FORM; } // Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is called - @PostMapping(value = "/owners/{ownerId}/pets/{petId}/visits/new") + @PostMapping(value = VISIT_NEW_PATH) public String processNewVisitForm(@Valid Visit visit, BindingResult result) { if (result.hasErrors()) { return VIEWS_VISIT_FORM; From 648acc8e5440682cd20ceeb65835e93a03e7c9b5 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 19:19:32 +0200 Subject: [PATCH 12/65] Refactor PetClinic pet model attribute constant --- .../springframework/samples/petclinic/web/PetController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetController.java b/src/main/java/org/springframework/samples/petclinic/web/PetController.java index c52555194..469abb59d 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetController.java @@ -66,7 +66,7 @@ public void initOwnerBinder(WebDataBinder dataBinder) { dataBinder.setDisallowedFields("id"); } - @InitBinder("pet") + @InitBinder(MODEL_ATTRIBUTE_PET) public void initPetBinder(WebDataBinder dataBinder) { dataBinder.setValidator(new PetValidator()); } From 8ec93481774fb60c9133c74cf38e3a7c00a74ea8 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 19:22:10 +0200 Subject: [PATCH 13/65] Refactor PetClinic owner redirect helper --- .../samples/petclinic/web/OwnerController.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java index 15f907503..d6cb02693 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java @@ -43,6 +43,7 @@ public class OwnerController { private static final String MODEL_ATTRIBUTE_OWNER = "owner"; private static final String OWNER_EDIT_PATH = "/owners/{ownerId}/edit"; private static final String OWNER_NEW_PATH = "/owners/new"; + private static final String REDIRECT_TO_OWNERS = "redirect:/owners/"; private final ClinicService clinicService; public OwnerController(ClinicService clinicService) { @@ -67,7 +68,7 @@ public String processCreationForm(@Valid Owner owner, BindingResult result) { } this.clinicService.saveOwner(owner); - return "redirect:/owners/" + owner.getId(); + return buildOwnerRedirect(owner.getId()); } @GetMapping(value = "/owners/find") @@ -101,7 +102,11 @@ private String handleNoOwners(BindingResult result) { } private String handleSingleOwner(Collection results) { - return "redirect:/owners/" + results.iterator().next().getId(); + return buildOwnerRedirect(results.iterator().next().getId()); + } + + private String buildOwnerRedirect(Integer ownerId) { + return REDIRECT_TO_OWNERS + ownerId; } private String handleMultipleOwners(Map model, Collection results) { From 1d0128a61dd20dacfaca23a5eea6c91f5a94f2a7 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 19:23:18 +0200 Subject: [PATCH 14/65] Refactor PetClinic visit list model attribute constant --- .../samples/petclinic/web/VisitController.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java index 0f7ff0502..09b6cd3f9 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java @@ -37,6 +37,7 @@ public class VisitController { private static final String VIEWS_VISIT_FORM = "pets/createOrUpdateVisitForm"; + private static final String MODEL_ATTRIBUTE_VISITS = "visits"; private static final String VISIT_NEW_PATH = "/owners/{ownerId}/pets/{petId}/visits/new"; private final ClinicService clinicService; @@ -85,8 +86,12 @@ public String processNewVisitForm(@Valid Visit visit, BindingResult result) { @GetMapping(value = "/owners/{ownerId}/pets/{petId}/visits") public String showVisits(@PathVariable int petId, Map model) { - model.put("visits", this.clinicService.findPetById(petId).getVisits()); + addVisitsToModel(petId, model); return "visitList"; } + private void addVisitsToModel(int petId, Map model) { + model.put(MODEL_ATTRIBUTE_VISITS, this.clinicService.findPetById(petId).getVisits()); + } + } From b031d5ffb7a355f6481c28a24cbf5bcbbd03407d Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 19:35:06 +0200 Subject: [PATCH 15/65] Refactor PetClinic owner lastName normalization --- .../samples/petclinic/web/OwnerController.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java index d6cb02693..47617d8e2 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java @@ -79,12 +79,9 @@ public String initFindForm(Map model) { @GetMapping(value = "/owners") public String processFindForm(Owner owner, BindingResult result, Map model) { + normalizeLastName(owner); // allow parameterless GET request for /owners to return all records - if (owner.getLastName() == null) { - owner.setLastName(""); // empty string signifies broadest possible search - } - // find owners by last name Collection results = this.clinicService.findOwnerByLastName(owner.getLastName()); if (results.isEmpty()) { @@ -96,6 +93,13 @@ public String processFindForm(Owner owner, BindingResult result, Map Date: Sun, 12 Jul 2026 19:36:14 +0200 Subject: [PATCH 16/65] Refactor PetClinic pet persistence helper --- .../samples/petclinic/web/PetController.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetController.java b/src/main/java/org/springframework/samples/petclinic/web/PetController.java index 469abb59d..d1b9ab065 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetController.java @@ -88,8 +88,7 @@ public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult res return showPetForm(model, pet); } - owner.addPet(pet); - this.clinicService.savePet(pet); + savePetForOwner(owner, pet); return VIEW_REDIRECT_OWNERS; } @@ -109,9 +108,13 @@ public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owne return showPetForm(model, pet); } + savePetForOwner(owner, pet); + return VIEW_REDIRECT_OWNERS; + } + + private void savePetForOwner(Owner owner, Pet pet) { owner.addPet(pet); this.clinicService.savePet(pet); - return VIEW_REDIRECT_OWNERS; } private String showPetForm(ModelMap model, Pet pet) { From 85dc0c21ccad54a53fe479c4256c423a0f46f9be Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 19:38:36 +0200 Subject: [PATCH 17/65] Refactor PetClinic owner details view helper --- .../samples/petclinic/web/OwnerController.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java index 47617d8e2..c544e7adb 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java @@ -143,6 +143,10 @@ public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result, @ */ @GetMapping("/owners/{ownerId}") public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) { + return buildOwnerDetailsView(ownerId); + } + + private ModelAndView buildOwnerDetailsView(int ownerId) { return new ModelAndView("owners/ownerDetails").addObject(this.clinicService.findOwnerById(ownerId)); } From 0123a460cc80239a57d696bd86dbadae9accabea Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 19:47:55 +0200 Subject: [PATCH 18/65] Refactor PetClinic pet validator readability --- .../samples/petclinic/web/PetValidator.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java b/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java index c551231cc..fa531ef89 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java @@ -41,17 +41,21 @@ public class PetValidator implements Validator { @Override public void validate(Object obj, Errors errors) { Pet pet = (Pet) obj; - // name validation + validateName(errors, pet); + validateRequiredFieldsForNewPet(errors, pet); + } + + private void validateName(Errors errors, Pet pet) { if (!StringUtils.hasLength(pet.getName())) { rejectRequiredField(errors, FIELD_NAME); } + } - // type validation + private void validateRequiredFieldsForNewPet(Errors errors, Pet pet) { if (pet.isNew() && pet.getType() == null) { rejectRequiredField(errors, FIELD_TYPE); } - // birth date validation if (pet.getBirthDate() == null) { rejectRequiredField(errors, FIELD_BIRTH_DATE); } From b85da56aeb1d538f7e5a0736369060afdd537858 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 19:50:08 +0200 Subject: [PATCH 19/65] Refactor PetClinic owner view model constants --- .../samples/petclinic/web/OwnerController.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java index c544e7adb..fc3a8cccb 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java @@ -40,7 +40,10 @@ public class OwnerController { private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm"; private static final String VIEWS_OWNER_FIND_OWNERS = "owners/findOwners"; + private static final String VIEWS_OWNER_LIST = "owners/ownersList"; + private static final String VIEWS_OWNER_DETAILS = "owners/ownerDetails"; private static final String MODEL_ATTRIBUTE_OWNER = "owner"; + private static final String MODEL_ATTRIBUTE_SELECTIONS = "selections"; private static final String OWNER_EDIT_PATH = "/owners/{ownerId}/edit"; private static final String OWNER_NEW_PATH = "/owners/new"; private static final String REDIRECT_TO_OWNERS = "redirect:/owners/"; @@ -114,8 +117,8 @@ private String buildOwnerRedirect(Integer ownerId) { } private String handleMultipleOwners(Map model, Collection results) { - model.put("selections", results); - return "owners/ownersList"; + model.put(MODEL_ATTRIBUTE_SELECTIONS, results); + return VIEWS_OWNER_LIST; } @GetMapping(value = OWNER_EDIT_PATH) @@ -147,7 +150,7 @@ public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) { } private ModelAndView buildOwnerDetailsView(int ownerId) { - return new ModelAndView("owners/ownerDetails").addObject(this.clinicService.findOwnerById(ownerId)); + return new ModelAndView(VIEWS_OWNER_DETAILS).addObject(this.clinicService.findOwnerById(ownerId)); } } From a31903ac4e5c586d0f0ade6db72222763432773f Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 19:52:01 +0200 Subject: [PATCH 20/65] Refactor PetClinic owner redirect constant --- .../springframework/samples/petclinic/web/OwnerController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java index fc3a8cccb..d8b13e9ac 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java @@ -47,6 +47,7 @@ public class OwnerController { private static final String OWNER_EDIT_PATH = "/owners/{ownerId}/edit"; private static final String OWNER_NEW_PATH = "/owners/new"; private static final String REDIRECT_TO_OWNERS = "redirect:/owners/"; + private static final String REDIRECT_TO_OWNER = "redirect:/owners/{ownerId}"; private final ClinicService clinicService; public OwnerController(ClinicService clinicService) { @@ -135,7 +136,7 @@ public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result, @ owner.setId(ownerId); this.clinicService.saveOwner(owner); - return "redirect:/owners/{ownerId}"; + return REDIRECT_TO_OWNER; } /** From 039c17d1fca1e5100ac840eb3abfc7b8dc06c50e Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 19:54:21 +0200 Subject: [PATCH 21/65] Refactor PetClinic visit redirect and list constants --- .../samples/petclinic/web/VisitController.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java index 09b6cd3f9..2d683f18b 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java @@ -39,6 +39,8 @@ public class VisitController { private static final String VIEWS_VISIT_FORM = "pets/createOrUpdateVisitForm"; private static final String MODEL_ATTRIBUTE_VISITS = "visits"; private static final String VISIT_NEW_PATH = "/owners/{ownerId}/pets/{petId}/visits/new"; + private static final String REDIRECT_TO_VISIT_OWNER = "redirect:/owners/{ownerId}"; + private static final String VIEWS_VISIT_LIST = "visitList"; private final ClinicService clinicService; public VisitController(ClinicService clinicService) { @@ -81,13 +83,13 @@ public String processNewVisitForm(@Valid Visit visit, BindingResult result) { } this.clinicService.saveVisit(visit); - return "redirect:/owners/{ownerId}"; + return REDIRECT_TO_VISIT_OWNER; } @GetMapping(value = "/owners/{ownerId}/pets/{petId}/visits") public String showVisits(@PathVariable int petId, Map model) { addVisitsToModel(petId, model); - return "visitList"; + return VIEWS_VISIT_LIST; } private void addVisitsToModel(int petId, Map model) { From a4b7a2d0a42f9f3c5c46564dfe91ffc37eb5a5e6 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 19:56:08 +0200 Subject: [PATCH 22/65] Refactor PetClinic pet type parse helper --- .../samples/petclinic/web/PetTypeFormatter.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetTypeFormatter.java b/src/main/java/org/springframework/samples/petclinic/web/PetTypeFormatter.java index 1550c698d..c7e85101b 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetTypeFormatter.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetTypeFormatter.java @@ -54,11 +54,15 @@ public String print(PetType petType, Locale locale) { @Override public PetType parse(String text, Locale locale) throws ParseException { for (PetType type : this.clinicService.findPetTypes()) { - if (type.getName().equals(text)) { + if (matchesName(type, text)) { return type; } } throw new ParseException("type not found: " + text, 0); } + private boolean matchesName(PetType type, String text) { + return type.getName().equals(text); + } + } From 60017b1d43b9a234bab455b610f097f3f487e271 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 19:57:32 +0200 Subject: [PATCH 23/65] Refactor PetClinic owner form initialization helper --- .../samples/petclinic/web/OwnerController.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java index d8b13e9ac..a8b0d022f 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java @@ -61,7 +61,7 @@ public void setAllowedFields(WebDataBinder dataBinder) { @GetMapping(value = OWNER_NEW_PATH) public String initCreationForm(Map model) { - model.put(MODEL_ATTRIBUTE_OWNER, new Owner()); + initializeOwnerModel(model); return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; } @@ -77,10 +77,14 @@ public String processCreationForm(@Valid Owner owner, BindingResult result) { @GetMapping(value = "/owners/find") public String initFindForm(Map model) { - model.put(MODEL_ATTRIBUTE_OWNER, new Owner()); + initializeOwnerModel(model); return VIEWS_OWNER_FIND_OWNERS; } + private void initializeOwnerModel(Map model) { + model.put(MODEL_ATTRIBUTE_OWNER, new Owner()); + } + @GetMapping(value = "/owners") public String processFindForm(Owner owner, BindingResult result, Map model) { normalizeLastName(owner); From 01d1b2fa3cc5eb5df71532b679c3399217800dc0 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 19:58:34 +0200 Subject: [PATCH 24/65] Refactor PetClinic pet form save flow --- .../samples/petclinic/web/PetController.java | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetController.java b/src/main/java/org/springframework/samples/petclinic/web/PetController.java index d1b9ab065..13e9f2edc 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetController.java @@ -81,15 +81,7 @@ public String initCreationForm(Owner owner, ModelMap model) { @PostMapping(value = PET_NEW_PATH) public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result, ModelMap model) { - if (hasDuplicatePetName(owner, pet)) { - result.rejectValue("name", "duplicate", "already exists"); - } - if (result.hasErrors()) { - return showPetForm(model, pet); - } - - savePetForOwner(owner, pet); - return VIEW_REDIRECT_OWNERS; + return savePetFormResult(owner, pet, result, model, hasDuplicatePetName(owner, pet)); } private boolean hasDuplicatePetName(Owner owner, Pet pet) { @@ -104,6 +96,18 @@ public String initUpdateForm(@PathVariable("petId") int petId, ModelMap model) { @PostMapping(value = PET_EDIT_PATH) public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owner, ModelMap model) { + return savePetFormResult(owner, pet, result, model, false); + } + + private void savePetForOwner(Owner owner, Pet pet) { + owner.addPet(pet); + this.clinicService.savePet(pet); + } + + private String savePetFormResult(Owner owner, Pet pet, BindingResult result, ModelMap model, boolean duplicate) { + if (duplicate) { + result.rejectValue("name", "duplicate", "already exists"); + } if (result.hasErrors()) { return showPetForm(model, pet); } @@ -112,11 +116,6 @@ public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owne return VIEW_REDIRECT_OWNERS; } - private void savePetForOwner(Owner owner, Pet pet) { - owner.addPet(pet); - this.clinicService.savePet(pet); - } - private String showPetForm(ModelMap model, Pet pet) { model.put(MODEL_ATTRIBUTE_PET, pet); return VIEWS_PETS_CREATE_OR_UPDATE_FORM; From 08626ba114b6a13e2c449bf869e33eee9b4d1f95 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 19:59:39 +0200 Subject: [PATCH 25/65] Refactor PetClinic vet response method reuse --- .../samples/petclinic/web/VetController.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/VetController.java b/src/main/java/org/springframework/samples/petclinic/web/VetController.java index 0429211c9..cfe8b4000 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/VetController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/VetController.java @@ -56,13 +56,13 @@ private void addVetsToModel(Map model) { @GetMapping(value = "/vets.json", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public Vets showJsonVetList() { - return getVets(); + return getVetsForResponse(); } @GetMapping(value = "/vets.xml", produces = MediaType.APPLICATION_XML_VALUE) @ResponseBody public Vets showXmlVetList() { - return getVets(); + return getVetsForResponse(); } private Vets getVets() { @@ -73,4 +73,8 @@ private Vets getVets() { return vets; } + private Vets getVetsForResponse() { + return getVets(); + } + } From f59b409097e6345d39de5f3efbc840a211f4c9da Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 20:01:19 +0200 Subject: [PATCH 26/65] Refactor PetClinic visit form submission flow --- .../samples/petclinic/web/VisitController.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java index 2d683f18b..0a3112577 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java @@ -78,14 +78,22 @@ public String initNewVisitForm() { // Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is called @PostMapping(value = VISIT_NEW_PATH) public String processNewVisitForm(@Valid Visit visit, BindingResult result) { + return handleVisitSubmission(visit, result); + } + + private String handleVisitSubmission(Visit visit, BindingResult result) { if (result.hasErrors()) { return VIEWS_VISIT_FORM; } - this.clinicService.saveVisit(visit); + saveVisit(visit); return REDIRECT_TO_VISIT_OWNER; } + private void saveVisit(Visit visit) { + this.clinicService.saveVisit(visit); + } + @GetMapping(value = "/owners/{ownerId}/pets/{petId}/visits") public String showVisits(@PathVariable int petId, Map model) { addVisitsToModel(petId, model); From 0b15790b192e0b871c9a29aaf66942d9b1e50c34 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 20:03:09 +0200 Subject: [PATCH 27/65] Refactor PetClinic owner edit model helper --- .../samples/petclinic/web/OwnerController.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java index a8b0d022f..3ae45f731 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java @@ -128,10 +128,14 @@ private String handleMultipleOwners(Map model, Collection @GetMapping(value = OWNER_EDIT_PATH) public String initUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model model) { - model.addAttribute(this.clinicService.findOwnerById(ownerId)); + addOwnerToModel(model, ownerId); return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; } + private void addOwnerToModel(Model model, int ownerId) { + model.addAttribute(MODEL_ATTRIBUTE_OWNER, this.clinicService.findOwnerById(ownerId)); + } + @PostMapping(value = OWNER_EDIT_PATH) public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result, @PathVariable("ownerId") int ownerId) { if (result.hasErrors()) { From cccb850041e9043de436b1781764fb0c2e19260a Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 20:04:25 +0200 Subject: [PATCH 28/65] Refactor PetClinic pet creation form helper --- .../samples/petclinic/web/PetController.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetController.java b/src/main/java/org/springframework/samples/petclinic/web/PetController.java index 13e9f2edc..6e57c1d85 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetController.java @@ -73,10 +73,14 @@ public void initPetBinder(WebDataBinder dataBinder) { @GetMapping(value = PET_NEW_PATH) public String initCreationForm(Owner owner, ModelMap model) { + addPetToModel(owner, model); + return VIEWS_PETS_CREATE_OR_UPDATE_FORM; + } + + private void addPetToModel(Owner owner, ModelMap model) { Pet pet = new Pet(); owner.addPet(pet); model.put(MODEL_ATTRIBUTE_PET, pet); - return VIEWS_PETS_CREATE_OR_UPDATE_FORM; } @PostMapping(value = PET_NEW_PATH) From 899cab4ba1121107e54820e4f56befe5ff855828 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 20:05:39 +0200 Subject: [PATCH 29/65] Refactor PetClinic pet update model helper --- .../samples/petclinic/web/PetController.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetController.java b/src/main/java/org/springframework/samples/petclinic/web/PetController.java index 6e57c1d85..fc2f98900 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetController.java @@ -94,10 +94,14 @@ private boolean hasDuplicatePetName(Owner owner, Pet pet) { @GetMapping(value = PET_EDIT_PATH) public String initUpdateForm(@PathVariable("petId") int petId, ModelMap model) { - model.put(MODEL_ATTRIBUTE_PET, this.clinicService.findPetById(petId)); + addPetToModelForUpdate(petId, model); return VIEWS_PETS_CREATE_OR_UPDATE_FORM; } + private void addPetToModelForUpdate(int petId, ModelMap model) { + model.put(MODEL_ATTRIBUTE_PET, this.clinicService.findPetById(petId)); + } + @PostMapping(value = PET_EDIT_PATH) public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owner, ModelMap model) { return savePetFormResult(owner, pet, result, model, false); From 50477e598ff32d17c9d2852458938e1a68849f56 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 20:06:49 +0200 Subject: [PATCH 30/65] Refactor PetClinic visit form view helper --- .../samples/petclinic/web/VisitController.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java index 0a3112577..841eb37e6 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java @@ -72,7 +72,7 @@ public Visit loadPetWithVisit(@PathVariable("petId") int petId) { // Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is called @GetMapping(value = VISIT_NEW_PATH) public String initNewVisitForm() { - return VIEWS_VISIT_FORM; + return visitFormView(); } // Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is called @@ -83,13 +83,17 @@ public String processNewVisitForm(@Valid Visit visit, BindingResult result) { private String handleVisitSubmission(Visit visit, BindingResult result) { if (result.hasErrors()) { - return VIEWS_VISIT_FORM; + return visitFormView(); } saveVisit(visit); return REDIRECT_TO_VISIT_OWNER; } + private String visitFormView() { + return VIEWS_VISIT_FORM; + } + private void saveVisit(Visit visit) { this.clinicService.saveVisit(visit); } From 10fb468d8199c62c11a5129e581047496145c4ff Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 20:08:41 +0200 Subject: [PATCH 31/65] Refactor PetClinic visit model helper --- .../samples/petclinic/web/VisitController.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java index 841eb37e6..b54720132 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java @@ -64,6 +64,10 @@ public void setAllowedFields(WebDataBinder dataBinder) { */ @ModelAttribute("visit") public Visit loadPetWithVisit(@PathVariable("petId") int petId) { + return createVisitForPet(petId); + } + + private Visit createVisitForPet(int petId) { Visit visit = new Visit(); this.clinicService.findPetById(petId).addVisit(visit); return visit; From 7da01de14b58442658d111bcc8af7b028cb4994c Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 20:16:19 +0200 Subject: [PATCH 32/65] Document autonomous PetClinic refactor lease --- ...lity-refactor-batch-invocation-template.md | 105 +++++ ...ous-readability-refactor-batch-lease-v1.md | 437 ++++++++++++++++++ 2 files changed, 542 insertions(+) create mode 100644 docs/leases/petclinic-autonomous-readability-refactor-batch-invocation-template.md create mode 100644 docs/leases/petclinic-autonomous-readability-refactor-batch-lease-v1.md diff --git a/docs/leases/petclinic-autonomous-readability-refactor-batch-invocation-template.md b/docs/leases/petclinic-autonomous-readability-refactor-batch-invocation-template.md new file mode 100644 index 000000000..b8270760e --- /dev/null +++ b/docs/leases/petclinic-autonomous-readability-refactor-batch-invocation-template.md @@ -0,0 +1,105 @@ +# PetClinic Autonomous Readability Refactor Batch Invocation Template + +Use this template to start a governed autonomous refactor batch from the current +repository state. + +```yaml +startLease: petclinic-autonomous-readability-refactor-batch-lease-v1 + +repository: C:\dev\spring-framework-petclinic +branch: +startHead: +initialWorktree: clean + +budget: + maxCandidatesThisRun: 10 + maxCommitsThisRun: 10 + maxFilesPerCandidate: 1 + maxChangedLinesPerCandidate: 80 + maxNewPrivateMethodsPerCandidate: 2 + maxRepairAttemptsPerCandidate: 1 + +scope: + primaryAllowedPaths: + - src/main/java/org/springframework/samples/petclinic/web/**/*.java + - src/main/java/org/springframework/samples/petclinic/service/**/*.java + optionalAllowedPaths: + - src/main/java/org/springframework/samples/petclinic/model/**/*.java + - src/main/java/org/springframework/samples/petclinic/util/**/*.java + +validation: + fullMavenTestRequiredPerCandidate: true + commands: + - git diff --name-only + - git diff --check + - $env:JAVA_HOME='C:\Program Files\Java\jdk-17'; .\mvnw.cmd test + +authorization: + allowed: + - inspect repository state + - discover and rank candidates + - admit one candidate at a time + - edit only the admitted file + - exact-revert current candidate on validation failure + - one bounded repair attempt per candidate + - one local commit per successful candidate + - continue until budget exhausted or no candidate qualifies + forbidden: + - push + - PR creation or update + - upstream interaction + - fetch or pull + - force push + - merge + - release + - deploy + - pom.xml or dependency changes + - src/test changes + - src/main/resources changes + - src/main/webapp changes + - request mapping/view/model/validation/repository/persistence behavior changes + +stopConditions: + - branch mismatch + - start HEAD mismatch + - dirty worktree before candidate + - unexpected changed path + - no candidate score >= 75 + - ambiguity after tie-breaks + - behavior preservation cannot be argued mechanically + - changed-line or helper-method budget exceeded + - git diff --check fails + - Maven test fails after one bounded repair + - exact revert fails + - commit fails + - worktree not clean after commit + - maxCandidatesThisRun reached + - maxCommitsThisRun reached + +finalReport: + include: + - processed candidates + - rejected candidate summary + - commits created + - final HEAD + - final git status + - tests run and results + - stopped reason + - remaining likely candidates + - explicit non-claims +``` + +Example invocation: + +```text +Start lease petclinic-autonomous-readability-refactor-batch-lease-v1. + +Repository: C:\dev\spring-framework-petclinic +Branch: threshold-governed-refactor-demo-3 +Start HEAD: +Initial worktree: clean + +Run autonomously until budget exhaustion or ready_no_candidates. +Do not push, open or update PRs, merge, release, deploy, fetch, pull, or +interact with upstream. +``` diff --git a/docs/leases/petclinic-autonomous-readability-refactor-batch-lease-v1.md b/docs/leases/petclinic-autonomous-readability-refactor-batch-lease-v1.md new file mode 100644 index 000000000..64cb07faa --- /dev/null +++ b/docs/leases/petclinic-autonomous-readability-refactor-batch-lease-v1.md @@ -0,0 +1,437 @@ +# PetClinic Autonomous Readability Refactor Batch Lease v1 + +This lease defines the governed workflow for autonomous, low-to-medium-risk +readability refactoring in the owned Spring PetClinic repository. + +It is intentionally split into authority phases. A refactor batch lease may +create local commits, but it must not publish, open or update a PR, merge, +release, deploy, or interact with upstream unless a later phase explicitly +authorizes that action. + +## Lease Identity + +```yaml +leaseName: petclinic-autonomous-readability-refactor-batch-lease-v1 +repository: C:\dev\spring-framework-petclinic +defaultBranchPattern: threshold-governed-refactor-demo* +requiredInitialWorktree: clean +``` + +## Purpose + +Continue PetClinic refactoring autonomously over a bounded candidate pool. +The lease is designed for mechanical readability work only: + +- private helper extraction +- duplicate literal constant extraction +- redundant local variable simplification +- small controller branch readability decomposition +- validator and formatter readability cleanup + +The lease does not authorize behavior changes or public claims about readiness, +correctness, security, or compliance. + +## Authority Levels + +```yaml +allowedRiskClasses: + - R0_READ_ONLY + - R1_READOUT + - R2_REVERSIBLE_LOCAL_MUTATION + - R3_LOCAL_COMMIT + +notAuthorizedByThisLease: + - R4_OWNED_REPO_BRANCH_PUSH + - R4_OWNED_REPO_DRAFT_PR_CREATE_OR_UPDATE + - R4_CI_STABILIZATION + - R5_PR_MERGE + - release + - deploy +``` + +## Batch Budget + +```yaml +maxCandidatesThisRun: 10 +maxCommitsThisRun: 10 +maxFilesPerCandidate: 1 +maxChangedLinesPerCandidate: 80 +maxNewPrivateMethodsPerCandidate: 2 +maxRepairAttemptsPerCandidate: 1 +fullMavenTestRequiredPerCandidate: true +requireCleanWorktreeBeforeEachCandidate: true +requireCleanWorktreeAfterEachCommit: true +stopOnUnexpectedChangedPath: true +``` + +## Allowed Paths + +```yaml +allowedPaths: + - src/main/java/org/springframework/samples/petclinic/web/**/*.java + - src/main/java/org/springframework/samples/petclinic/service/**/*.java + - src/main/java/org/springframework/samples/petclinic/model/**/*.java + - src/main/java/org/springframework/samples/petclinic/util/**/*.java + +forbiddenPaths: + - pom.xml + - src/test/** + - src/main/resources/** + - src/main/webapp/** + - .github/** + - target/** + - hidden/config files +``` + +The default starting scope should be `web` and `service`. `model` and `util` +may be admitted only when the candidate is mechanically local and covered by the +same validation protocol. + +## Candidate Classes + +### 1. Private Helper Extraction For Readability + +Allowed when all conditions hold: + +- same class only +- helper method is private +- at most two new private methods +- extracted code is contiguous or logically identical to an existing local block +- no public API change +- no request mapping change +- no validation annotation change +- no repository call change +- no persistence behavior change +- no returned view name or model attribute name change + +### 2. Duplicate Literal Constant Extraction + +Allowed when all conditions hold: + +- same class only +- new constant is `private static final String` +- literal is repeated in the same file +- literal represents a view name, redirect, model key, route path, validation + field, or validation code +- replacement preserves the exact string value +- at most two constants per candidate + +### 3. Redundant Local Variable Simplification + +Allowed when all conditions hold: + +- local variable is assigned exactly once +- initializer is side-effect free or already required immediately +- simplification does not reduce readability +- no behavior change + +### 4. Controller Branch Readability Decomposition + +Allowed when all conditions hold: + +- only splits existing branch bodies into private methods +- returned view names and redirects are identical strings or constants +- model keys are identical +- BindingResult behavior is identical +- branch order is unchanged +- no request mapping, HTTP method, validation, repository, or persistence change + +### 5. Validator Or Formatter Readability Cleanup + +Allowed when all conditions hold: + +- same class only +- no supported type change +- no validation field/code/message change +- no parse/print semantics change +- no exception message change unless explicitly authorized + +### 6. Micro-Format Cleanup + +Allowed only inside a method already admitted for one of the candidate classes +above. Broad file formatting is not authorized. + +## Explicitly Forbidden Changes + +```yaml +forbiddenChanges: + - request mapping path changes + - HTTP method changes + - validation annotation semantic changes + - repository query semantic changes + - persistence behavior changes + - transaction behavior changes unless separately authorized + - cache annotation changes + - returned view name changes + - model attribute name changes + - dependency or plugin updates + - pom.xml edits + - test edits + - resource edits + - broad formatting + - feature work +``` + +## Discovery Protocol + +Before each candidate: + +```powershell +git status -sb +git rev-parse HEAD +git branch --show-current +git diff --name-only +``` + +Required: + +- worktree is clean +- branch matches the lease invocation +- changed path list is empty + +Discovery must scan only allowed paths. Exclude candidates already committed in +the current branch lineage unless the new candidate is a strictly local +continuation in a different method. + +## Scoring + +```yaml +positive: + singleFile: 30 + noBehaviorChangeExpected: 30 + fullMavenTestAvailable: 20 + existingControllerOrServiceCoverage: 10 + privateHelperImprovesReadability: 15 + duplicateLiteralConstantRemovesRepetition: 10 + redundantLocalVariableSimplification: 12 + diffUnder40ChangedLines: 10 + diff40To80ChangedLines: 5 + noPublicSignatureChange: 10 + +negative: + controllerBranchDecomposition: -10 + helperNameRequiresSemanticJudgment: -10 + changesMoreThanOneBranch: -15 + extractionCrossesNonContiguousLogic: -20 + +reject: + - changes public method signature + - changes request mapping, view, model, validation, repository, or persistence behavior + - requires test changes + - requires pom.xml or dependency changes + - requires upstream, push, PR, merge, release, or deploy +``` + +Admission threshold: + +```yaml +minimumScore: 75 +``` + +## Tie-Break Rules + +Apply in order: + +1. Prefer smaller diff. +2. Prefer one private helper over two. +3. Prefer already covered controller or service methods. +4. Prefer `OwnerController` over `PetController` over `VisitController` over + `VetController` over `PetValidator` over `PetTypeFormatter` over service. +5. Prefer lexical file path. +6. Prefer lexical method name. +7. Stop with an ambiguity packet if still indistinguishable. + +## Candidate Packet + +For every admitted candidate, record: + +```yaml +candidateId: +candidateClass: +baseHead: +allowedFile: +beforeSha256: +expectedDiffSummary: +score: +tieBreakReason: +behaviorPreservation: +validationCommands: + - git diff --name-only + - git diff --check + - $env:JAVA_HOME='C:\Program Files\Java\jdk-17'; .\mvnw.cmd test +revertStrategy: exact restore of admitted file before commit +nonClaims: + - no push + - no PR + - no upstream interaction + - no merge + - no release + - no deploy + - no readiness/correctness/security/compliance claim +``` + +## Patch Protocol + +Rules: + +- edit only the admitted file +- stay within changed-line and helper-method budgets +- preserve all forbidden behavior-change checks +- use repository style +- avoid unrelated formatting + +## Validation Protocol + +Run after every candidate patch: + +```powershell +git diff --name-only +git diff --check +$env:JAVA_HOME='C:\Program Files\Java\jdk-17'; .\mvnw.cmd test +``` + +Required: + +- changed paths contain only the admitted file +- `git diff --check` passes +- full Maven test passes + +## Failure And Repair Protocol + +If validation fails: + +1. Determine whether the failure is clearly caused by the current candidate. +2. If yes, perform at most one bounded repair inside the admitted file. +3. Rerun `git diff --check` and full Maven test. +4. If still failing, exact-revert the admitted file. +5. Require clean worktree after revert. +6. Stop with a failure receipt. + +If the failure requires forbidden files, upstream interaction, dependency +changes, test changes, force push, merge, release, or deploy, stop without +repair. + +## Commit Protocol + +After successful validation: + +```powershell +Get-FileHash -Algorithm SHA256 +git add +git commit -m "Refactor PetClinic " +git status -sb +git log -1 --oneline +git rev-parse HEAD +``` + +Required: + +- only admitted file staged +- exactly one local commit per successful candidate +- worktree clean after commit + +## Batch Continuation + +Continue to the next candidate only when: + +- budget remains +- worktree is clean +- previous commit succeeded +- another candidate reaches the admission threshold + +Stop with `ready_no_candidates` when no candidate qualifies. + +## Terminal Receipt + +At batch end, report: + +```yaml +processedCandidates: [] +commitsCreated: [] +finalHead: +finalGitStatus: +testsRun: +stoppedReason: +remainingLikelyCandidates: [] +nonClaims: + - no push + - no PR + - no upstream interaction + - no fetch/pull + - no pom.xml/dependency change + - no src/test change + - no request mapping/view/model/validation/repository/persistence behavior change + - no merge/release/deploy + - no public readiness/correctness/security/compliance claim +``` + +## Downstream Authority Phases + +The following leases must be separate invocations. + +### Owned Branch Publish And Draft PR Lease + +Purpose: + +- run final `git diff --check` +- run full Maven test +- push the current branch to owned origin +- create or update a draft PR in the owned repository only + +Not allowed: + +- upstream interaction +- force push +- ready-for-review +- reviewer request +- merge +- release +- deploy + +### Draft PR CI Stabilization Lease + +Purpose: + +- inspect existing owned draft PR +- read CI status +- if green, stop +- if pending, stop +- if failing, perform at most bounded branch-local repairs inside allowed paths + +Not allowed: + +- new feature work +- broad refactoring +- dependency or test changes +- ready-for-review +- reviewer request +- merge +- release +- deploy + +### Owned Repo PR Merge Lease + +Purpose: + +- merge exactly one owned-repository PR using the authorized merge method + +Preconditions: + +- worktree clean +- PR open +- PR not draft +- PR head matches required SHA +- PR targets owned repository, not third-party upstream +- no conflicts +- all required checks pass +- full local Maven test passes + +Not allowed: + +- branch protection bypass +- force push +- code changes +- tag creation +- release +- deploy +- upstream interaction From 24720467e7f60a245f87395a84ad8ab3607b61d0 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 20:29:45 +0200 Subject: [PATCH 33/65] Refactor PetClinic owner pet lookup name check --- .../org/springframework/samples/petclinic/model/Owner.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/model/Owner.java b/src/main/java/org/springframework/samples/petclinic/model/Owner.java index e662aeaa3..46ebb6de2 100644 --- a/src/main/java/org/springframework/samples/petclinic/model/Owner.java +++ b/src/main/java/org/springframework/samples/petclinic/model/Owner.java @@ -126,9 +126,7 @@ public Pet getPet(String name, boolean ignoreNew) { name = name.toLowerCase(); for (Pet pet : getPetsInternal()) { if (!ignoreNew || !pet.isNew()) { - String compName = pet.getName(); - compName = compName.toLowerCase(); - if (compName.equals(name)) { + if (pet.getName().toLowerCase().equals(name)) { return pet; } } From 025113cf4389ec33ae2126250ea55a885c2e8bcb Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 21:07:31 +0200 Subject: [PATCH 34/65] Refactor PetClinic owner pet match helper --- .../springframework/samples/petclinic/model/Owner.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/model/Owner.java b/src/main/java/org/springframework/samples/petclinic/model/Owner.java index 46ebb6de2..f6e54c207 100644 --- a/src/main/java/org/springframework/samples/petclinic/model/Owner.java +++ b/src/main/java/org/springframework/samples/petclinic/model/Owner.java @@ -125,15 +125,17 @@ public Pet getPet(String name) { public Pet getPet(String name, boolean ignoreNew) { name = name.toLowerCase(); for (Pet pet : getPetsInternal()) { - if (!ignoreNew || !pet.isNew()) { - if (pet.getName().toLowerCase().equals(name)) { - return pet; - } + if (isMatchingPet(pet, name, ignoreNew)) { + return pet; } } return null; } + private boolean isMatchingPet(Pet pet, String name, boolean ignoreNew) { + return (!ignoreNew || !pet.isNew()) && pet.getName().toLowerCase().equals(name); + } + @Override public String toString() { return new ToStringCreator(this) From 4b276f69923e519076b9e0a8e195848fcbf07766 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 21:56:19 +0200 Subject: [PATCH 35/65] Extend PetClinic refactor lease repository scope --- ...lity-refactor-batch-invocation-template.md | 11 ++++ ...ous-readability-refactor-batch-lease-v1.md | 64 ++++++++++++++++--- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/docs/leases/petclinic-autonomous-readability-refactor-batch-invocation-template.md b/docs/leases/petclinic-autonomous-readability-refactor-batch-invocation-template.md index b8270760e..7db4f4d8d 100644 --- a/docs/leases/petclinic-autonomous-readability-refactor-batch-invocation-template.md +++ b/docs/leases/petclinic-autonomous-readability-refactor-batch-invocation-template.md @@ -26,6 +26,15 @@ scope: optionalAllowedPaths: - src/main/java/org/springframework/samples/petclinic/model/**/*.java - src/main/java/org/springframework/samples/petclinic/util/**/*.java + - src/main/java/org/springframework/samples/petclinic/repository/**/*.java + repositoryScopeRules: + - readability-only changes + - no repository interface signature changes + - no SQL/HQL/JPQL string changes + - no query parameter name, value, or order changes + - no row mapper, result extractor, association, or persistence behavior changes + - no transaction or cache annotation changes + - no exception type or exception message changes validation: fullMavenTestRequiredPerCandidate: true @@ -58,6 +67,8 @@ authorization: - src/main/resources changes - src/main/webapp changes - request mapping/view/model/validation/repository/persistence behavior changes + - SQL/HQL/JPQL changes + - repository interface changes stopConditions: - branch mismatch diff --git a/docs/leases/petclinic-autonomous-readability-refactor-batch-lease-v1.md b/docs/leases/petclinic-autonomous-readability-refactor-batch-lease-v1.md index 64cb07faa..e6bc2eded 100644 --- a/docs/leases/petclinic-autonomous-readability-refactor-batch-lease-v1.md +++ b/docs/leases/petclinic-autonomous-readability-refactor-batch-lease-v1.md @@ -27,6 +27,8 @@ The lease is designed for mechanical readability work only: - redundant local variable simplification - small controller branch readability decomposition - validator and formatter readability cleanup +- repository/JDBC/JPA readability cleanup without query or persistence behavior + changes The lease does not authorize behavior changes or public claims about readiness, correctness, security, or compliance. @@ -72,6 +74,7 @@ allowedPaths: - src/main/java/org/springframework/samples/petclinic/service/**/*.java - src/main/java/org/springframework/samples/petclinic/model/**/*.java - src/main/java/org/springframework/samples/petclinic/util/**/*.java + - src/main/java/org/springframework/samples/petclinic/repository/**/*.java forbiddenPaths: - pom.xml @@ -83,9 +86,14 @@ forbiddenPaths: - hidden/config files ``` -The default starting scope should be `web` and `service`. `model` and `util` -may be admitted only when the candidate is mechanically local and covered by the -same validation protocol. +The default starting scope should be `web` and `service`. `model`, `util`, and +`repository` may be admitted only when the candidate is mechanically local and +covered by the same validation protocol. + +Repository work is restricted to readability-only changes in the existing JDBC, +JPA, and repository adapter classes. It must not change SQL/HQL/JPQL strings, +query parameters, result mapping semantics, save/merge/persist behavior, +transaction behavior, exception behavior, or repository interface contracts. ## Candidate Classes @@ -146,7 +154,40 @@ Allowed when all conditions hold: - no parse/print semantics change - no exception message change unless explicitly authorized -### 6. Micro-Format Cleanup +### 6. Repository/JDBC/JPA Readability Cleanup + +Allowed when all conditions hold: + +- same class only +- helper method is private +- at most two new private methods +- no repository interface signature change +- no SQL/HQL/JPQL string change +- no query parameter name, value, or order change +- no row mapper, result extractor, or entity association semantic change +- no persist, merge, insert, generated-key, or update behavior change +- no transaction annotation or cache annotation change +- no exception type or exception message change +- existing repository call order is preserved unless the candidate is purely a + local helper extraction that calls the same operations in the same order + +Examples that may qualify: + +- remove an unnecessary `else` after an immediate `return` +- extract a contiguous loop body into a private helper when query and mapping + behavior remain identical +- extract a local private helper for setting already-loaded associations on + already-loaded entities + +Examples that must be rejected: + +- rewriting SQL/HQL/JPQL +- changing joins, ordering, filters, aliases, or selected columns +- changing mapper classes or result extractor contracts +- changing save/update support or generated-key handling +- changing repository interfaces + +### 7. Micro-Format Cleanup Allowed only inside a method already admitted for one of the candidate classes above. Broad file formatting is not authorized. @@ -201,15 +242,19 @@ positive: noBehaviorChangeExpected: 30 fullMavenTestAvailable: 20 existingControllerOrServiceCoverage: 10 + existingRepositoryCoverage: 10 privateHelperImprovesReadability: 15 duplicateLiteralConstantRemovesRepetition: 10 redundantLocalVariableSimplification: 12 + repositoryReadabilityWithoutSemanticChange: 10 diffUnder40ChangedLines: 10 diff40To80ChangedLines: 5 noPublicSignatureChange: 10 negative: controllerBranchDecomposition: -10 + repositoryAdapterTouch: -10 + jdbcOrJpaQueryAdjacentCode: -15 helperNameRequiresSemanticJudgment: -10 changesMoreThanOneBranch: -15 extractionCrossesNonContiguousLogic: -20 @@ -236,10 +281,13 @@ Apply in order: 2. Prefer one private helper over two. 3. Prefer already covered controller or service methods. 4. Prefer `OwnerController` over `PetController` over `VisitController` over - `VetController` over `PetValidator` over `PetTypeFormatter` over service. -5. Prefer lexical file path. -6. Prefer lexical method name. -7. Stop with an ambiguity packet if still indistinguishable. + `VetController` over `PetValidator` over `PetTypeFormatter` over service + over model over util over repository. +5. Within repository scope, prefer non-query-adjacent cleanup over JDBC helper + extraction, then JDBC helper extraction over JPA query-adjacent cleanup. +6. Prefer lexical file path. +7. Prefer lexical method name. +8. Stop with an ambiguity packet if still indistinguishable. ## Candidate Packet From 5994c0ac37a670dd476bdebba1e922df011d15cc Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 22:01:46 +0200 Subject: [PATCH 36/65] Refactor PetClinic JDBC visit foreign key branch --- .../petclinic/repository/jdbc/JdbcPetVisitExtractor.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetVisitExtractor.java b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetVisitExtractor.java index 2d787aa80..404278345 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetVisitExtractor.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetVisitExtractor.java @@ -41,9 +41,8 @@ protected Integer mapPrimaryKey(ResultSet rs) throws SQLException { protected Integer mapForeignKey(ResultSet rs) throws SQLException { if (rs.getObject("visits.pet_id") == null) { return null; - } else { - return rs.getInt("visits.pet_id"); } + return rs.getInt("visits.pet_id"); } @Override From 46b4412cc9b8ec16e63cc8edce2629e25cee15b4 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 22:03:13 +0200 Subject: [PATCH 37/65] Refactor PetClinic JPA owner save branch --- .../petclinic/repository/jpa/JpaOwnerRepositoryImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java index 452aa94f2..b1b83c07a 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java @@ -72,9 +72,9 @@ public Owner findById(int id) { public void save(Owner owner) { if (owner.getId() == null) { this.em.persist(owner); - } else { - this.em.merge(owner); + return; } + this.em.merge(owner); } From 6f1a8adf1bb861206f8766572f98b739ce782c57 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 22:04:50 +0200 Subject: [PATCH 38/65] Refactor PetClinic JPA pet save branch --- .../petclinic/repository/jpa/JpaPetRepositoryImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaPetRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaPetRepositoryImpl.java index 509b6395f..05df3c876 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaPetRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaPetRepositoryImpl.java @@ -57,9 +57,9 @@ public Pet findById(int id) { public void save(Pet pet) { if (pet.getId() == null) { this.em.persist(pet); - } else { - this.em.merge(pet); + return; } + this.em.merge(pet); } } From 2aa8fb428252a3130eb53a73dcfb601ed20c6ebd Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 22:06:04 +0200 Subject: [PATCH 39/65] Refactor PetClinic JPA visit save branch --- .../petclinic/repository/jpa/JpaVisitRepositoryImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVisitRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVisitRepositoryImpl.java index e34b6ae97..19028a5fb 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVisitRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVisitRepositoryImpl.java @@ -48,9 +48,9 @@ public JpaVisitRepositoryImpl(EntityManager em) { public void save(Visit visit) { if (visit.getId() == null) { this.em.persist(visit); - } else { - this.em.merge(visit); + return; } + this.em.merge(visit); } From f533ff59a7668aa9045ee6689523a78eeaa24d61 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 22:07:16 +0200 Subject: [PATCH 40/65] Refactor PetClinic JDBC owner save branch --- .../petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java index 65403977f..7355462bc 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java @@ -124,15 +124,15 @@ public void save(Owner owner) { if (owner.isNew()) { Number newKey = this.insertOwner.executeAndReturnKey(parameterSource); owner.setId(newKey.intValue()); - } else { - this.jdbcClient.sql(""" + return; + } + this.jdbcClient.sql(""" UPDATE owners SET first_name=:firstName, last_name=:lastName, address=:address, city=:city, telephone=:telephone WHERE id=:id """) .paramSource(parameterSource) .update(); - } } public Collection getPetTypes() { From f417e7a365b822353f78e31d27ed374aeb8b3a31 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 22:10:01 +0200 Subject: [PATCH 41/65] Refactor PetClinic JDBC pet save branch --- .../petclinic/repository/jdbc/JdbcPetRepositoryImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java index 166f75476..991ddf8ee 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java @@ -90,8 +90,9 @@ public void save(Pet pet) { Number newKey = this.insertPet.executeAndReturnKey( createPetParameterSource(pet)); pet.setId(newKey.intValue()); - } else { - this.jdbcClient + return; + } + this.jdbcClient .sql(""" UPDATE pets SET name=:name, birth_date=:birth_date, type_id=:type_id, owner_id=:owner_id @@ -99,7 +100,6 @@ public void save(Pet pet) { """) .paramSource(createPetParameterSource(pet)) .update(); - } } /** From a2af860258645037ef97b2d59c2a80b57dd21e76 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 22:11:36 +0200 Subject: [PATCH 42/65] Refactor PetClinic JDBC visit save branch --- .../repository/jdbc/JdbcVisitRepositoryImpl.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java index 17d81507b..58cc91d35 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java @@ -54,13 +54,12 @@ public JdbcVisitRepositoryImpl(DataSource dataSource, JdbcClient jdbcClient) { @Override public void save(Visit visit) { - if (visit.isNew()) { - Number newKey = this.insertVisit.executeAndReturnKey( - createVisitParameterSource(visit)); - visit.setId(newKey.intValue()); - } else { + if (!visit.isNew()) { throw new UnsupportedOperationException("Visit update not supported"); } + Number newKey = this.insertVisit.executeAndReturnKey( + createVisitParameterSource(visit)); + visit.setId(newKey.intValue()); } From 4e4c7eba92c80d35e8244f8526828680f0a25922 Mon Sep 17 00:00:00 2001 From: formatunitedandreas-code Date: Sun, 12 Jul 2026 22:46:07 +0200 Subject: [PATCH 43/65] Refactor PetClinic low-risk controller and service cleanup PR #5\nbranch threshold-governed-refactor-demo-3\nlocal validation: Maven test BUILD SUCCESS\nCI: all visible checks passed\nnon-claims: no upstream interaction, no release, no deploy, no public readiness/correctness/security/compliance claim --- ...lity-refactor-batch-invocation-template.md | 116 +++++ ...ous-readability-refactor-batch-lease-v1.md | 485 ++++++++++++++++++ .../samples/petclinic/model/Owner.java | 12 +- .../jdbc/JdbcOwnerRepositoryImpl.java | 6 +- .../jdbc/JdbcPetRepositoryImpl.java | 6 +- .../jdbc/JdbcPetVisitExtractor.java | 3 +- .../jdbc/JdbcVisitRepositoryImpl.java | 9 +- .../jpa/JpaOwnerRepositoryImpl.java | 4 +- .../repository/jpa/JpaPetRepositoryImpl.java | 4 +- .../jpa/JpaVisitRepositoryImpl.java | 4 +- .../petclinic/web/OwnerController.java | 61 ++- .../samples/petclinic/web/PetController.java | 55 +- .../petclinic/web/PetTypeFormatter.java | 6 +- .../samples/petclinic/web/PetValidator.java | 23 +- .../samples/petclinic/web/VetController.java | 8 +- .../petclinic/web/VisitController.java | 38 +- 16 files changed, 761 insertions(+), 79 deletions(-) create mode 100644 docs/leases/petclinic-autonomous-readability-refactor-batch-invocation-template.md create mode 100644 docs/leases/petclinic-autonomous-readability-refactor-batch-lease-v1.md diff --git a/docs/leases/petclinic-autonomous-readability-refactor-batch-invocation-template.md b/docs/leases/petclinic-autonomous-readability-refactor-batch-invocation-template.md new file mode 100644 index 000000000..7db4f4d8d --- /dev/null +++ b/docs/leases/petclinic-autonomous-readability-refactor-batch-invocation-template.md @@ -0,0 +1,116 @@ +# PetClinic Autonomous Readability Refactor Batch Invocation Template + +Use this template to start a governed autonomous refactor batch from the current +repository state. + +```yaml +startLease: petclinic-autonomous-readability-refactor-batch-lease-v1 + +repository: C:\dev\spring-framework-petclinic +branch: +startHead: +initialWorktree: clean + +budget: + maxCandidatesThisRun: 10 + maxCommitsThisRun: 10 + maxFilesPerCandidate: 1 + maxChangedLinesPerCandidate: 80 + maxNewPrivateMethodsPerCandidate: 2 + maxRepairAttemptsPerCandidate: 1 + +scope: + primaryAllowedPaths: + - src/main/java/org/springframework/samples/petclinic/web/**/*.java + - src/main/java/org/springframework/samples/petclinic/service/**/*.java + optionalAllowedPaths: + - src/main/java/org/springframework/samples/petclinic/model/**/*.java + - src/main/java/org/springframework/samples/petclinic/util/**/*.java + - src/main/java/org/springframework/samples/petclinic/repository/**/*.java + repositoryScopeRules: + - readability-only changes + - no repository interface signature changes + - no SQL/HQL/JPQL string changes + - no query parameter name, value, or order changes + - no row mapper, result extractor, association, or persistence behavior changes + - no transaction or cache annotation changes + - no exception type or exception message changes + +validation: + fullMavenTestRequiredPerCandidate: true + commands: + - git diff --name-only + - git diff --check + - $env:JAVA_HOME='C:\Program Files\Java\jdk-17'; .\mvnw.cmd test + +authorization: + allowed: + - inspect repository state + - discover and rank candidates + - admit one candidate at a time + - edit only the admitted file + - exact-revert current candidate on validation failure + - one bounded repair attempt per candidate + - one local commit per successful candidate + - continue until budget exhausted or no candidate qualifies + forbidden: + - push + - PR creation or update + - upstream interaction + - fetch or pull + - force push + - merge + - release + - deploy + - pom.xml or dependency changes + - src/test changes + - src/main/resources changes + - src/main/webapp changes + - request mapping/view/model/validation/repository/persistence behavior changes + - SQL/HQL/JPQL changes + - repository interface changes + +stopConditions: + - branch mismatch + - start HEAD mismatch + - dirty worktree before candidate + - unexpected changed path + - no candidate score >= 75 + - ambiguity after tie-breaks + - behavior preservation cannot be argued mechanically + - changed-line or helper-method budget exceeded + - git diff --check fails + - Maven test fails after one bounded repair + - exact revert fails + - commit fails + - worktree not clean after commit + - maxCandidatesThisRun reached + - maxCommitsThisRun reached + +finalReport: + include: + - processed candidates + - rejected candidate summary + - commits created + - final HEAD + - final git status + - tests run and results + - stopped reason + - remaining likely candidates + - explicit non-claims +``` + +Example invocation: + +```text +Start lease petclinic-autonomous-readability-refactor-batch-lease-v1. + +Repository: C:\dev\spring-framework-petclinic +Branch: threshold-governed-refactor-demo-3 +Start HEAD: +Initial worktree: clean + +Run autonomously until budget exhaustion or ready_no_candidates. +Do not push, open or update PRs, merge, release, deploy, fetch, pull, or +interact with upstream. +``` diff --git a/docs/leases/petclinic-autonomous-readability-refactor-batch-lease-v1.md b/docs/leases/petclinic-autonomous-readability-refactor-batch-lease-v1.md new file mode 100644 index 000000000..e6bc2eded --- /dev/null +++ b/docs/leases/petclinic-autonomous-readability-refactor-batch-lease-v1.md @@ -0,0 +1,485 @@ +# PetClinic Autonomous Readability Refactor Batch Lease v1 + +This lease defines the governed workflow for autonomous, low-to-medium-risk +readability refactoring in the owned Spring PetClinic repository. + +It is intentionally split into authority phases. A refactor batch lease may +create local commits, but it must not publish, open or update a PR, merge, +release, deploy, or interact with upstream unless a later phase explicitly +authorizes that action. + +## Lease Identity + +```yaml +leaseName: petclinic-autonomous-readability-refactor-batch-lease-v1 +repository: C:\dev\spring-framework-petclinic +defaultBranchPattern: threshold-governed-refactor-demo* +requiredInitialWorktree: clean +``` + +## Purpose + +Continue PetClinic refactoring autonomously over a bounded candidate pool. +The lease is designed for mechanical readability work only: + +- private helper extraction +- duplicate literal constant extraction +- redundant local variable simplification +- small controller branch readability decomposition +- validator and formatter readability cleanup +- repository/JDBC/JPA readability cleanup without query or persistence behavior + changes + +The lease does not authorize behavior changes or public claims about readiness, +correctness, security, or compliance. + +## Authority Levels + +```yaml +allowedRiskClasses: + - R0_READ_ONLY + - R1_READOUT + - R2_REVERSIBLE_LOCAL_MUTATION + - R3_LOCAL_COMMIT + +notAuthorizedByThisLease: + - R4_OWNED_REPO_BRANCH_PUSH + - R4_OWNED_REPO_DRAFT_PR_CREATE_OR_UPDATE + - R4_CI_STABILIZATION + - R5_PR_MERGE + - release + - deploy +``` + +## Batch Budget + +```yaml +maxCandidatesThisRun: 10 +maxCommitsThisRun: 10 +maxFilesPerCandidate: 1 +maxChangedLinesPerCandidate: 80 +maxNewPrivateMethodsPerCandidate: 2 +maxRepairAttemptsPerCandidate: 1 +fullMavenTestRequiredPerCandidate: true +requireCleanWorktreeBeforeEachCandidate: true +requireCleanWorktreeAfterEachCommit: true +stopOnUnexpectedChangedPath: true +``` + +## Allowed Paths + +```yaml +allowedPaths: + - src/main/java/org/springframework/samples/petclinic/web/**/*.java + - src/main/java/org/springframework/samples/petclinic/service/**/*.java + - src/main/java/org/springframework/samples/petclinic/model/**/*.java + - src/main/java/org/springframework/samples/petclinic/util/**/*.java + - src/main/java/org/springframework/samples/petclinic/repository/**/*.java + +forbiddenPaths: + - pom.xml + - src/test/** + - src/main/resources/** + - src/main/webapp/** + - .github/** + - target/** + - hidden/config files +``` + +The default starting scope should be `web` and `service`. `model`, `util`, and +`repository` may be admitted only when the candidate is mechanically local and +covered by the same validation protocol. + +Repository work is restricted to readability-only changes in the existing JDBC, +JPA, and repository adapter classes. It must not change SQL/HQL/JPQL strings, +query parameters, result mapping semantics, save/merge/persist behavior, +transaction behavior, exception behavior, or repository interface contracts. + +## Candidate Classes + +### 1. Private Helper Extraction For Readability + +Allowed when all conditions hold: + +- same class only +- helper method is private +- at most two new private methods +- extracted code is contiguous or logically identical to an existing local block +- no public API change +- no request mapping change +- no validation annotation change +- no repository call change +- no persistence behavior change +- no returned view name or model attribute name change + +### 2. Duplicate Literal Constant Extraction + +Allowed when all conditions hold: + +- same class only +- new constant is `private static final String` +- literal is repeated in the same file +- literal represents a view name, redirect, model key, route path, validation + field, or validation code +- replacement preserves the exact string value +- at most two constants per candidate + +### 3. Redundant Local Variable Simplification + +Allowed when all conditions hold: + +- local variable is assigned exactly once +- initializer is side-effect free or already required immediately +- simplification does not reduce readability +- no behavior change + +### 4. Controller Branch Readability Decomposition + +Allowed when all conditions hold: + +- only splits existing branch bodies into private methods +- returned view names and redirects are identical strings or constants +- model keys are identical +- BindingResult behavior is identical +- branch order is unchanged +- no request mapping, HTTP method, validation, repository, or persistence change + +### 5. Validator Or Formatter Readability Cleanup + +Allowed when all conditions hold: + +- same class only +- no supported type change +- no validation field/code/message change +- no parse/print semantics change +- no exception message change unless explicitly authorized + +### 6. Repository/JDBC/JPA Readability Cleanup + +Allowed when all conditions hold: + +- same class only +- helper method is private +- at most two new private methods +- no repository interface signature change +- no SQL/HQL/JPQL string change +- no query parameter name, value, or order change +- no row mapper, result extractor, or entity association semantic change +- no persist, merge, insert, generated-key, or update behavior change +- no transaction annotation or cache annotation change +- no exception type or exception message change +- existing repository call order is preserved unless the candidate is purely a + local helper extraction that calls the same operations in the same order + +Examples that may qualify: + +- remove an unnecessary `else` after an immediate `return` +- extract a contiguous loop body into a private helper when query and mapping + behavior remain identical +- extract a local private helper for setting already-loaded associations on + already-loaded entities + +Examples that must be rejected: + +- rewriting SQL/HQL/JPQL +- changing joins, ordering, filters, aliases, or selected columns +- changing mapper classes or result extractor contracts +- changing save/update support or generated-key handling +- changing repository interfaces + +### 7. Micro-Format Cleanup + +Allowed only inside a method already admitted for one of the candidate classes +above. Broad file formatting is not authorized. + +## Explicitly Forbidden Changes + +```yaml +forbiddenChanges: + - request mapping path changes + - HTTP method changes + - validation annotation semantic changes + - repository query semantic changes + - persistence behavior changes + - transaction behavior changes unless separately authorized + - cache annotation changes + - returned view name changes + - model attribute name changes + - dependency or plugin updates + - pom.xml edits + - test edits + - resource edits + - broad formatting + - feature work +``` + +## Discovery Protocol + +Before each candidate: + +```powershell +git status -sb +git rev-parse HEAD +git branch --show-current +git diff --name-only +``` + +Required: + +- worktree is clean +- branch matches the lease invocation +- changed path list is empty + +Discovery must scan only allowed paths. Exclude candidates already committed in +the current branch lineage unless the new candidate is a strictly local +continuation in a different method. + +## Scoring + +```yaml +positive: + singleFile: 30 + noBehaviorChangeExpected: 30 + fullMavenTestAvailable: 20 + existingControllerOrServiceCoverage: 10 + existingRepositoryCoverage: 10 + privateHelperImprovesReadability: 15 + duplicateLiteralConstantRemovesRepetition: 10 + redundantLocalVariableSimplification: 12 + repositoryReadabilityWithoutSemanticChange: 10 + diffUnder40ChangedLines: 10 + diff40To80ChangedLines: 5 + noPublicSignatureChange: 10 + +negative: + controllerBranchDecomposition: -10 + repositoryAdapterTouch: -10 + jdbcOrJpaQueryAdjacentCode: -15 + helperNameRequiresSemanticJudgment: -10 + changesMoreThanOneBranch: -15 + extractionCrossesNonContiguousLogic: -20 + +reject: + - changes public method signature + - changes request mapping, view, model, validation, repository, or persistence behavior + - requires test changes + - requires pom.xml or dependency changes + - requires upstream, push, PR, merge, release, or deploy +``` + +Admission threshold: + +```yaml +minimumScore: 75 +``` + +## Tie-Break Rules + +Apply in order: + +1. Prefer smaller diff. +2. Prefer one private helper over two. +3. Prefer already covered controller or service methods. +4. Prefer `OwnerController` over `PetController` over `VisitController` over + `VetController` over `PetValidator` over `PetTypeFormatter` over service + over model over util over repository. +5. Within repository scope, prefer non-query-adjacent cleanup over JDBC helper + extraction, then JDBC helper extraction over JPA query-adjacent cleanup. +6. Prefer lexical file path. +7. Prefer lexical method name. +8. Stop with an ambiguity packet if still indistinguishable. + +## Candidate Packet + +For every admitted candidate, record: + +```yaml +candidateId: +candidateClass: +baseHead: +allowedFile: +beforeSha256: +expectedDiffSummary: +score: +tieBreakReason: +behaviorPreservation: +validationCommands: + - git diff --name-only + - git diff --check + - $env:JAVA_HOME='C:\Program Files\Java\jdk-17'; .\mvnw.cmd test +revertStrategy: exact restore of admitted file before commit +nonClaims: + - no push + - no PR + - no upstream interaction + - no merge + - no release + - no deploy + - no readiness/correctness/security/compliance claim +``` + +## Patch Protocol + +Rules: + +- edit only the admitted file +- stay within changed-line and helper-method budgets +- preserve all forbidden behavior-change checks +- use repository style +- avoid unrelated formatting + +## Validation Protocol + +Run after every candidate patch: + +```powershell +git diff --name-only +git diff --check +$env:JAVA_HOME='C:\Program Files\Java\jdk-17'; .\mvnw.cmd test +``` + +Required: + +- changed paths contain only the admitted file +- `git diff --check` passes +- full Maven test passes + +## Failure And Repair Protocol + +If validation fails: + +1. Determine whether the failure is clearly caused by the current candidate. +2. If yes, perform at most one bounded repair inside the admitted file. +3. Rerun `git diff --check` and full Maven test. +4. If still failing, exact-revert the admitted file. +5. Require clean worktree after revert. +6. Stop with a failure receipt. + +If the failure requires forbidden files, upstream interaction, dependency +changes, test changes, force push, merge, release, or deploy, stop without +repair. + +## Commit Protocol + +After successful validation: + +```powershell +Get-FileHash -Algorithm SHA256 +git add +git commit -m "Refactor PetClinic " +git status -sb +git log -1 --oneline +git rev-parse HEAD +``` + +Required: + +- only admitted file staged +- exactly one local commit per successful candidate +- worktree clean after commit + +## Batch Continuation + +Continue to the next candidate only when: + +- budget remains +- worktree is clean +- previous commit succeeded +- another candidate reaches the admission threshold + +Stop with `ready_no_candidates` when no candidate qualifies. + +## Terminal Receipt + +At batch end, report: + +```yaml +processedCandidates: [] +commitsCreated: [] +finalHead: +finalGitStatus: +testsRun: +stoppedReason: +remainingLikelyCandidates: [] +nonClaims: + - no push + - no PR + - no upstream interaction + - no fetch/pull + - no pom.xml/dependency change + - no src/test change + - no request mapping/view/model/validation/repository/persistence behavior change + - no merge/release/deploy + - no public readiness/correctness/security/compliance claim +``` + +## Downstream Authority Phases + +The following leases must be separate invocations. + +### Owned Branch Publish And Draft PR Lease + +Purpose: + +- run final `git diff --check` +- run full Maven test +- push the current branch to owned origin +- create or update a draft PR in the owned repository only + +Not allowed: + +- upstream interaction +- force push +- ready-for-review +- reviewer request +- merge +- release +- deploy + +### Draft PR CI Stabilization Lease + +Purpose: + +- inspect existing owned draft PR +- read CI status +- if green, stop +- if pending, stop +- if failing, perform at most bounded branch-local repairs inside allowed paths + +Not allowed: + +- new feature work +- broad refactoring +- dependency or test changes +- ready-for-review +- reviewer request +- merge +- release +- deploy + +### Owned Repo PR Merge Lease + +Purpose: + +- merge exactly one owned-repository PR using the authorized merge method + +Preconditions: + +- worktree clean +- PR open +- PR not draft +- PR head matches required SHA +- PR targets owned repository, not third-party upstream +- no conflicts +- all required checks pass +- full local Maven test passes + +Not allowed: + +- branch protection bypass +- force push +- code changes +- tag creation +- release +- deploy +- upstream interaction diff --git a/src/main/java/org/springframework/samples/petclinic/model/Owner.java b/src/main/java/org/springframework/samples/petclinic/model/Owner.java index e662aeaa3..f6e54c207 100644 --- a/src/main/java/org/springframework/samples/petclinic/model/Owner.java +++ b/src/main/java/org/springframework/samples/petclinic/model/Owner.java @@ -125,17 +125,17 @@ public Pet getPet(String name) { public Pet getPet(String name, boolean ignoreNew) { name = name.toLowerCase(); for (Pet pet : getPetsInternal()) { - if (!ignoreNew || !pet.isNew()) { - String compName = pet.getName(); - compName = compName.toLowerCase(); - if (compName.equals(name)) { - return pet; - } + if (isMatchingPet(pet, name, ignoreNew)) { + return pet; } } return null; } + private boolean isMatchingPet(Pet pet, String name, boolean ignoreNew) { + return (!ignoreNew || !pet.isNew()) && pet.getName().toLowerCase().equals(name); + } + @Override public String toString() { return new ToStringCreator(this) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java index 65403977f..7355462bc 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java @@ -124,15 +124,15 @@ public void save(Owner owner) { if (owner.isNew()) { Number newKey = this.insertOwner.executeAndReturnKey(parameterSource); owner.setId(newKey.intValue()); - } else { - this.jdbcClient.sql(""" + return; + } + this.jdbcClient.sql(""" UPDATE owners SET first_name=:firstName, last_name=:lastName, address=:address, city=:city, telephone=:telephone WHERE id=:id """) .paramSource(parameterSource) .update(); - } } public Collection getPetTypes() { diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java index 166f75476..991ddf8ee 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java @@ -90,8 +90,9 @@ public void save(Pet pet) { Number newKey = this.insertPet.executeAndReturnKey( createPetParameterSource(pet)); pet.setId(newKey.intValue()); - } else { - this.jdbcClient + return; + } + this.jdbcClient .sql(""" UPDATE pets SET name=:name, birth_date=:birth_date, type_id=:type_id, owner_id=:owner_id @@ -99,7 +100,6 @@ public void save(Pet pet) { """) .paramSource(createPetParameterSource(pet)) .update(); - } } /** diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetVisitExtractor.java b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetVisitExtractor.java index 2d787aa80..404278345 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetVisitExtractor.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetVisitExtractor.java @@ -41,9 +41,8 @@ protected Integer mapPrimaryKey(ResultSet rs) throws SQLException { protected Integer mapForeignKey(ResultSet rs) throws SQLException { if (rs.getObject("visits.pet_id") == null) { return null; - } else { - return rs.getInt("visits.pet_id"); } + return rs.getInt("visits.pet_id"); } @Override diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java index 17d81507b..58cc91d35 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java @@ -54,13 +54,12 @@ public JdbcVisitRepositoryImpl(DataSource dataSource, JdbcClient jdbcClient) { @Override public void save(Visit visit) { - if (visit.isNew()) { - Number newKey = this.insertVisit.executeAndReturnKey( - createVisitParameterSource(visit)); - visit.setId(newKey.intValue()); - } else { + if (!visit.isNew()) { throw new UnsupportedOperationException("Visit update not supported"); } + Number newKey = this.insertVisit.executeAndReturnKey( + createVisitParameterSource(visit)); + visit.setId(newKey.intValue()); } diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java index 452aa94f2..b1b83c07a 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java @@ -72,9 +72,9 @@ public Owner findById(int id) { public void save(Owner owner) { if (owner.getId() == null) { this.em.persist(owner); - } else { - this.em.merge(owner); + return; } + this.em.merge(owner); } diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaPetRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaPetRepositoryImpl.java index 509b6395f..05df3c876 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaPetRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaPetRepositoryImpl.java @@ -57,9 +57,9 @@ public Pet findById(int id) { public void save(Pet pet) { if (pet.getId() == null) { this.em.persist(pet); - } else { - this.em.merge(pet); + return; } + this.em.merge(pet); } } diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVisitRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVisitRepositoryImpl.java index e34b6ae97..19028a5fb 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVisitRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVisitRepositoryImpl.java @@ -48,9 +48,9 @@ public JpaVisitRepositoryImpl(EntityManager em) { public void save(Visit visit) { if (visit.getId() == null) { this.em.persist(visit); - } else { - this.em.merge(visit); + return; } + this.em.merge(visit); } diff --git a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java index ebe8f2e62..3ae45f731 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java @@ -40,7 +40,14 @@ public class OwnerController { private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm"; private static final String VIEWS_OWNER_FIND_OWNERS = "owners/findOwners"; + private static final String VIEWS_OWNER_LIST = "owners/ownersList"; + private static final String VIEWS_OWNER_DETAILS = "owners/ownerDetails"; private static final String MODEL_ATTRIBUTE_OWNER = "owner"; + private static final String MODEL_ATTRIBUTE_SELECTIONS = "selections"; + private static final String OWNER_EDIT_PATH = "/owners/{ownerId}/edit"; + private static final String OWNER_NEW_PATH = "/owners/new"; + private static final String REDIRECT_TO_OWNERS = "redirect:/owners/"; + private static final String REDIRECT_TO_OWNER = "redirect:/owners/{ownerId}"; private final ClinicService clinicService; public OwnerController(ClinicService clinicService) { @@ -52,36 +59,37 @@ public void setAllowedFields(WebDataBinder dataBinder) { dataBinder.setDisallowedFields("id"); } - @GetMapping(value = "/owners/new") + @GetMapping(value = OWNER_NEW_PATH) public String initCreationForm(Map model) { - model.put(MODEL_ATTRIBUTE_OWNER, new Owner()); + initializeOwnerModel(model); return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; } - @PostMapping(value = "/owners/new") + @PostMapping(value = OWNER_NEW_PATH) public String processCreationForm(@Valid Owner owner, BindingResult result) { if (result.hasErrors()) { return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; } this.clinicService.saveOwner(owner); - return "redirect:/owners/" + owner.getId(); + return buildOwnerRedirect(owner.getId()); } @GetMapping(value = "/owners/find") public String initFindForm(Map model) { - model.put(MODEL_ATTRIBUTE_OWNER, new Owner()); + initializeOwnerModel(model); return VIEWS_OWNER_FIND_OWNERS; } + private void initializeOwnerModel(Map model) { + model.put(MODEL_ATTRIBUTE_OWNER, new Owner()); + } + @GetMapping(value = "/owners") public String processFindForm(Owner owner, BindingResult result, Map model) { + normalizeLastName(owner); // allow parameterless GET request for /owners to return all records - if (owner.getLastName() == null) { - owner.setLastName(""); // empty string signifies broadest possible search - } - // find owners by last name Collection results = this.clinicService.findOwnerByLastName(owner.getLastName()); if (results.isEmpty()) { @@ -93,27 +101,42 @@ public String processFindForm(Owner owner, BindingResult result, Map results) { - return "redirect:/owners/" + results.iterator().next().getId(); + return buildOwnerRedirect(results.iterator().next().getId()); + } + + private String buildOwnerRedirect(Integer ownerId) { + return REDIRECT_TO_OWNERS + ownerId; } private String handleMultipleOwners(Map model, Collection results) { - model.put("selections", results); - return "owners/ownersList"; + model.put(MODEL_ATTRIBUTE_SELECTIONS, results); + return VIEWS_OWNER_LIST; } - @GetMapping(value = "/owners/{ownerId}/edit") + @GetMapping(value = OWNER_EDIT_PATH) public String initUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model model) { - model.addAttribute(this.clinicService.findOwnerById(ownerId)); + addOwnerToModel(model, ownerId); return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; } - @PostMapping(value = "/owners/{ownerId}/edit") + private void addOwnerToModel(Model model, int ownerId) { + model.addAttribute(MODEL_ATTRIBUTE_OWNER, this.clinicService.findOwnerById(ownerId)); + } + + @PostMapping(value = OWNER_EDIT_PATH) public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result, @PathVariable("ownerId") int ownerId) { if (result.hasErrors()) { return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; @@ -121,7 +144,7 @@ public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result, @ owner.setId(ownerId); this.clinicService.saveOwner(owner); - return "redirect:/owners/{ownerId}"; + return REDIRECT_TO_OWNER; } /** @@ -132,7 +155,11 @@ public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result, @ */ @GetMapping("/owners/{ownerId}") public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) { - return new ModelAndView("owners/ownerDetails").addObject(this.clinicService.findOwnerById(ownerId)); + return buildOwnerDetailsView(ownerId); + } + + private ModelAndView buildOwnerDetailsView(int ownerId) { + return new ModelAndView(VIEWS_OWNER_DETAILS).addObject(this.clinicService.findOwnerById(ownerId)); } } diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetController.java b/src/main/java/org/springframework/samples/petclinic/web/PetController.java index ec6f3a78c..fc2f98900 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetController.java @@ -41,6 +41,9 @@ public class PetController { private static final String VIEWS_PETS_CREATE_OR_UPDATE_FORM = "pets/createOrUpdatePetForm"; private static final String MODEL_ATTRIBUTE_PET = "pet"; + private static final String PET_EDIT_PATH = "/pets/{petId}/edit"; + private static final String PET_NEW_PATH = "/pets/new"; + private static final String MODEL_ATTRIBUTE_OWNER = "owner"; private static final String VIEW_REDIRECT_OWNERS = "redirect:/owners/{ownerId}"; private final ClinicService clinicService; @@ -53,61 +56,71 @@ public Collection populatePetTypes() { return this.clinicService.findPetTypes(); } - @ModelAttribute("owner") + @ModelAttribute(MODEL_ATTRIBUTE_OWNER) public Owner findOwner(@PathVariable("ownerId") int ownerId) { return this.clinicService.findOwnerById(ownerId); } - @InitBinder("owner") + @InitBinder(MODEL_ATTRIBUTE_OWNER) public void initOwnerBinder(WebDataBinder dataBinder) { dataBinder.setDisallowedFields("id"); } - @InitBinder("pet") + @InitBinder(MODEL_ATTRIBUTE_PET) public void initPetBinder(WebDataBinder dataBinder) { dataBinder.setValidator(new PetValidator()); } - @GetMapping(value = "/pets/new") + @GetMapping(value = PET_NEW_PATH) public String initCreationForm(Owner owner, ModelMap model) { + addPetToModel(owner, model); + return VIEWS_PETS_CREATE_OR_UPDATE_FORM; + } + + private void addPetToModel(Owner owner, ModelMap model) { Pet pet = new Pet(); owner.addPet(pet); model.put(MODEL_ATTRIBUTE_PET, pet); - return VIEWS_PETS_CREATE_OR_UPDATE_FORM; } - @PostMapping(value = "/pets/new") + @PostMapping(value = PET_NEW_PATH) public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result, ModelMap model) { - if (hasDuplicatePetName(owner, pet)) { - result.rejectValue("name", "duplicate", "already exists"); - } - if (result.hasErrors()) { - return showPetForm(model, pet); - } - - owner.addPet(pet); - this.clinicService.savePet(pet); - return VIEW_REDIRECT_OWNERS; + return savePetFormResult(owner, pet, result, model, hasDuplicatePetName(owner, pet)); } private boolean hasDuplicatePetName(Owner owner, Pet pet) { return StringUtils.hasLength(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null; } - @GetMapping(value = "/pets/{petId}/edit") + @GetMapping(value = PET_EDIT_PATH) public String initUpdateForm(@PathVariable("petId") int petId, ModelMap model) { - model.put(MODEL_ATTRIBUTE_PET, this.clinicService.findPetById(petId)); + addPetToModelForUpdate(petId, model); return VIEWS_PETS_CREATE_OR_UPDATE_FORM; } - @PostMapping(value = "/pets/{petId}/edit") + private void addPetToModelForUpdate(int petId, ModelMap model) { + model.put(MODEL_ATTRIBUTE_PET, this.clinicService.findPetById(petId)); + } + + @PostMapping(value = PET_EDIT_PATH) public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owner, ModelMap model) { + return savePetFormResult(owner, pet, result, model, false); + } + + private void savePetForOwner(Owner owner, Pet pet) { + owner.addPet(pet); + this.clinicService.savePet(pet); + } + + private String savePetFormResult(Owner owner, Pet pet, BindingResult result, ModelMap model, boolean duplicate) { + if (duplicate) { + result.rejectValue("name", "duplicate", "already exists"); + } if (result.hasErrors()) { return showPetForm(model, pet); } - owner.addPet(pet); - this.clinicService.savePet(pet); + savePetForOwner(owner, pet); return VIEW_REDIRECT_OWNERS; } diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetTypeFormatter.java b/src/main/java/org/springframework/samples/petclinic/web/PetTypeFormatter.java index 1550c698d..c7e85101b 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetTypeFormatter.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetTypeFormatter.java @@ -54,11 +54,15 @@ public String print(PetType petType, Locale locale) { @Override public PetType parse(String text, Locale locale) throws ParseException { for (PetType type : this.clinicService.findPetTypes()) { - if (type.getName().equals(text)) { + if (matchesName(type, text)) { return type; } } throw new ParseException("type not found: " + text, 0); } + private boolean matchesName(PetType type, String text) { + return type.getName().equals(text); + } + } diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java b/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java index b5d510220..fa531ef89 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java @@ -34,26 +34,37 @@ public class PetValidator implements Validator { private static final String REQUIRED = "required"; + private static final String FIELD_NAME = "name"; + private static final String FIELD_TYPE = "type"; + private static final String FIELD_BIRTH_DATE = "birthDate"; @Override public void validate(Object obj, Errors errors) { Pet pet = (Pet) obj; - // name validation + validateName(errors, pet); + validateRequiredFieldsForNewPet(errors, pet); + } + + private void validateName(Errors errors, Pet pet) { if (!StringUtils.hasLength(pet.getName())) { - errors.rejectValue("name", REQUIRED, REQUIRED); + rejectRequiredField(errors, FIELD_NAME); } + } - // type validation + private void validateRequiredFieldsForNewPet(Errors errors, Pet pet) { if (pet.isNew() && pet.getType() == null) { - errors.rejectValue("type", REQUIRED, REQUIRED); + rejectRequiredField(errors, FIELD_TYPE); } - // birth date validation if (pet.getBirthDate() == null) { - errors.rejectValue("birthDate", REQUIRED, REQUIRED); + rejectRequiredField(errors, FIELD_BIRTH_DATE); } } + private void rejectRequiredField(Errors errors, String fieldName) { + errors.rejectValue(fieldName, REQUIRED, REQUIRED); + } + /** * This Validator validates *just* Pet instances */ diff --git a/src/main/java/org/springframework/samples/petclinic/web/VetController.java b/src/main/java/org/springframework/samples/petclinic/web/VetController.java index 0429211c9..cfe8b4000 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/VetController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/VetController.java @@ -56,13 +56,13 @@ private void addVetsToModel(Map model) { @GetMapping(value = "/vets.json", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public Vets showJsonVetList() { - return getVets(); + return getVetsForResponse(); } @GetMapping(value = "/vets.xml", produces = MediaType.APPLICATION_XML_VALUE) @ResponseBody public Vets showXmlVetList() { - return getVets(); + return getVetsForResponse(); } private Vets getVets() { @@ -73,4 +73,8 @@ private Vets getVets() { return vets; } + private Vets getVetsForResponse() { + return getVets(); + } + } diff --git a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java index e770532ab..b54720132 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java @@ -37,6 +37,10 @@ public class VisitController { private static final String VIEWS_VISIT_FORM = "pets/createOrUpdateVisitForm"; + private static final String MODEL_ATTRIBUTE_VISITS = "visits"; + private static final String VISIT_NEW_PATH = "/owners/{ownerId}/pets/{petId}/visits/new"; + private static final String REDIRECT_TO_VISIT_OWNER = "redirect:/owners/{ownerId}"; + private static final String VIEWS_VISIT_LIST = "visitList"; private final ClinicService clinicService; public VisitController(ClinicService clinicService) { @@ -60,32 +64,52 @@ public void setAllowedFields(WebDataBinder dataBinder) { */ @ModelAttribute("visit") public Visit loadPetWithVisit(@PathVariable("petId") int petId) { + return createVisitForPet(petId); + } + + private Visit createVisitForPet(int petId) { Visit visit = new Visit(); this.clinicService.findPetById(petId).addVisit(visit); return visit; } // Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is called - @GetMapping(value = "/owners/{ownerId}/pets/{petId}/visits/new") + @GetMapping(value = VISIT_NEW_PATH) public String initNewVisitForm() { - return VIEWS_VISIT_FORM; + return visitFormView(); } // Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is called - @PostMapping(value = "/owners/{ownerId}/pets/{petId}/visits/new") + @PostMapping(value = VISIT_NEW_PATH) public String processNewVisitForm(@Valid Visit visit, BindingResult result) { + return handleVisitSubmission(visit, result); + } + + private String handleVisitSubmission(Visit visit, BindingResult result) { if (result.hasErrors()) { - return VIEWS_VISIT_FORM; + return visitFormView(); } + saveVisit(visit); + return REDIRECT_TO_VISIT_OWNER; + } + + private String visitFormView() { + return VIEWS_VISIT_FORM; + } + + private void saveVisit(Visit visit) { this.clinicService.saveVisit(visit); - return "redirect:/owners/{ownerId}"; } @GetMapping(value = "/owners/{ownerId}/pets/{petId}/visits") public String showVisits(@PathVariable int petId, Map model) { - model.put("visits", this.clinicService.findPetById(petId).getVisits()); - return "visitList"; + addVisitsToModel(petId, model); + return VIEWS_VISIT_LIST; + } + + private void addVisitsToModel(int petId, Map model) { + model.put(MODEL_ATTRIBUTE_VISITS, this.clinicService.findPetById(petId).getVisits()); } } From acbf5914bd2fdf9af3a7394fcef8eae487f296ea Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 22:49:36 +0200 Subject: [PATCH 44/65] Refactor PetClinic JPA owner query readability --- .../repository/jpa/JpaOwnerRepositoryImpl.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java index b1b83c07a..e449ce9ef 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java @@ -18,7 +18,6 @@ import java.util.Collection; import jakarta.persistence.EntityManager; -import jakarta.persistence.Query; import org.springframework.samples.petclinic.model.Owner; import org.springframework.samples.petclinic.repository.OwnerRepository; @@ -53,18 +52,20 @@ public JpaOwnerRepositoryImpl(EntityManager em) { public Collection findByLastName(String lastName) { // using 'join fetch' because a single query should load both owners and pets // using 'left join fetch' because it might happen that an owner does not have pets yet - Query query = this.em.createQuery("SELECT DISTINCT owner FROM Owner owner left join fetch owner.pets WHERE owner.lastName LIKE :lastName"); - query.setParameter("lastName", lastName + "%"); - return query.getResultList(); + return this.em + .createQuery("SELECT DISTINCT owner FROM Owner owner left join fetch owner.pets WHERE owner.lastName LIKE :lastName") + .setParameter("lastName", lastName + "%") + .getResultList(); } @Override public Owner findById(int id) { // using 'join fetch' because a single query should load both owners and pets // using 'left join fetch' because it might happen that an owner does not have pets yet - Query query = this.em.createQuery("SELECT owner FROM Owner owner left join fetch owner.pets WHERE owner.id =:id"); - query.setParameter("id", id); - return (Owner) query.getSingleResult(); + return (Owner) this.em + .createQuery("SELECT owner FROM Owner owner left join fetch owner.pets WHERE owner.id =:id") + .setParameter("id", id) + .getSingleResult(); } From 0d7f81695ed6919a0ce1c6488d671306843976a1 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 22:56:06 +0200 Subject: [PATCH 45/65] Refactor PetClinic JDBC visit association helper --- .../repository/jdbc/JdbcVisitRepositoryImpl.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java index 58cc91d35..3e4c25412 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java @@ -88,11 +88,15 @@ public List findByPetId(Integer petId) { .query(new JdbcVisitRowMapper()) .list(); - for (Visit visit: visits) { - visit.setPet(pet); - } + attachPetToVisits(visits, pet); return visits; } + private void attachPetToVisits(List visits, JdbcPet pet) { + for (Visit visit : visits) { + visit.setPet(pet); + } + } + } From 6c036f767ad647d7a2d6a9eaef79bb7c80b2610c Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 23:07:15 +0200 Subject: [PATCH 46/65] Refactor PetClinic visit list lookup helper --- .../samples/petclinic/web/VisitController.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java index b54720132..47e609c46 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/VisitController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/VisitController.java @@ -16,6 +16,7 @@ package org.springframework.samples.petclinic.web; import java.util.Map; +import java.util.Collection; import jakarta.validation.Valid; @@ -109,7 +110,11 @@ public String showVisits(@PathVariable int petId, Map model) { } private void addVisitsToModel(int petId, Map model) { - model.put(MODEL_ATTRIBUTE_VISITS, this.clinicService.findPetById(petId).getVisits()); + model.put(MODEL_ATTRIBUTE_VISITS, findVisitsForPet(petId)); + } + + private Collection findVisitsForPet(int petId) { + return this.clinicService.findPetById(petId).getVisits(); } } From 39b3fabb205a9f535d98b51254e796f560803928 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 23:09:08 +0200 Subject: [PATCH 47/65] Refactor PetClinic JDBC pet owner lookup helper --- .../jdbc/JdbcPetRepositoryImpl.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java index 991ddf8ee..80221e99b 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java @@ -70,17 +70,7 @@ public List findPetTypes() { @Override public Pet findById(int id) { - int ownerId; - try { - ownerId = this.jdbcClient - .sql("SELECT owner_id FROM pets WHERE id=:id") - .param("id", id) - .query(Integer.class) - .single(); - } catch (EmptyResultDataAccessException ex) { - throw new ObjectRetrievalFailureException(Pet.class, id); - } - Owner owner = this.ownerRepository.findById(ownerId); + Owner owner = loadOwnerForPet(id); return EntityUtils.getById(owner.getPets(), Pet.class, id); } @@ -99,7 +89,21 @@ public void save(Pet pet) { WHERE id=:id """) .paramSource(createPetParameterSource(pet)) - .update(); + .update(); + } + + private Owner loadOwnerForPet(int petId) { + int ownerId; + try { + ownerId = this.jdbcClient + .sql("SELECT owner_id FROM pets WHERE id=:id") + .param("id", petId) + .query(Integer.class) + .single(); + } catch (EmptyResultDataAccessException ex) { + throw new ObjectRetrievalFailureException(Pet.class, petId); + } + return this.ownerRepository.findById(ownerId); } /** From 5dfc03a2dc283a8cf1b8cce8b6501f26b4babbe3 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 23:13:20 +0200 Subject: [PATCH 48/65] Refactor PetClinic owner search helper --- .../samples/petclinic/web/OwnerController.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java index 3ae45f731..db43691aa 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java @@ -91,7 +91,7 @@ public String processFindForm(Owner owner, BindingResult result, Map results = this.clinicService.findOwnerByLastName(owner.getLastName()); + Collection results = findMatchingOwners(owner); if (results.isEmpty()) { return handleNoOwners(result); } @@ -108,6 +108,10 @@ private void normalizeLastName(Owner owner) { } } + private Collection findMatchingOwners(Owner owner) { + return this.clinicService.findOwnerByLastName(owner.getLastName()); + } + private String handleNoOwners(BindingResult result) { result.rejectValue("lastName", "notFound", "not found"); return VIEWS_OWNER_FIND_OWNERS; From 1142177319efbf61896466477971e49420a17d6c Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 23:15:53 +0200 Subject: [PATCH 49/65] Refactor PetClinic owner find result dispatch --- .../samples/petclinic/web/OwnerController.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java index db43691aa..e1de303db 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/OwnerController.java @@ -92,6 +92,10 @@ public String processFindForm(Owner owner, BindingResult result, Map results = findMatchingOwners(owner); + return resolveOwnerFindResult(results, result, model); + } + + private String resolveOwnerFindResult(Collection results, BindingResult result, Map model) { if (results.isEmpty()) { return handleNoOwners(result); } From 427bf22f2845ff598a2814d1d27de7b38f963488 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 23:18:25 +0200 Subject: [PATCH 50/65] Refactor PetClinic pet validator predicates --- .../samples/petclinic/web/PetValidator.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java b/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java index fa531ef89..5607717f2 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetValidator.java @@ -46,21 +46,33 @@ public void validate(Object obj, Errors errors) { } private void validateName(Errors errors, Pet pet) { - if (!StringUtils.hasLength(pet.getName())) { + if (isMissingName(pet)) { rejectRequiredField(errors, FIELD_NAME); } } private void validateRequiredFieldsForNewPet(Errors errors, Pet pet) { - if (pet.isNew() && pet.getType() == null) { + if (isMissingTypeForNewPet(pet)) { rejectRequiredField(errors, FIELD_TYPE); } - if (pet.getBirthDate() == null) { + if (isMissingBirthDate(pet)) { rejectRequiredField(errors, FIELD_BIRTH_DATE); } } + private boolean isMissingName(Pet pet) { + return !StringUtils.hasLength(pet.getName()); + } + + private boolean isMissingTypeForNewPet(Pet pet) { + return pet.isNew() && pet.getType() == null; + } + + private boolean isMissingBirthDate(Pet pet) { + return pet.getBirthDate() == null; + } + private void rejectRequiredField(Errors errors, String fieldName) { errors.rejectValue(fieldName, REQUIRED, REQUIRED); } From 5da17d5a058630270b3c87d94757cad289c6d444 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 23:31:56 +0200 Subject: [PATCH 51/65] Refactor PetClinic call monitoring readability --- .../petclinic/util/CallMonitoringAspect.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/util/CallMonitoringAspect.java b/src/main/java/org/springframework/samples/petclinic/util/CallMonitoringAspect.java index e906b8e69..71de4f07a 100644 --- a/src/main/java/org/springframework/samples/petclinic/util/CallMonitoringAspect.java +++ b/src/main/java/org/springframework/samples/petclinic/util/CallMonitoringAspect.java @@ -67,10 +67,18 @@ public int getCallCount() { @ManagedAttribute public long getCallTime() { - if (this.callCount > 0) - return this.accumulatedCallTime / this.callCount; - else - return 0; + if (hasRecordedCalls()) { + return averageCallTime(); + } + return 0; + } + + private boolean hasRecordedCalls() { + return this.callCount > 0; + } + + private long averageCallTime() { + return this.accumulatedCallTime / this.callCount; } From 0a4640a77d9b31ccdbfa08748e9333dbb02372d0 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 23:33:32 +0200 Subject: [PATCH 52/65] Refactor PetClinic visit repository query helper --- .../petclinic/repository/jpa/JpaVisitRepositoryImpl.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVisitRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVisitRepositoryImpl.java index 19028a5fb..dda1091b0 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVisitRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaVisitRepositoryImpl.java @@ -57,9 +57,14 @@ public void save(Visit visit) { @Override @SuppressWarnings("unchecked") public List findByPetId(Integer petId) { + Query query = createFindByPetIdQuery(petId); + return query.getResultList(); + } + + private Query createFindByPetIdQuery(Integer petId) { Query query = this.em.createQuery("SELECT v FROM Visit v where v.pet.id= :id"); query.setParameter("id", petId); - return query.getResultList(); + return query; } } From bd3932a323a908821dd2a3b845d08da6ad7ad32d Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 23:36:35 +0200 Subject: [PATCH 53/65] Refactor PetClinic vet repository specialty loading --- .../jdbc/JdbcVetRepositoryImpl.java | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVetRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVetRepositoryImpl.java index 58239df63..e122c6494 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVetRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVetRepositoryImpl.java @@ -23,8 +23,6 @@ import org.springframework.samples.petclinic.util.EntityUtils; import org.springframework.stereotype.Repository; -import java.sql.ResultSet; -import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -68,22 +66,23 @@ public Collection findAll() { // Build each vet's list of specialties. for (Vet vet : vets) { - final List vetSpecialtiesIds = this.jdbcClient.sql( - "SELECT specialty_id FROM vet_specialties WHERE vet_id=?") - .param(vet.getId()) - .query( - new BeanPropertyRowMapper() { - @Override - public Integer mapRow(ResultSet rs, int row) throws SQLException { - return rs.getInt(1); - } - } - ).list(); - for (int specialtyId : vetSpecialtiesIds) { - Specialty specialty = EntityUtils.getById(specialties, Specialty.class, specialtyId); - vet.addSpecialty(specialty); - } + addSpecialtiesToVet(vet, specialties); } return vets; } + + private void addSpecialtiesToVet(Vet vet, List specialties) { + for (Integer specialtyId : loadSpecialtyIdsByVetId(vet.getId())) { + Specialty specialty = EntityUtils.getById(specialties, Specialty.class, specialtyId); + vet.addSpecialty(specialty); + } + } + + private List loadSpecialtyIdsByVetId(int vetId) { + return this.jdbcClient.sql("SELECT specialty_id FROM vet_specialties WHERE vet_id=?") + .param(vetId) + .query((rs, rowNum) -> rs.getInt("specialty_id")) + .list(); + } + } From cc3333e11765ec9e3973efbed04e83965e8b6daf Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 23:38:06 +0200 Subject: [PATCH 54/65] Refactor PetClinic visit repository loader methods --- .../jdbc/JdbcVisitRepositoryImpl.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java index 3e4c25412..cd98055ab 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcVisitRepositoryImpl.java @@ -76,21 +76,29 @@ private MapSqlParameterSource createVisitParameterSource(Visit visit) { @Override public List findByPetId(Integer petId) { - JdbcPet pet = this.jdbcClient + JdbcPet pet = loadPetForVisit(petId); + + List visits = loadVisitsForPet(petId); + + attachPetToVisits(visits, pet); + + return visits; + } + + private JdbcPet loadPetForVisit(Integer petId) { + return this.jdbcClient .sql("SELECT id, name, birth_date, type_id, owner_id FROM pets WHERE id=:id") .param("id", petId) .query(new JdbcPetRowMapper()) .single(); + } - List visits = this.jdbcClient + private List loadVisitsForPet(Integer petId) { + return this.jdbcClient .sql("SELECT id as visit_id, visit_date, description FROM visits WHERE pet_id=:id") .param("id", petId) .query(new JdbcVisitRowMapper()) .list(); - - attachPetToVisits(visits, pet); - - return visits; } private void attachPetToVisits(List visits, JdbcPet pet) { From 6162f91460bae799428fa8fd4e2dac51ea837714 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 23:40:32 +0200 Subject: [PATCH 55/65] Refactor PetClinic owner repository query helpers --- .../repository/jpa/JpaOwnerRepositoryImpl.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java index e449ce9ef..1c528339b 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jpa/JpaOwnerRepositoryImpl.java @@ -52,16 +52,25 @@ public JpaOwnerRepositoryImpl(EntityManager em) { public Collection findByLastName(String lastName) { // using 'join fetch' because a single query should load both owners and pets // using 'left join fetch' because it might happen that an owner does not have pets yet - return this.em - .createQuery("SELECT DISTINCT owner FROM Owner owner left join fetch owner.pets WHERE owner.lastName LIKE :lastName") - .setParameter("lastName", lastName + "%") - .getResultList(); + return getOwnersByLastNamePrefix(lastName); } @Override public Owner findById(int id) { // using 'join fetch' because a single query should load both owners and pets // using 'left join fetch' because it might happen that an owner does not have pets yet + return findOwnerById(id); + } + + @SuppressWarnings("unchecked") + private Collection getOwnersByLastNamePrefix(String lastName) { + return this.em + .createQuery("SELECT DISTINCT owner FROM Owner owner left join fetch owner.pets WHERE owner.lastName LIKE :lastName") + .setParameter("lastName", lastName + "%") + .getResultList(); + } + + private Owner findOwnerById(int id) { return (Owner) this.em .createQuery("SELECT owner FROM Owner owner left join fetch owner.pets WHERE owner.id =:id") .setParameter("id", id) From d5b328026c5da0c34c67d0518c7b35935c0ea1f3 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 23:41:59 +0200 Subject: [PATCH 56/65] Refactor PetClinic JDBC owner repository helpers --- .../jdbc/JdbcOwnerRepositoryImpl.java | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java index 7355462bc..81a8830a6 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java @@ -69,14 +69,7 @@ public JdbcOwnerRepositoryImpl(DataSource dataSource, JdbcClient jdbcClient) { */ @Override public Collection findByLastName(String lastName) { - List owners = this.jdbcClient.sql(""" - SELECT id, first_name, last_name, address, city, telephone - FROM owners - WHERE last_name like :lastName - """) - .param("lastName", lastName + "%") - .query(BeanPropertyRowMapper.newInstance(Owner.class)) - .list(); + List owners = findOwnersByLastNamePrefix(lastName); loadOwnersPetsAndVisits(owners); return owners; } @@ -87,9 +80,25 @@ public Collection findByLastName(String lastName) { */ @Override public Owner findById(int id) { - Owner owner; + Owner owner = findOwnerById(id); + loadPetsAndVisits(owner); + return owner; + } + + private List findOwnersByLastNamePrefix(String lastName) { + return this.jdbcClient.sql(""" + SELECT id, first_name, last_name, address, city, telephone + FROM owners + WHERE last_name like :lastName + """) + .param("lastName", lastName + "%") + .query(BeanPropertyRowMapper.newInstance(Owner.class)) + .list(); + } + + private Owner findOwnerById(int id) { try { - owner = this.jdbcClient.sql(""" + return this.jdbcClient.sql(""" SELECT id, first_name, last_name, address, city, telephone FROM owners WHERE id = :id """) @@ -99,8 +108,6 @@ public Owner findById(int id) { } catch (EmptyResultDataAccessException ex) { throw new ObjectRetrievalFailureException(Owner.class, id); } - loadPetsAndVisits(owner); - return owner; } public void loadPetsAndVisits(final Owner owner) { From 5b3de4f28c4b79b031c68764ab317978d6fc2442 Mon Sep 17 00:00:00 2001 From: Elvis Date: Sun, 12 Jul 2026 23:43:19 +0200 Subject: [PATCH 57/65] Refactor PetClinic JDBC pet repository owner-id lookup --- .../petclinic/repository/jdbc/JdbcPetRepositoryImpl.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java index 80221e99b..6d47696a8 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java @@ -93,9 +93,13 @@ public void save(Pet pet) { } private Owner loadOwnerForPet(int petId) { - int ownerId; + int ownerId = findOwnerIdForPet(petId); + return this.ownerRepository.findById(ownerId); + } + + private int findOwnerIdForPet(int petId) { try { - ownerId = this.jdbcClient + return this.jdbcClient .sql("SELECT owner_id FROM pets WHERE id=:id") .param("id", petId) .query(Integer.class) @@ -103,7 +107,6 @@ private Owner loadOwnerForPet(int petId) { } catch (EmptyResultDataAccessException ex) { throw new ObjectRetrievalFailureException(Pet.class, petId); } - return this.ownerRepository.findById(ownerId); } /** From ed30c54b77e377e0a2fb1b19859e3c53f9418ac5 Mon Sep 17 00:00:00 2001 From: Elvis Date: Mon, 13 Jul 2026 07:06:12 +0200 Subject: [PATCH 58/65] Refactor PetClinic pet visit sorting comparator --- .../java/org/springframework/samples/petclinic/model/Pet.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/samples/petclinic/model/Pet.java b/src/main/java/org/springframework/samples/petclinic/model/Pet.java index 562620473..8725c913c 100644 --- a/src/main/java/org/springframework/samples/petclinic/model/Pet.java +++ b/src/main/java/org/springframework/samples/petclinic/model/Pet.java @@ -45,6 +45,8 @@ @Table(name = "pets") public class Pet extends NamedEntity { + private static final Comparator VISIT_DATE_DESCENDING = Comparator.comparing(Visit::getDate).reversed(); + @Column(name = "birth_date") @DateTimeFormat(pattern = "yyyy/MM/dd") private LocalDate birthDate; @@ -98,7 +100,7 @@ protected void setVisitsInternal(Set visits) { public List getVisits() { List sortedVisits = new ArrayList<>(getVisitsInternal()); - sortedVisits.sort(Comparator.comparing(Visit::getDate).reversed()); + sortedVisits.sort(VISIT_DATE_DESCENDING); return Collections.unmodifiableList(sortedVisits); } From b9f17831ea2466c66a02489d273c24c3cd83ceca Mon Sep 17 00:00:00 2001 From: Elvis Date: Mon, 13 Jul 2026 07:09:59 +0200 Subject: [PATCH 59/65] Refactor PetClinic owner pet sorting comparator --- .../org/springframework/samples/petclinic/model/Owner.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/samples/petclinic/model/Owner.java b/src/main/java/org/springframework/samples/petclinic/model/Owner.java index f6e54c207..d51a493aa 100644 --- a/src/main/java/org/springframework/samples/petclinic/model/Owner.java +++ b/src/main/java/org/springframework/samples/petclinic/model/Owner.java @@ -43,6 +43,10 @@ @Entity @Table(name = "owners") public class Owner extends Person { + + private static final Comparator PET_NAME_COMPARATOR = + Comparator.comparing(Pet::getName, String.CASE_INSENSITIVE_ORDER); + @Column(name = "address") @NotEmpty private String address; @@ -97,7 +101,7 @@ protected void setPetsInternal(Set pets) { public List getPets() { List sortedPets = new ArrayList<>(getPetsInternal()); - sortedPets.sort(Comparator.comparing(Pet::getName, String.CASE_INSENSITIVE_ORDER)); + sortedPets.sort(PET_NAME_COMPARATOR); return Collections.unmodifiableList(sortedPets); } From 7a3ad83932b6e630e3c0503db3abfe63369389a1 Mon Sep 17 00:00:00 2001 From: Elvis Date: Mon, 13 Jul 2026 07:11:41 +0200 Subject: [PATCH 60/65] Refactor PetClinic vet specialty sorting comparator --- .../org/springframework/samples/petclinic/model/Vet.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/samples/petclinic/model/Vet.java b/src/main/java/org/springframework/samples/petclinic/model/Vet.java index a8af064b3..c98acbb6a 100644 --- a/src/main/java/org/springframework/samples/petclinic/model/Vet.java +++ b/src/main/java/org/springframework/samples/petclinic/model/Vet.java @@ -43,6 +43,9 @@ @Table(name = "vets") public class Vet extends Person { + private static final Comparator SPECIALTY_NAME_COMPARATOR = + Comparator.comparing(Specialty::getName, String.CASE_INSENSITIVE_ORDER); + @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"), inverseJoinColumns = @JoinColumn(name = "specialty_id")) @@ -62,7 +65,7 @@ protected void setSpecialtiesInternal(Set specialties) { @XmlElement public List getSpecialties() { List sortedSpecs = new ArrayList<>(getSpecialtiesInternal()); - sortedSpecs.sort(Comparator.comparing(Specialty::getName, String.CASE_INSENSITIVE_ORDER)); + sortedSpecs.sort(SPECIALTY_NAME_COMPARATOR); return Collections.unmodifiableList(sortedSpecs); } From ada98aead3327c06b3cb091aaa893cb894755dea Mon Sep 17 00:00:00 2001 From: Elvis Date: Mon, 13 Jul 2026 07:18:11 +0200 Subject: [PATCH 61/65] Refactor PetClinic JDBC owner pets loading helper --- .../repository/jdbc/JdbcOwnerRepositoryImpl.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java index 81a8830a6..565dcd245 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java @@ -111,13 +111,21 @@ private Owner findOwnerById(int id) { } public void loadPetsAndVisits(final Owner owner) { - final List pets = this.jdbcClient.sql(""" + final List pets = findPetsForOwner(owner.getId()); + attachPetsToOwner(owner, pets); + } + + private List findPetsForOwner(int ownerId) { + return this.jdbcClient.sql(""" SELECT pets.id, name, birth_date, type_id, owner_id, visits.id as visit_id, visit_date, description, pet_id FROM pets LEFT OUTER JOIN visits ON pets.id = pet_id WHERE owner_id=:id ORDER BY pet_id """) - .param("id", owner.getId()) + .param("id", ownerId) .query(new JdbcPetVisitExtractor()); + } + + private void attachPetsToOwner(Owner owner, List pets) { Collection petTypes = getPetTypes(); for (JdbcPet pet : pets) { pet.setType(EntityUtils.getById(petTypes, PetType.class, pet.getTypeId())); From 7bcc5b636130ae5b0abfa6fa4c56c223cd33e648 Mon Sep 17 00:00:00 2001 From: Elvis Date: Mon, 13 Jul 2026 07:19:29 +0200 Subject: [PATCH 62/65] Refactor PetClinic entity lookup matcher --- .../springframework/samples/petclinic/util/EntityUtils.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/samples/petclinic/util/EntityUtils.java b/src/main/java/org/springframework/samples/petclinic/util/EntityUtils.java index 78e13793c..b18358b9c 100644 --- a/src/main/java/org/springframework/samples/petclinic/util/EntityUtils.java +++ b/src/main/java/org/springframework/samples/petclinic/util/EntityUtils.java @@ -47,11 +47,15 @@ private EntityUtils() { */ public static T getById(Collection entities, Class entityClass, int entityId) { for (T entity : entities) { - if (entity.getId() == entityId && entityClass.isInstance(entity)) { + if (isMatchingEntity(entity, entityClass, entityId)) { return entity; } } throw new ObjectRetrievalFailureException(entityClass, entityId); } + private static boolean isMatchingEntity(T entity, Class entityClass, int entityId) { + return entity.getId() == entityId && entityClass.isInstance(entity); + } + } From 33ae6f45d27dd24aede1d244110ee1dc487ac50d Mon Sep 17 00:00:00 2001 From: Elvis Date: Mon, 13 Jul 2026 07:21:41 +0200 Subject: [PATCH 63/65] Refactor PetClinic one-to-many result size validation --- .../repository/jdbc/OneToManyResultSetExtractor.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/OneToManyResultSetExtractor.java b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/OneToManyResultSetExtractor.java index 549112f49..56865ce9b 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/OneToManyResultSetExtractor.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/OneToManyResultSetExtractor.java @@ -112,6 +112,11 @@ public List extractData(ResultSet rs) throws SQLException { } results.add(root); } + validateExpectedResults(results); + return results; + } + + private void validateExpectedResults(List results) { if ((expectedResults == ExpectedResults.ONE_AND_ONLY_ONE || expectedResults == ExpectedResults.ONE_OR_NONE) && results.size() > 1) { throw new IncorrectResultSizeDataAccessException(1, results.size()); @@ -120,7 +125,6 @@ public List extractData(ResultSet rs) throws SQLException { results.isEmpty()) { throw new IncorrectResultSizeDataAccessException(1, 0); } - return results; } /** From f2a1fe63f11e118528c3f715fbc5cb6343cfac48 Mon Sep 17 00:00:00 2001 From: Elvis Date: Mon, 13 Jul 2026 07:25:04 +0200 Subject: [PATCH 64/65] Refactor PetClinic jdbc owner lookup readability --- .../petclinic/repository/jdbc/JdbcPetRepositoryImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java index 6d47696a8..5fb13542e 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcPetRepositoryImpl.java @@ -93,8 +93,7 @@ public void save(Pet pet) { } private Owner loadOwnerForPet(int petId) { - int ownerId = findOwnerIdForPet(petId); - return this.ownerRepository.findById(ownerId); + return this.ownerRepository.findById(findOwnerIdForPet(petId)); } private int findOwnerIdForPet(int petId) { From 5e984a7f87930c4260074efe823abbe7ebb433b8 Mon Sep 17 00:00:00 2001 From: Elvis Date: Mon, 13 Jul 2026 07:26:14 +0200 Subject: [PATCH 65/65] Refactor PetClinic loadPetsAndVisits readability --- .../petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java index 565dcd245..4b471f3fe 100644 --- a/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/repository/jdbc/JdbcOwnerRepositoryImpl.java @@ -111,8 +111,7 @@ private Owner findOwnerById(int id) { } public void loadPetsAndVisits(final Owner owner) { - final List pets = findPetsForOwner(owner.getId()); - attachPetsToOwner(owner, pets); + attachPetsToOwner(owner, findPetsForOwner(owner.getId())); } private List findPetsForOwner(int ownerId) {