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;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.validation.constraints.Digits;
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.samples.petclinic.model.Person;
@ -60,8 +57,10 @@ public class Owner extends Person {
@Digits(fraction = 0, integer = 10)
private String telephone;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "ownerId", fetch = FetchType.EAGER)
private Set<Pet> pets;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "owner_id")
@OrderBy("name")
private List<Pet> pets = new ArrayList<>();
public String getAddress() {
return this.address;
@ -87,28 +86,14 @@ public class Owner extends Person {
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() {
List<Pet> sortedPets = new ArrayList<>(getPetsInternal());
PropertyComparator.sort(sortedPets, new MutableSortDefinition("name", true, true));
return Collections.unmodifiableList(sortedPets);
return this.pets;
}
public void addPet(Pet pet) {
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
*/
public Pet getPet(Integer id) {
for (Pet pet : getPetsInternal()) {
for (Pet pet : getPets()) {
if (!pet.isNew()) {
Integer compId = pet.getId();
if (compId.equals(id)) {
@ -144,7 +129,7 @@ public class Owner extends Person {
*/
public Pet getPet(String name, boolean ignoreNew) {
name = name.toLowerCase();
for (Pet pet : getPetsInternal()) {
for (Pet pet : getPets()) {
if (!ignoreNew || !pet.isNew()) {
String compName = pet.getName();
compName = compName == null ? "" : compName.toLowerCase();

View file

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

View file

@ -40,12 +40,8 @@ public class Visit extends BaseEntity {
private LocalDate date;
@NotEmpty
@Column
private String description;
@Column
private Integer petId;
/**
* Creates a new instance of Visit for the current date
*/
@ -69,12 +65,4 @@ public class Visit extends BaseEntity {
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
// called
@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()) {
return "pets/createOrUpdateVisitForm";
}
else {
owner.getPet(visit.getPetId()).addVisit(visit);
owner.getPet(petId).addVisit(visit);
this.owners.save(owner);
return "redirect:/owners/{ownerId}";
}

View file

@ -48,7 +48,7 @@ CREATE TABLE pets (
name VARCHAR(30),
birth_date DATE,
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_types FOREIGN KEY (type_id) REFERENCES types (id);
@ -56,7 +56,7 @@ CREATE INDEX pets_name ON pets (name);
CREATE TABLE visits (
id INTEGER IDENTITY PRIMARY KEY,
pet_id INTEGER NOT NULL,
pet_id INTEGER,
visit_date DATE,
description VARCHAR(255)
);

View file

@ -48,7 +48,7 @@ CREATE TABLE pets (
name VARCHAR(30),
birth_date DATE,
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_types FOREIGN KEY (type_id) REFERENCES types (id);
@ -56,7 +56,7 @@ CREATE INDEX pets_name ON pets (name);
CREATE TABLE visits (
id INTEGER IDENTITY PRIMARY KEY,
pet_id INTEGER NOT NULL,
pet_id INTEGER,
visit_date DATE,
description VARCHAR(255)
);

View file

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

View file

@ -38,14 +38,14 @@ CREATE TABLE IF NOT EXISTS pets (
name TEXT,
birth_date DATE,
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 (owner_id);
CREATE TABLE IF NOT EXISTS visits (
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,
description TEXT
);

View file

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

View file

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