Veel-op-veel-relatie in PPV

1. Inleiding

In deze zelfstudie zien we meerdere manieren om omgaan met veel-op-veel-relaties met behulp van JPA.

Om de ideeën te presenteren, gebruiken we een model van studenten, cursussen en verschillende relaties daartussen.

Eenvoudigheidshalve laten we in de codevoorbeelden alleen de attributen en de JPA-configuratie zien die betrekking hebben op de veel-op-veel-relaties.

2. Basis veel-op-veel

2.1. Een veel-op-veel-relatie modelleren

Een relatie is een verbinding tussen twee soorten entiteiten. In het geval van een veel-op-veel-relatie, kunnen beide partijen betrekking hebben op meerdere instanties van de andere kant.

Merk op dat het mogelijk is dat entiteitstypen een relatie met zichzelf hebben. Als we bijvoorbeeld stambomen modelleren: elk knooppunt is een persoon, dus als we het hebben over de ouder-kindrelatie, zullen beide deelnemers een persoon zijn.

Het maakt echter niet zoveel uit of we het hebben over een relatie tussen enkele of meerdere entiteitstypen. Omdat het gemakkelijker is om na te denken over relaties tussen twee verschillende entiteitstypen, zullen we dat gebruiken om onze cases te illustreren.

Als de studenten bijvoorbeeld de cursussen markeren die ze leuk vinden: een student kan leuk vinden veel cursussen, en veel studenten kunnen dezelfde cursus leuk vinden:

Zoals we weten, kunnen we in RDBMS'en relaties aangaan met externe sleutels. Omdat beide partijen in staat zouden moeten zijn om naar de andere te verwijzen, we moeten een aparte tabel maken voor de externe sleutels:

Zo'n tafel heet een toetreden tot tafel. Merk op dat in een samenvoegtabel de combinatie van de externe sleutels de samengestelde primaire sleutel is.

2.2. Implementatie in JPA

Het modelleren van een veel-op-veel-relatie met POJO's is makkelijk. We zouden omvatten een Verzameling in beide klassen, die de elementen van de anderen bevat.

Daarna moeten we de klas markeren met @Entiteit, en de primaire sleutel met @ID kaart om ze echte PPV-entiteiten te maken.

We moeten ook het relatietype configureren. Vandaar we markeren de collecties met @Veel te veel annotaties:

@Entity class Student {@Id Lange id; @ManyToMany Set likesCourses; // aanvullende eigenschappen // standaard constructors, getters en setters} @ Entity class Course {@Id Long id; @ManyToMany Set likes; // aanvullende eigenschappen // standaard constructors, getters en setters}

Bovendien moeten we configureren hoe de relatie in het RDBMS moet worden gemodelleerd.

Aan de kant van de eigenaar configureren we de relatie, die we voor dit voorbeeld de Leerling klasse.

We kunnen dit doen met de @JoinTable annotatie in het Leerling klasse. We geven de naam van de join-tafel (course_like), en de externe sleutels met de @JoinColumn annotaties. De joinColumn attribuut maakt verbinding met de eigenaarkant van de relatie, en het inverseJoinColumn naar de andere kant:

@ManyToMany @JoinTable (name = "course_like", joinColumns = @JoinColumn (name = "student_id"), inverseJoinColumns = @JoinColumn (name = "course_id")) Set likescourses;

Merk op dat het gebruik van @JoinTable, of zelfs @JoinColumn is niet vereist: JPA genereert de tabel- en kolomnamen voor ons. De strategie die JPA gebruikt, komt echter niet altijd overeen met de naamgevingsconventies die we gebruiken. Vandaar de mogelijkheid om tabel- en kolomnamen te configureren.

Aan de doelzijde hoeven we alleen de naam van het veld op te geven, dat de relatie in kaart brengt. Daarom stellen we de mappedBy attribuut van de @Veel te veel annotatie in het Cursus klasse:

@ManyToMany (mappedBy = "likeCourses") Vind ik leuk;

Merk op dat sinds een veel-op-veel-relatie heeft geen eigenaarszijde in de database, kunnen we de join-tafel configureren in het Cursus class en verwijs ernaar vanuit de Leerling klasse.

3. Veel-op-veel met een samengestelde sleutel

3.1. Relatiekenmerken modelleren

Stel dat we studenten de cursussen willen laten beoordelen. Een student kan een willekeurig aantal cursussen beoordelen en een willekeurig aantal studenten kan dezelfde cursus beoordelen. Daarom is het ook een veel-op-veel-relatie. Wat het een beetje ingewikkelder maakt, is dat er is meer aan de beoordelingsrelatie dan aan het feit dat het bestaat. We moeten de beoordelingsscore opslaan die de student op de cursus heeft gegeven.

Waar kunnen we deze informatie opslaan? We kunnen het niet in de Leerling entiteit, aangezien een student verschillende beoordelingen kan geven aan verschillende cursussen. Evenzo bewaart u het in het Cursus entiteit zou ook geen goede oplossing zijn.

Dit is een situatie waarin de relatie zelf heeft een attribuut.

In dit voorbeeld ziet het koppelen van een attribuut aan een relatie er als volgt uit in een ER-diagram:

We kunnen het bijna op dezelfde manier modelleren als met de eenvoudige veel-op-veel-relatie. Het enige verschil is dat we een nieuw attribuut aan de join-tafel koppelen:

3.2. Een samengestelde sleutel maken in JPA

De implementatie van een eenvoudige veel-op-veel-relatie was vrij eenvoudig. Het enige probleem is dat we op die manier geen eigenschap aan een relatie kunnen toevoegen, omdat we de entiteiten rechtstreeks met elkaar hebben verbonden. Daarom we hadden geen manier om een ​​eigenschap aan de relatie zelf toe te voegen.

Aangezien we DB-attributen toewijzen aan klassevelden in JPA, we moeten een nieuwe entiteitsklasse maken voor de relatie.

Natuurlijk heeft elke PPV-entiteit een primaire sleutel nodig. Omdat onze primaire sleutel een samengestelde sleutel is, moeten we een nieuwe klasse maken, die de verschillende delen van de sleutel bevat:

@Embeddable klas CourseRatingKey implementeert Serializable {@Column (name = "student_id") Lange studentId; @Column (name = "course_id") Lange courseId; // standaard constructors, getters en setters // hashcode en is gelijk aan implementatie}

Merk op dat er enkele zijn sleutelvereisten waaraan een samengestelde sleutelklasse moet voldoen:

  • We moeten het markeren met @Embeddable
  • Het moet worden uitgevoerd java.io.Serializable
  • We moeten zorgen voor een implementatie van het hashcode () en is gelijk aan () methoden
  • Geen van de velden kan zelf een entiteit zijn

3.3. Een samengestelde sleutel gebruiken in JPA

Met behulp van deze samengestelde sleutelklasse kunnen we de entiteitsklasse maken, die de join-tabel modelleert:

@Entity class CourseRating {@EmbeddedId CourseRatingKey id; @ManyToOne @MapsId ("studentId") @JoinColumn (name = "student_id") Student student; @ManyToOne @MapsId ("courseId") @JoinColumn (name = "course_id") Cursuscursus; int beoordeling; // standaard constructeurs, getters en setters}

Deze code lijkt sterk op een reguliere entiteitsimplementatie. We hebben echter enkele belangrijke verschillen:

  • we gebruikten @EmbeddedId, om de primaire sleutel te markeren, wat een exemplaar is van de CourseRatingKey klasse
  • we hebben de leerling en Cursus velden met @MapsId

@MapsId betekent dat we die velden aan een deel van de sleutel koppelen, en het zijn de externe sleutels van een veel-op-een-relatie. We hebben het nodig, omdat we, zoals we hierboven vermeldden, in de samengestelde sleutel geen entiteiten kunnen hebben.

Hierna kunnen we de inverse verwijzingen in het Leerling en Cursus entiteiten zoals voorheen:

klas Student {// ... @OneToMany (mappedBy = "student") Stel beoordelingen in; // ...} class Cursus {// ... @OneToMany (mappedBy = "course") Stel beoordelingen in; // ...}

Merk op dat er een alternatieve manier is om samengestelde sleutels te gebruiken: de @IdClass annotatie.

3.4. Verdere kenmerken

We hebben de relaties met de Leerling en Cursus klassen als @ManyToOne. We zouden dit kunnen doen omdat we met de nieuwe entiteit de veel-op-veel-relatie structureel hebben ontbonden in twee veel-op-een-relaties.

Waarom hebben we dit kunnen doen? Als we de tabellen in het vorige geval nauwkeurig bekijken, kunnen we zien dat deze twee veel-op-een-relaties bevatte. Met andere woorden, er is geen veel-op-veel-relatie in een RDBMS. We noemen de structuren die we maken met samenvoegtabellen veel-op-veel-relaties, omdat we dat modelleren.

Bovendien is het duidelijker als we het hebben over veel-op-veel-relaties, want dat is onze bedoeling. Ondertussen is een join-tafel slechts een implementatiedetail; we geven er niet echt om.

Bovendien heeft deze oplossing een extra functie die we nog niet hebben genoemd. De eenvoudige veel-op-veel-oplossing creëert een relatie tussen twee entiteiten. Daarom kunnen we de relatie niet uitbreiden naar meer entiteiten. In deze oplossing hebben we deze limiet echter niet: we kunnen relaties tussen een willekeurig aantal entiteitstypen modelleren.

Als bijvoorbeeld meerdere docenten een cursus kunnen geven, kunnen studenten beoordelen hoe een specifieke docent een specifieke cursus geeft. Op die manier zou een beoordeling een relatie zijn tussen drie entiteiten: een student, een cursus en een leraar.

4. Veel-op-veel met een nieuwe entiteit

4.1. Relatie-eigenschappen modelleren

Stel dat we studenten willen laten inschrijven voor cursussen. Ook, we moeten het punt opslaan waarop een student zich inschreef voor een specifieke cursus. Daarnaast willen we ook weten welk cijfer ze in de cursus heeft behaald.

In een ideale wereld konden we dit oplossen met de vorige oplossing, toen we een entiteit hadden met een samengestelde sleutel. Onze wereld is echter verre van ideaal en studenten halen niet altijd een cursus bij de eerste poging.

In dit geval zijn er meerdere verbindingen tussen dezelfde student-cursusparen, of meerdere rijen, met hetzelfde student_id-course_id paren. We kunnen het niet modelleren met behulp van een van de vorige oplossingen, omdat alle primaire sleutels uniek moeten zijn. Daarom moeten we een aparte primaire sleutel gebruiken.

Daarom we kunnen een entiteit introduceren, die de kenmerken van de registratie zal bevatten:

In dit geval, de registratie-entiteit vertegenwoordigt de relatie tussen de andere twee entiteiten.

Omdat het een entiteit is, heeft het zijn eigen primaire sleutel.

Merk op dat we in de vorige oplossing een samengestelde primaire sleutel hadden, die we hebben gemaakt op basis van de twee externe sleutels. Nu zullen de twee externe sleutels geen deel uitmaken van de primaire sleutel:

4.2. Implementatie in JPA

Sinds de coure_registration een gewone tabel is geworden, kunnen we een gewone oude JPA-entiteit maken door deze te modelleren:

@Entity class CourseRegistration {@Id Lange id; @ManyToOne @JoinColumn (name = "student_id") Student student; @ManyToOne @JoinColumn (name = "course_id") Cursus; LocalDateTime geregistreerdAt; int rang; // aanvullende eigenschappen // standaard constructeurs, getters en setters}

We moeten ook de relaties in het Leerling en Cursus klassen:

klas Student {// ... @OneToMany (mappedBy = "student") Registraties instellen; // ...} class Cursus {// ... @OneToMany (mappedBy = "courses") Registraties instellen; // ...}

Nogmaals, we hebben de relatie eerder geconfigureerd. Daarom hoeven we JPA alleen te vertellen waar het die configuratie kan vinden.

Merk op dat we deze oplossing zouden kunnen gebruiken om het vorige probleem aan te pakken: studenten beoordelen cursussen. Het voelt echter raar om een ​​speciale primaire sleutel te maken, tenzij dat nodig is. Bovendien heeft het vanuit RDBMS-perspectief weinig zin, aangezien het combineren van de twee externe sleutels een perfecte samengestelde sleutel opleverde. Daarnaast samengestelde sleutel had een duidelijke betekenis: welke entiteiten we verbinden in de relatie.

Anders is de keuze tussen deze twee implementaties vaak gewoon persoonlijke voorkeur.

5. Conclusie

In deze tutorial hebben we gezien wat een veel-op-veel-relatie is en hoe we deze kunnen modelleren in een RDBMS met behulp van JPA.

We hebben drie manieren gezien om het in JPA te modelleren. Alle drie hebben ze verschillende voor- en nadelen als het gaat om:

  • code duidelijkheid
  • DB duidelijkheid
  • mogelijkheid om attributen aan de relatie toe te wijzen
  • hoeveel entiteiten kunnen we koppelen aan de relatie, en
  • ondersteuning voor meerdere verbindingen tussen dezelfde entiteiten

Zoals gewoonlijk zijn de voorbeelden beschikbaar op GitHub.