Asked:
A while back there was a bug where editing an owner's info made their pets disappear from the page, and pet updates wouldn't save. What was actually causing that, and what should I know about that part of the code before I touch it?
What CIRCUIT did (tool calls, in order):
status{}find_precedent{"query":"editing owner info causes pets to disappear","leg":"Issue"}find_precedent{"query":"pet updates not saving owner edit","leg":"Commit"}inspect{"id":"947"}traverse{"startId":"947","edges":["RESOLVED_BY>","MODIFIES>"],"depth":2,"stopAt":["File"]}inspect{"id":"d381fb658cb435a04e2271ca85bd3e8627a5e7e4","detail":true}inspect{"id":"org/springframework/samples/petclinic/owner/OwnerController#","detail":true}
Answer:
Now I have the full picture. Here's what was actually happening and what you need to know.
The same commit fixed both symptoms — pets disappearing after an owner edit, and pet updates not saving. The commit message names two root causes explicitly:
"Open session in view was switched off accidentally a while ago. Also the mapping changes recently meant that the changes to @Valid model attributes were not being propagated correctly."
This was the more critical one. Without the @ModelAttribute method in each controller, Spring MVC creates a fresh, empty Owner object when the POST form comes in, binds only the fields present in the form onto it (name, address, etc. — not pets), and passes that to the handler.
When owners.save(owner) is then called on an owner with an empty pets list, JPA's CascadeType.ALL on the pets collection faithfully executes that: all pets are deleted.
For the pet update bug: updatePetDetails calls owner.getPet(petId) to find the existing pet in the owner's collection. If owner has no pets loaded, it returns null → falls to owner.addPet(pet) → addPet silently does nothing for existing pets (it has an isNew() guard at Owner.java:98) → the update is lost, and again saving the empty-pets owner wipes everything via cascade.
The fix was adding the @ModelAttribute("owner") methods you see at OwnerController.java:64–70 and PetController.java:66–72. They load the real owner from the DB (with all pets) before Spring MVC binds the form fields, so binding only touches what's in the form and the pets collection stays intact.
At the time, if the pets collection was lazy-loaded, turning OSIV off meant the Hibernate session was closed before the view template could access pets. The current code has FetchType.EAGER on Owner.pets (Owner.java:64), which is why spring.jpa.open-in-view=false in application.properties is safe now. At some point the two were decoupled — OSIV off but pets made EAGER.
@ModelAttribute("owner") in both controllers is load-bearing. It's not boilerplate. If you change it to return a new Owner() or remove it, every save will cascade-delete all pets. The CascadeType.ALL + @JoinColumn pattern at Owner.java:64–67 means the owner is the sole authority over its pets list in the DB.
addPet() only adds new pets (ones without an ID, Owner.java:97–101). It silently ignores existing pets. That's why updatePetDetails (PetController.java:167–181) looks up the pet in the owner's loaded collection first and mutates it in-place — it can't just call addPet on an existing one.
@InitBinder blocks id and *.id from form binding (OwnerController.java:61, PetController.java:90). IDs arrive via URL path variables through @ModelAttribute, not from the form. Don't confuse the two paths.
If you ever switch Owner.pets from EAGER to LAZY, you'll need to either turn OSIV back on or add an explicit join in the repo query — otherwise you're back to the original OSIV half of this bug.