I think I have a bad setup for my hibernate database. I have Citizen
entities who have one to many relationships with WeeklyCare
entities. Below is the relevant code.
Citizen:
@Entity
@Table(name = "citizens")
public class Citizen {
@Id
@Size(max = 10, min = 10, message = "CPR must be exactly 10 characters")
private String cpr;
@OneToMany()
@JoinColumn(name = "cpr")
private List<WeeklyCare> weeklyCare;
}
WeeklyCare:
@Entity
public class WeeklyCare {
@EmbeddedId
private WeeklyCareIdentifier weeklyCareIdentifier;
}
WeeklyCareIdentifier:
@Embeddable
public class WeeklyCareIdentifier implements Serializable {
@NotNull
@Size(max = 10, min = 10, message = "CPR must tbe exactly 10 characters")
private String cpr;
@NotNull
private Integer week;
@NotNull
private Integer year;
}
I have some problems when I want to save data to the database:
- I can't save
WeeklyCare
first, because it requires aCitizen
. - When I send the citizens to my backend, the objects contain a list of
WeeklyCare
. When I try to save the citizens, it gives me this error:Unable to find Application.Models.WeeklyCare with id Application.Models.WeeklyCareIdentifier@b23ef67b
I can solve the problem by clearing the list of WeeklyCare
on the Citizen
before saving it, and then saving the list of WeeklyCare
after, but that feels like a terrible way to do it.
I guess I want hibernate to ignore the list of WeeklyCare
when it saves a Citizen
, but acknowledge it when it fetches a Citizen
. Is this possible? Or is there an even better way to do it? Thanks.
- I can't save WeeklyCare first, because it requires a Citizen.
You have the "cpr" identifier used in two entities:
- it's the primary Id for
Citizen
- it's part of the composite Id for
WeeklyCare
You could, theoretically speaking, create a list of WeeklyCare
(not with the way it is modeled now though) and later update the associations of each WeeklyCare
to Citizen
.
- When I send the citizens to my backend, the objects contain a list of WeeklyCare. When I try to save the citizens, it gives me this error: Unable to find Application.Models.WeeklyCare with id Application.Models.WeeklyCareIdentifier@b23ef67b
The best way to map One-To-Many association is bidirectional. This will also save you from some unnecessary queries Hibernate is generating when using @OneToMany with @JoinColumn only.
1) Remove cpr from WeeklyCareIdentifier
class (and probably rename the class).
@Embeddable
public class WeeklyCareIdentifier implements Serializable {
@NotNull
private Integer week;
@NotNull
private Integer year;
//constructors, getters, setters
}
2) Remove the composite @EmbeddedId in favor of Long id field:
@Entity
public class WeeklyCare {
@Id
@GeneratedValue
private Long id;
@Embedded
private WeeklyCareIdentifier weeklyCareIdentifier;
//constructors, getters, setters
}
3) Move to bidirectional mapping:
@Entity
@Table(name = "citizens")
public class Citizen {
@Id
@Size(max = 10, min = 10, message = "CPR must be exactly 10 characters")
private String cpr;
@OneToMany(
mappedBy = "citizen",
cascade = CascadeType.ALL, //cascade all operations to children
orphanRemoval = true //remove orphaned WeeklyCare if they don't have associated Citizen
)
private List<WeeklyCare> weeklyCares = new ArrayList<>(); //init collections to avoid nulls
//constructors, getters, setters
//add utility methods to manipulate the relationship
public void addWeeklyCare(WeeklyCare weeklyCare) {
weeklyCares.add(weeklyCare);
weeklyCare.setCitizen(this);
}
public void removeWeeklyCare(WeeklyCare weeklyCare) {
weeklyCares.remove(weeklyCare);
weeklyCare.setCitizen(null);
}
}
and:
@Entity
public class WeeklyCare {
@Id
@GeneratedValue
private Long id;
//having reference to the citizen entity from WeeklyCare
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "citizen_cpr")
private Citizen citizen;
@Embedded
private WeeklyCareIdentifier weeklyCareIdentifier;
//constructors, getters, setters
}
I would also recommend to use Long ids for the entities, even if the cpr is unique and so on. Convert the cpr to a normal column and introduce a DB generated ID column which you use in to join with in your internal domain and treat the cpr as a pure user-facing data column.