Home > Software design >  How to correctly save explicit Many-To-Many realation
How to correctly save explicit Many-To-Many realation

Time:11-11

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:

  1. create label and save it in DB
  2. create technology and save it in DB
  3. create technologyLabel add label and technology 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.

enter image description here

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.

  • Related