Remove manual id management in child entities

This is reverting a workaround for a Hibernate "feature". There's
no need for the child entities (Pet and Visit) to know about their
parent (foreign key). Hibernate can manage that just fine with a
@JoinColumn. But it needs a nullable foreign key column in the
DB schema. That's the downside. The upside is much less code in
Java.
This commit is contained in:
Dave Syer 2022-01-06 11:18:15 +00:00
parent 43beff91a3
commit b559077f14
10 changed files with 25 additions and 60 deletions

View file

@ -16,22 +16,19 @@
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.FetchType; import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table; import javax.persistence.Table;
import javax.validation.constraints.Digits; import javax.validation.constraints.Digits;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import org.springframework.beans.support.MutableSortDefinition;
import org.springframework.beans.support.PropertyComparator;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
import org.springframework.samples.petclinic.model.Person; import org.springframework.samples.petclinic.model.Person;
@ -60,8 +57,10 @@ public class Owner extends Person {
@Digits(fraction = 0, integer = 10) @Digits(fraction = 0, integer = 10)
private String telephone; private String telephone;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "ownerId", fetch = FetchType.EAGER) @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Pet> pets; @JoinColumn(name = "owner_id")
@OrderBy("name")
private List<Pet> pets = new ArrayList<>();
public String getAddress() { public String getAddress() {
return this.address; return this.address;
@ -87,28 +86,14 @@ public class Owner extends Person {
this.telephone = telephone; this.telephone = telephone;
} }
protected Set<Pet> getPetsInternal() {
if (this.pets == null) {
this.pets = new HashSet<>();
}
return this.pets;
}
protected void setPetsInternal(Set<Pet> pets) {
this.pets = pets;
}
public List<Pet> getPets() { public List<Pet> getPets() {
List<Pet> sortedPets = new ArrayList<>(getPetsInternal()); return this.pets;
PropertyComparator.sort(sortedPets, new MutableSortDefinition("name", true, true));
return Collections.unmodifiableList(sortedPets);
} }
public void addPet(Pet pet) { public void addPet(Pet pet) {
if (pet.isNew()) { if (pet.isNew()) {
getPetsInternal().add(pet); getPets().add(pet);
} }
pet.setOwnerId(getId());
} }
/** /**
@ -126,7 +111,7 @@ public class Owner extends Person {
* @return a pet if pet id is already in use * @return a pet if pet id is already in use
*/ */
public Pet getPet(Integer id) { public Pet getPet(Integer id) {
for (Pet pet : getPetsInternal()) { for (Pet pet : getPets()) {
if (!pet.isNew()) { if (!pet.isNew()) {
Integer compId = pet.getId(); Integer compId = pet.getId();
if (compId.equals(id)) { if (compId.equals(id)) {
@ -144,7 +129,7 @@ public class Owner extends Person {
*/ */
public Pet getPet(String name, boolean ignoreNew) { public Pet getPet(String name, boolean ignoreNew) {
name = name.toLowerCase(); name = name.toLowerCase();
for (Pet pet : getPetsInternal()) { for (Pet pet : getPets()) {
if (!ignoreNew || !pet.isNew()) { if (!ignoreNew || !pet.isNew()) {
String compName = pet.getName(); String compName = pet.getName();
compName = compName == null ? "" : compName.toLowerCase(); compName = compName == null ? "" : compName.toLowerCase();

View file

@ -52,17 +52,11 @@ public class Pet extends NamedEntity {
@JoinColumn(name = "type_id") @JoinColumn(name = "type_id")
private PetType type; private PetType type;
@Column @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Integer ownerId; @JoinColumn(name = "pet_id")
@OneToMany(cascade = CascadeType.ALL, mappedBy = "petId", fetch = FetchType.LAZY)
@OrderBy("visit_date ASC") @OrderBy("visit_date ASC")
private Set<Visit> visits = new LinkedHashSet<>(); private Set<Visit> visits = new LinkedHashSet<>();
public void setOwnerId(Integer ownerId) {
this.ownerId = ownerId;
}
public void setBirthDate(LocalDate birthDate) { public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate; this.birthDate = birthDate;
} }
@ -85,7 +79,6 @@ public class Pet extends NamedEntity {
public void addVisit(Visit visit) { public void addVisit(Visit visit) {
getVisits().add(visit); getVisits().add(visit);
visit.setPetId(this.getId());
} }
} }

View file

@ -40,12 +40,8 @@ public class Visit extends BaseEntity {
private LocalDate date; private LocalDate date;
@NotEmpty @NotEmpty
@Column
private String description; private String description;
@Column
private Integer petId;
/** /**
* Creates a new instance of Visit for the current date * Creates a new instance of Visit for the current date
*/ */
@ -69,12 +65,4 @@ public class Visit extends BaseEntity {
this.description = description; this.description = description;
} }
public Integer getPetId() {
return this.petId;
}
public void setPetId(Integer petId) {
this.petId = petId;
}
} }

View file

@ -78,12 +78,13 @@ class VisitController {
// Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is // Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is
// called // called
@PostMapping("/owners/{ownerId}/pets/{petId}/visits/new") @PostMapping("/owners/{ownerId}/pets/{petId}/visits/new")
public String processNewVisitForm(@ModelAttribute Owner owner, @Valid Visit visit, BindingResult result) { public String processNewVisitForm(@ModelAttribute Owner owner, @PathVariable("petId") int petId, @Valid Visit visit,
BindingResult result) {
if (result.hasErrors()) { if (result.hasErrors()) {
return "pets/createOrUpdateVisitForm"; return "pets/createOrUpdateVisitForm";
} }
else { else {
owner.getPet(visit.getPetId()).addVisit(visit); owner.getPet(petId).addVisit(visit);
this.owners.save(owner); this.owners.save(owner);
return "redirect:/owners/{ownerId}"; return "redirect:/owners/{ownerId}";
} }

View file

@ -48,7 +48,7 @@ CREATE TABLE pets (
name VARCHAR(30), name VARCHAR(30),
birth_date DATE, birth_date DATE,
type_id INTEGER NOT NULL, type_id INTEGER NOT NULL,
owner_id INTEGER NOT NULL owner_id INTEGER
); );
ALTER TABLE pets ADD CONSTRAINT fk_pets_owners FOREIGN KEY (owner_id) REFERENCES owners (id); ALTER TABLE pets ADD CONSTRAINT fk_pets_owners FOREIGN KEY (owner_id) REFERENCES owners (id);
ALTER TABLE pets ADD CONSTRAINT fk_pets_types FOREIGN KEY (type_id) REFERENCES types (id); ALTER TABLE pets ADD CONSTRAINT fk_pets_types FOREIGN KEY (type_id) REFERENCES types (id);
@ -56,7 +56,7 @@ CREATE INDEX pets_name ON pets (name);
CREATE TABLE visits ( CREATE TABLE visits (
id INTEGER IDENTITY PRIMARY KEY, id INTEGER IDENTITY PRIMARY KEY,
pet_id INTEGER NOT NULL, pet_id INTEGER,
visit_date DATE, visit_date DATE,
description VARCHAR(255) description VARCHAR(255)
); );

View file

@ -48,7 +48,7 @@ CREATE TABLE pets (
name VARCHAR(30), name VARCHAR(30),
birth_date DATE, birth_date DATE,
type_id INTEGER NOT NULL, type_id INTEGER NOT NULL,
owner_id INTEGER NOT NULL owner_id INTEGER
); );
ALTER TABLE pets ADD CONSTRAINT fk_pets_owners FOREIGN KEY (owner_id) REFERENCES owners (id); ALTER TABLE pets ADD CONSTRAINT fk_pets_owners FOREIGN KEY (owner_id) REFERENCES owners (id);
ALTER TABLE pets ADD CONSTRAINT fk_pets_types FOREIGN KEY (type_id) REFERENCES types (id); ALTER TABLE pets ADD CONSTRAINT fk_pets_types FOREIGN KEY (type_id) REFERENCES types (id);
@ -56,7 +56,7 @@ CREATE INDEX pets_name ON pets (name);
CREATE TABLE visits ( CREATE TABLE visits (
id INTEGER IDENTITY PRIMARY KEY, id INTEGER IDENTITY PRIMARY KEY,
pet_id INTEGER NOT NULL, pet_id INTEGER,
visit_date DATE, visit_date DATE,
description VARCHAR(255) description VARCHAR(255)
); );

View file

@ -40,7 +40,7 @@ CREATE TABLE IF NOT EXISTS pets (
name VARCHAR(30), name VARCHAR(30),
birth_date DATE, birth_date DATE,
type_id INT(4) UNSIGNED NOT NULL, type_id INT(4) UNSIGNED NOT NULL,
owner_id INT(4) UNSIGNED NOT NULL, owner_id INT(4) UNSIGNED,
INDEX(name), INDEX(name),
FOREIGN KEY (owner_id) REFERENCES owners(id), FOREIGN KEY (owner_id) REFERENCES owners(id),
FOREIGN KEY (type_id) REFERENCES types(id) FOREIGN KEY (type_id) REFERENCES types(id)
@ -48,7 +48,7 @@ CREATE TABLE IF NOT EXISTS pets (
CREATE TABLE IF NOT EXISTS visits ( CREATE TABLE IF NOT EXISTS visits (
id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
pet_id INT(4) UNSIGNED NOT NULL, pet_id INT(4) UNSIGNED,
visit_date DATE, visit_date DATE,
description VARCHAR(255), description VARCHAR(255),
FOREIGN KEY (pet_id) REFERENCES pets(id) FOREIGN KEY (pet_id) REFERENCES pets(id)

View file

@ -38,14 +38,14 @@ CREATE TABLE IF NOT EXISTS pets (
name TEXT, name TEXT,
birth_date DATE, birth_date DATE,
type_id INT NOT NULL REFERENCES types (id), type_id INT NOT NULL REFERENCES types (id),
owner_id INT NOT NULL REFERENCES owners (id) owner_id INT REFERENCES owners (id)
); );
CREATE INDEX ON pets (name); CREATE INDEX ON pets (name);
CREATE INDEX ON pets (owner_id); CREATE INDEX ON pets (owner_id);
CREATE TABLE IF NOT EXISTS visits ( CREATE TABLE IF NOT EXISTS visits (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
pet_id INT NOT NULL REFERENCES pets (id), pet_id INT REFERENCES pets (id),
visit_date DATE, visit_date DATE,
description TEXT description TEXT
); );

View file

@ -75,11 +75,11 @@ class OwnerControllerTests {
Pet max = new Pet(); Pet max = new Pet();
PetType dog = new PetType(); PetType dog = new PetType();
dog.setName("dog"); dog.setName("dog");
max.setId(1);
max.setType(dog); max.setType(dog);
max.setName("Max"); max.setName("Max");
max.setBirthDate(LocalDate.now()); max.setBirthDate(LocalDate.now());
george.setPetsInternal(Collections.singleton(max)); george.addPet(max);
max.setId(1);
return george; return george;
}; };
@ -95,7 +95,6 @@ class OwnerControllerTests {
given(this.owners.findById(TEST_OWNER_ID)).willReturn(george); given(this.owners.findById(TEST_OWNER_ID)).willReturn(george);
Visit visit = new Visit(); Visit visit = new Visit();
visit.setDate(LocalDate.now()); visit.setDate(LocalDate.now());
visit.setPetId(george.getPet("Max").getId());
george.getPet("Max").getVisits().add(visit); george.getPet("Max").getVisits().add(visit);
} }

View file

@ -216,7 +216,6 @@ class ClinicServiceTests {
assertThat(visits).hasSize(2); assertThat(visits).hasSize(2);
Visit[] visitArr = visits.toArray(new Visit[visits.size()]); Visit[] visitArr = visits.toArray(new Visit[visits.size()]);
assertThat(visitArr[0].getDate()).isNotNull(); assertThat(visitArr[0].getDate()).isNotNull();
assertThat(visitArr[0].getPetId()).isEqualTo(7);
} }
} }