I had previously modelled a ManyToMany relation in JPA but now I had to make it OneToMany and ManyToOne. I had some input from a friend but now I am unable to save the join table correctly.
These are my entities:
Label:
public class Label implements Serializable {
@Id
@GeneratedValue()
@Column(updatable = false, nullable = false, columnDefinition = "BINARY(16)")
private UUID id;
// BEFORE
// @OneToMany
@OneToMany(mappedBy = "technology")
private Set<TechnologyLabel> technology;
//getters setters equal hashcode
}
Technology:
public class Technology implements Serializable {
@Id
@GeneratedValue
@Column(updatable = false, nullable = false, columnDefinition = "BINARY(16)")
private UUID uuid;
@Column(nullable = false, length = 30)
private String technologyName;
// BEFORE
// @OneToMany
@OneToMany(mappedBy = "label")
private Set<TechnologyLabel> labels;
//getters setters equal hashcode
}
TechnologyLabel:
public class TechnologyLabel {
@Id
@EqualsAndHashCode.Include
UUID technologyId;
@Id
@EqualsAndHashCode.Include
@Column(name = "label__id")
UUID labelId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(insertable = false, updatable = false)
Technology technology;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(insertable = false, updatable = false)
Label label;
//getters setters equal hashcode
@Data
public static class PK implements Serializable {
UUID technologyId;
UUID labelId;
}
}
My issues is in how to save TechnologyLabel
. What I do is:
- create
label
and save it in DB - create
technology
and save it in DB - create
technologyLabel
addlabel
andtechnology
and save it in DB
an example of step 3 is:
var technologyLabelList = new ArrayList<TechnologyLabel>();
for (var t : technologies) {
for (var l : labels) {
var technologyLabel = new TechnologyLabel();
technologyLabel.setTechnology(t);
technologyLabel.setLabelId(l.getId());
technologyLabel.setTechnologyId(t.getUuid());
technologyLabel.setLabel(l);
technologyLabelList.add(technologyLabel);
}
}
technologyLabelRepository.saveAll(technologyLabelList);
like this my program runs and the table is created. The issue I have is that I have to manually set the labelId
and technologyId
. The resulting TechnologyLabel table
looks weird to me though as it has the IDs twice and once empty.
I had to set the name of the column for the labelId
to label__id
or I would get the following error:
Caused by: org.hibernate.DuplicateMappingException: Table [technology_label] contains physical column name [label_id] referred to by multiple logical column names: [label_id], [labelId]
CodePudding user response:
Try something like this:
@Entity
@IdClass(TechnologyLabelPk.class)
public class TechnologyLabel {
@Id
UUID technologyId;
@Id
@Column(name = "label_id")//pick the column name you really want to use!
UUID labelId;
@ManyToOne(fetch = FetchType.LAZY)
@MapsId("technologyId")//tells JPA to set your technologyId property from this relationship
Technology technology;
@ManyToOne(fetch = FetchType.LAZY)
@MapsId("labelId")//tells JPA to set your labelId property from this relationship
Label label;
}
public class TechnologyLabelPk implements Serializable {
public UUID technologyId;
public UUID labelId;
}
This allows you to use the ID properties directly without having to fetch the relationships if you want, or in queries without forcing joins, but still have JPA manage everything just with the object reference. So you don't need to ever have to set the TechnologyLabel.labelId or technologyId properties yourself. It is a versatile model where if you are creating Labels and Technology instances, you can do so without having to get their Pks assigned before creating TechnologyLabel relations between them.
Downside is that you cannot create the object by setting those fields - you must set the TechnologyLabel.label and technology references.
CodePudding user response:
This easiest way to do it is a manyToMany relation :
public class Technology {
@Id
@GeneratedValue
@Column(updatable = false, nullable = false)
private long uuid;
@Column(nullable = false, length = 30)
private String technologyName;
@ManyToMany(cascade = CascadeType.ALL)
private Set<Label> labels = new HashSet<>();
public Set<Label> getLabels() {
return labels;
}
public void setTechnologyName(String technologyName) {
this.technologyName = technologyName;
}
public void add(Label label) {
labels.add(label);
label.getTechnologies().add(this);
}
}
public class Label {
@Id
@GeneratedValue()
@Column(updatable = false, nullable = false)
private long id;
@ManyToMany(cascade = CascadeType.ALL, mappedBy = "labels")
private Set<Technology> technologies = new HashSet<>();
public Set<Technology> getTechnologies() {
return technologies;
}
public void add(Technology t) {
technologies.add(t);
t.getLabels().add(this);
}
}
The relation is define on both sides but the mappedBy
on the technologies of the Label
class is needed to make sure it is the opposite direction of the one in the Technology
class.
This create automatically an extra table to manage the manytomany relation.