Programovanie

Vytrvalosť v prostredí Java s JPA a Hibernate, časť 2: Vzťahy medzi mnohými

Prvá polovica tohto tutoriálu predstavila základy rozhrania Java Persistence API a ukázala vám, ako nakonfigurovať aplikáciu JPA pomocou Hibernate 5.3.6 a Java 8. Ak ste si prečítali tento tutoriál a preštudovali si jeho ukážkovú aplikáciu, potom poznáte základy modelovanie entít JPA a vzťahov typu dvojice k jedným v JPA. Mali ste tiež skúsenosti s písaním pomenovaných dotazov pomocou jazyka JPA Query Language (JPQL).

V tejto druhej polovici tutoriálu pôjdeme hlbšie k JPA a Hibernate. Dozviete sa, ako modelovať vzájomné vzťahy medzi mnohými Film a SuperHero entít, zriadiť pre tieto entity jednotlivé úložiská a uchovať ich v databáze H2 v pamäti. Dozviete sa tiež viac o úlohe kaskádových operácií v JPA a získate tipy na výber a CascadeType stratégia pre subjekty v databáze. Na záver spojíme fungujúcu aplikáciu, ktorú môžete spustiť vo svojom IDE alebo na príkazovom riadku.

Tento výukový program sa zameriava na základy JPA, ale nezabudnite si prečítať tieto tipy pre Javu, ktoré zavádzajú pokročilejšie témy v JPA:

  • Dedičské vzťahy v JPA a Hibernate
  • Kompozitné kľúče v JPA a Hibernate
stiahnuť Získajte kód Stiahnite si zdrojový kód napríklad pre aplikácie použité v tomto návode. Vytvoril Steven Haines pre JavaWorld.

Vzťahy typu many-to-many v JPA

Vzťahy medzi mnohými definujte entity, pre ktoré môžu mať obe strany vzťahu viac vzájomných odkazov. Pre náš príklad budeme modelovať filmy a superhrdinov. Na rozdiel od príkladu Autori a knihy z 1. časti môže mať film viacerých superhrdinov a superhrdina sa môže objaviť vo viacerých filmoch. Naši superhrdinovia, Ironman a Thor, sa objavujú v dvoch filmoch „The Avengers“ a „Avengers: Infinity War“.

Na modelovanie tohto vzťahu medzi mnohými pomocou JPA budeme potrebovať tri tabuľky:

  • FILM
  • SUPER_HERO
  • SUPERHERO_MOVIES

Obrázok 1 zobrazuje doménový model s tromi tabuľkami.

Steven Haines

Poznač si to SuperHero_Movies je a pripojiť sa k stolu medzi Film a SuperHero stoly. V JPA je spojovacia tabuľka špeciálny druh tabuľky, ktorá uľahčuje vzájomné vzťahy medzi mnohými.

Jednosmerný alebo obojsmerný?

V JPA používame @ManyToMany anotácia na modelovanie vzťahov medzi mnohými. Tento typ vzťahu môže byť jednosmerný alebo obojsmerný:

  • V jednosmerný vzťah iba jedna entita vo vzťahu ukazuje na druhú.
  • V obojsmerný vzťah obidva subjekty ukazujú na seba.

Náš príklad je obojsmerný, čo znamená, že film ukazuje na všetkých svojich superhrdinov a superhrdina ukazuje na všetky svoje filmy. V obojsmernom vzťahu mnoho-veľa jedna entita vlastné vzťah a druhý je mapované na vzťah. Používame mappedBy atribút @ManyToMany anotáciu na vytvorenie tohto mapovania.

Zoznam 1 zobrazuje zdrojový kód súboru SuperHero trieda.

Zoznam 1. SuperHero.java

 balíček com.geekcap.javaworld.jpa.model; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.Table; import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; @Entity @Table (name = "SUPER_HERO") verejná trieda SuperHero {@Id @GeneratedValue súkromné ​​celé číslo; súkromné ​​meno reťazca; @ManyToMany (fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) @JoinTable (name = "SuperHero_Movies", joinColumns = {@JoinColumn (name = "superhero_id")}, inverseJoinColumns = {@JoinColumn) }) private Set films = new HashSet (); public SuperHero () {} public SuperHero (Integer id, String name) {this.id = id; this.name = meno; } public SuperHero (názov reťazca) {this.name = meno; } public Integer getId () {return id; } public void setId (integer id) {this.id = id; } public String getName () {návratové meno; } public void setName (názov reťazca) {this.name = name; } public Set getMovies () {návrat filmov; } @Override public String toString () {return "SuperHero {" + "id =" + id + ", + name +" \ '' + ", + films.stream (). Map (Movie :: getTitle) .collect (Collectors.toList ()) + "\ '' + '}'; }} 

The SuperHero trieda má niekoľko anotácií, ktoré by mali byť známe z 1. časti:

  • @Entity identifikuje SuperHero ako subjekt JPA.
  • @ Tabuľka mapuje SuperHero entita do tabuľky „SUPER_HERO“.

Všimnite si tiež Celé čísloid pole, ktoré určuje, že sa automaticky vygeneruje primárny kľúč tabuľky.

Ďalej sa pozrieme na @ManyToMany a @JoinTable anotácie.

Načítavajú sa stratégie

Vec, ktorú si treba všimnúť v @ManyToMany anotácia je spôsob, akým konfigurujeme aportovacia stratégia, ktoré môžu byť lenivé alebo nedočkavé. V tomto prípade sme nastavili aportovať do EAGER, aby sme pri načítaní a SuperHero z databázy tiež automaticky načítame všetky jej príslušné Films.

Keby sme sa rozhodli vykonať a LENIVÝ namiesto načítania by sme načítali iba každú z nich Film pretože bol konkrétne prístupný. Lenivé načítanie je možné iba vtedy, keď SuperHero je pripojený k EntityManager; inak prístup k filmom o superhrdinovi spôsobí výnimku. Chceme mať prístup k filmom superhrdinov na požiadanie, preto v tomto prípade zvolíme možnosť EAGER aportovacia stratégia.

CascadeType.PERSIST

Kaskádové operácie definovať, ako sa superhrdinovia a ich príslušné filmy uchovávajú do az databázy. Existuje niekoľko konfigurácií kaskádového typu, z ktorých si môžete vybrať, a o nich si povieme viac neskôr v tomto návode. Zatiaľ si všimnite, že sme nastavili kaskáda atribút CascadeType.PERSIST, čo znamená, že keď uložíme superhrdinu, uložia sa aj jeho filmy.

Pripojte sa k stolom

JoinTable je trieda, ktorá uľahčuje vzájomné vzťahy medzi mnohými SuperHero a Film. V tejto triede definujeme tabuľku, ktorá bude ukladať primárne kľúče pre obidve SuperHero a Film subjekty.

Výpis 1 určuje, že bude názov tabuľky SuperHero_Movies. The pripojiť sa k stĺpcu bude superhrdina_ida stĺpec inverzného spojenia bude film_id. The SuperHero entita vlastní vzťah, takže stĺpec spojenia sa vyplní SuperHeroprimárny kľúč. Stĺpec inverzného spojenia potom odkazuje na entitu na druhej strane vzťahu, čo je Film.

Na základe týchto definícií v zozname 1 by sme očakávali vytvorenie novej tabuľky s názvom SuperHero_Movies. Tabuľka bude mať dva stĺpce: superhrdina_id, ktorý odkazuje na id stĺpec SUPERHERO stôl a film_id, ktorý odkazuje na id stĺpec FILM stôl.

Trieda filmu

Zoznam 2 zobrazuje zdrojový kód súboru Film trieda. Pripomeňme, že v obojsmernom vzťahu vlastní jeden subjekt vzťah (v tomto prípade SuperHero), zatiaľ čo druhá je mapovaná do vzťahu. Kód v zozname 2 obsahuje mapovanie vzťahov aplikované na Film trieda.

Zoznam 2. Movie.java

 balíček com.geekcap.javaworld.jpa.model; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToMany; import javax.persistence.Table; import java.util.HashSet; import java.util.Set; @Entity @Table (name = "MOVIE") verejná trieda Film {@Id @GeneratedValue súkromné ​​celé číslo; súkromný názov reťazca; @ManyToMany (mappedBy = "films", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER) private Set superHeroes = new HashSet (); public Movie () {} public Movie (Integer id, String title) {this.id = id; this.title = title; } verejný film (názov reťazca) {this.title = názov; } public Integer getId () {return id; } public void setId (integer id) {this.id = id; } public String getTitle () {návratový názov; } public void setTitle (názov reťazca) {this.title = title; } public Set getSuperHeroes () {return superHeroes; } public void addSuperHero (SuperHero superHero) {superHeroes.add (superHero); superHero.getMovies (). pridať (toto); } @Override public String toString () {return "Movie {" + "id =" + id + ", + title +" \ '' + '}'; }}

Nasledujúce vlastnosti sa uplatňujú na @ManyToMany anotácia v zozname 2:

  • mappedBy odkazuje na názov poľa na SuperHero triedy, ktorá riadi vzťah mnohých proti mnohým. V tomto prípade odkazuje na filmy pole, ktoré sme definovali v zozname 1 s príslušným JoinTable.
  • kaskáda je nakonfigurovaný na CascadeType.PERSIST, čo znamená, že keď a Film je uložený zodpovedajúci SuperHero subjekty by sa tiež mali uložiť.
  • aportovať hovorí EntityManager že by mala získať superhrdinov filmu nedočkavo: keď načíta a Film, mala by tiež načítať všetky zodpovedajúce SuperHero subjekty.

Niečo iné, čo si treba uvedomiť o Film trieda je jeho addSuperHero () metóda.

Pri konfigurácii entít na perzistenciu nestačí jednoducho pridať do filmu superhrdinu; musíme aktualizovať aj druhú stranu vzťahu. To znamená, že musíme film pridať k superhrdinovi. Keď sú obe strany vzťahu správne nakonfigurované, takže film má odkaz na superhrdinu a superhrdina má odkaz na film, tabuľka spojenia sa tiež správne vyplní.

Definovali sme naše dve entity. Teraz sa pozrime na úložiská, ktoré použijeme na ich perzistenciu do az databázy.

Tip! Postavte obe strany stola

Častou chybou je nastaviť iba jednu stranu vzťahu, pretrvávať v entite a potom pozorovať, že tabuľka spojení je prázdna. Toto napraví nastavenie oboch strán vzťahu.

Úložiská JPA

Celý náš kód perzistencie by sme mohli implementovať priamo vo vzorovej aplikácii, ale vytvorenie tried úložiska nám umožňuje oddeliť kód perzistencie od kódu aplikácie. Rovnako ako v prípade aplikácie Knihy a autori v 1. časti, vytvoríme aj EntityManager a potom ho použiť na inicializáciu dvoch úložísk, jedného pre každú entitu, ktorú pretrvávame.

Zoznam 3 zobrazuje zdrojový kód súboru MovieRepository trieda.

Zoznam 3. MovieRepository.java

 balíček com.geekcap.javaworld.jpa.repository; import com.geekcap.javaworld.jpa.model.Movie; import javax.persistence.EntityManager; import java.util.List; import java.util.Voliteľné; verejná trieda MovieRepository {private EntityManager entityManager; public MovieRepository (EntityManager entityManager) {this.entityManager = entityManager; } verejné Voliteľné uloženie (filmový film) {try {entityManager.getTransaction (). begin (); entityManager.persist (film); entityManager.getTransaction (). commit (); návrat Optional.of (film); } catch (Výnimka e) {e.printStackTrace (); } návrat Optional.empty (); } public Voliteľné findById (celé číslo) {Movie movie = entityManager.find (Movie.class, id); vrátiť film! = null? Optional.of (film): Optional.empty (); } public List findAll () {return entityManager.createQuery ("from Movie"). getResultList (); } public void deleteById (Integer id) {// Načítať film s týmto ID Movie movie = entityManager.find (Movie.class, id); if (movie! = null) {try {// Spustiť transakciu, pretože zmeníme databázu entityManager.getTransaction (). begin (); // Odstrániť všetky odkazy na tento film superhrdinami movie.getSuperHeroes (). ForEach (superHero -> {superHero.getMovies (). Remove (movie);}); // Teraz odstráňte film entityManager.remove (film); // Potvrdenie transakcie entityManager.getTransaction (). Commit (); } catch (Výnimka e) {e.printStackTrace (); }}}} 

The MovieRepository sa inicializuje pomocou EntityManager, potom ho uloží do členskej premennej, ktorá sa použije v metódach perzistencie. Zvážime každú z týchto metód.

Metódy perzistencie

Poďme preskúmať MovieRepositorymetódy vytrvalosti a zistiť, ako interagujú s EntityManagermetódy vytrvalosti.

$config[zx-auto] not found$config[zx-overlay] not found