Programování

Perzistence prostředí Java s JPA a Hibernate, část 2: Vztahy mnoho k mnoha

První polovina tohoto kurzu představila základy rozhraní API Java Persistence a ukázala vám, jak konfigurovat aplikaci JPA pomocí Hibernate 5.3.6 a Java 8. Pokud jste si přečetli tento tutoriál a prostudovali jeho ukázkovou aplikaci, pak znáte základy modelování entit JPA a mnohostranných vztahů v JPA. Také jste měli nějaké zkušenosti s psaním pojmenovaných dotazů pomocí JPA Query Language (JPQL).

V této druhé polovině tutoriálu pojedeme hlouběji s JPA a Hibernate. Dozvíte se, jak modelovat vztah mezi mnoha Film a Super hrdina entity, nastavit jednotlivá úložiště pro tyto entity a přetrvávat entity do databáze H2 v paměti. Dozvíte se také více o roli kaskádových operací v JPA a získáte tipy pro výběr a CascadeType strategie pro entity v databázi. Nakonec spojíme fungující aplikaci, kterou můžete spustit ve svém IDE nebo na příkazovém řádku.

Tento kurz se zaměřuje na základy JPA, ale nezapomeňte se podívat na tyto tipy pro Javu, které zavádějí pokročilejší témata v JPA:

  • Vztahy dědičnosti v JPA a Hibernate
  • Kompozitní klíče v JPA a Hibernate
stáhnout Získat kód Stáhněte si zdrojový kód například aplikací použitých v tomto výukovém programu. Vytvořil Steven Haines pro JavaWorld.

Vztahy mnoho k mnoha v JPA

Vztahy mnoho k mnoha definujte entity, pro které mohou mít obě strany vztahu více vzájemných odkazů. Pro náš příklad budeme modelovat filmy a superhrdiny. Na rozdíl od příkladu Autoři a knihy z 1. části může mít film několik superhrdinů a superhrdina se může objevit ve více filmech. Naši superhrdinové, Ironman a Thor, se objevují ve dvou filmech „The Avengers“ a „Avengers: Infinity War“.

K modelování tohoto vztahu mnoho k mnoha pomocí JPA budeme potřebovat tři tabulky:

  • FILM
  • SUPER HRDINA
  • SUPERHERO_MOVIES

Obrázek 1 ukazuje model domény se třemi tabulkami.

Steven Haines

Všimněte si, že SuperHero_Movies je připojit stůl mezi Film a Super hrdina tabulky. V JPA je tabulka spojení speciální druh tabulky, který usnadňuje vztah mezi mnoha.

Jednosměrný nebo obousměrný?

V JPA používáme @ManyToMany anotace k modelování vztahů mezi mnoha. Tento typ vztahu může být jednosměrný nebo obousměrný:

  • V jednosměrný vztah pouze jedna entita ve vztahu ukazuje na druhou.
  • V obousměrný vztah obě entity ukazují na sebe.

Náš příklad je obousměrný, což znamená, že film ukazuje na všechny své superhrdiny a superhrdina ukazuje na všechny jejich filmy. V obousměrném vztahu mnoho k jedné entitě vlastní vztah a druhý je mapováno na vztah. Používáme mappedBy atribut @ManyToMany anotace k vytvoření tohoto mapování.

Výpis 1 zobrazuje zdrojový kód pro Super hrdina třída.

Výpis 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") veřejná třída SuperHero {@Id @GeneratedValue soukromé celé číslo; soukromé jméno řetězce; @ManyToMany (fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) @JoinTable (name = "SuperHero_Movies", joinColumns = {@JoinColumn (name = "superhero_id")}, inverseJoinColumns = {@JoinColumn) }) soukromá sada filmů = nová HashSet (); public SuperHero () {} public SuperHero (Integer id, String name) {this.id = id; this.name = name; } public SuperHero (název řetězce) {this.name = name; } public Integer getId () {return id; } public void setId (Integer id) {this.id = id; } public String getName () {zpáteční jméno; } public void setName (název řetězce) {this.name = name; } public Set getMovies () {návrat filmů; } @Override public String toString () {return "SuperHero {" + "id =" + id + ", + name +" \ '' + ", + films.stream (). Map (Movie :: getTitle) .collect (Collectors.toList ()) + "\ '' + '}'; }} 

The Super hrdina třída má několik anotací, které by měly být známé z části 1:

  • @Entity identifikuje Super hrdina jako subjekt JPA.
  • @Stůl mapuje Super hrdina entita do tabulky „SUPER_HERO“.

Všimněte si také Celé čísloid pole, které určuje, že primární klíč tabulky bude automaticky generován.

Dále se podíváme na @ManyToMany a @JoinTable anotace.

Načítání strategií

Věc, kterou si musíte všimnout v @ManyToMany anotace je způsob, jakým konfigurujeme načítání strategie, což může být líné nebo dychtivé. V tomto případě jsme nastavili vynést na EAGER, takže když získáme a Super hrdina z databáze také automaticky načteme všechny její odpovídající Films.

Pokud jsme se rozhodli provést a LÍNÝ místo toho načíst, načteme pouze každý Film protože to bylo konkrétně přístupné. Líné načítání je možné pouze při Super hrdina je připojen k EntityManager; jinak přístup k filmům superhrdiny vyvolá výjimku. Chceme mít přístup k filmům superhrdiny na vyžádání, takže v tomto případě zvolíme možnost EAGER načítání strategie.

CascadeType.PERSIST

Kaskádové operace definovat, jak se superhrdinové a jejich odpovídající filmy přetrvávají do az databáze. Existuje řada konfigurací typu kaskády, ze kterých si můžete vybrat, a o nich si povíme později v tomto kurzu. Prozatím si všimněte, že jsme nastavili kaskáda přisuzovat CascadeType.PERSIST, což znamená, že když uložíme superhrdinu, uloží se také jeho filmy.

Připojte se ke stolům

JoinTable je třída, která usnadňuje vztah mezi mnoha lidmi Super hrdina a Film. V této třídě definujeme tabulku, která bude ukládat primární klíče pro oba Super hrdina a Film subjekty.

Výpis 1 určuje, že název tabulky bude SuperHero_Movies. The připojit sloupec bude superhrdina_ida sloupec inverzního spojení bude film_id. The Super hrdina entita vlastní vztah, takže sloupec spojení bude naplněn Super hrdinaprimární klíč. Sloupec inverzního spojení pak odkazuje na entitu na druhé straně vztahu, což je Film.

Na základě těchto definic v seznamu 1 bychom očekávali vytvoření nové tabulky s názvem SuperHero_Movies. Tabulka bude mít dva sloupce: superhrdina_id, který odkazuje na id sloupec SUPER HRDINA stůl a film_id, který odkazuje na id sloupec FILM stůl.

Třída filmu

Výpis 2 zobrazuje zdrojový kód pro Film třída. Připomeňme, že v obousměrném vztahu vlastní jeden subjekt vztah (v tomto případě Super hrdina), zatímco druhý je mapován na vztah. Kód v seznamu 2 obsahuje mapování vztahů aplikované na Film třída.

Výpis 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") veřejná třída Film {@Id @GeneratedValue soukromé celé číslo; soukromý název řetězce; @ManyToMany (mappedBy = "films", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER) soukromá sada superHeroes = nová HashSet (); public Movie () {} public Movie (Integer id, String title) {this.id = id; this.title = title; } veřejný film (název řetězce) {this.title = název; } public Integer getId () {return id; } public void setId (Integer id) {this.id = id; } public String getTitle () {návratový název; } public void setTitle (název řetězce) {this.title = title; } public Set getSuperHeroes () {return superHeroes; } public void addSuperHero (SuperHero superHero) {superHeroes.add (superHero); superHero.getMovies (). přidat (toto); } @Override public String toString () {return "Movie {" + "id =" + id + ", + title +" \ '' + '}'; }}

Následující vlastnosti jsou aplikovány na @ManyToMany anotace ve výpisu 2:

  • mappedBy odkazuje na název pole na Super hrdina třída, která spravuje vztah mnoho k mnoha. V tomto případě odkazuje na filmy pole, které jsme definovali v seznamu 1 s odpovídajícím JoinTable.
  • kaskáda je nakonfigurován na CascadeType.PERSIST, což znamená, že když a Film je uložen odpovídající Super hrdina entity by také měly být uloženy.
  • vynést říká EntityManager že by měl získat superhrdiny filmu dychtivě: když načte a Film, měl by také načíst všechny odpovídající Super hrdina subjekty.

Něco jiného k poznámce o Film třída je jeho addSuperHero () metoda.

Při konfiguraci entit pro trvalost nestačí jednoduše přidat do filmu superhrdinu; musíme také aktualizovat druhou stranu vztahu. To znamená, že musíme film přidat do superhrdiny. Když jsou obě strany vztahu správně nakonfigurovány, takže film má odkaz na superhrdinu a superhrdina má odkaz na film, bude tabulka připojení také správně naplněna.

Definovali jsme naše dvě entity. Nyní se podívejme na úložiště, která použijeme k jejich přetrvávání do az databáze.

Spropitné! Postavte obě strany stolu

Je běžnou chybou nastavit pouze jednu stranu vztahu, přetrvávat entitu a poté sledovat, že tabulka spojení je prázdná. Nastavení obou stran vztahu to napraví.

Úložiště JPA

Mohli bychom implementovat veškerý náš kód perzistence přímo v ukázkové aplikaci, ale vytváření tříd úložiště nám umožňuje oddělit kód perzistence od kódu aplikace. Stejně jako v případě aplikace Knihy a autoři v části 1 vytvoříme EntityManager a poté jej použít k inicializaci dvou úložišť, jednoho pro každou entitu, kterou přetrváváme.

Výpis 3 zobrazuje zdrojový kód pro Úložiště filmu třída.

Výpis 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.Optional; veřejná třída MovieRepository {private EntityManager entityManager; public MovieRepository (EntityManager entityManager) {this.entityManager = entityManager; } public Volitelné uložení (film) {try {entityManager.getTransaction (). begin (); entityManager.persist (film); entityManager.getTransaction (). commit (); návrat Optional.of (film); } catch (Výjimka e) {e.printStackTrace (); } vrátit Optional.empty (); } public Volitelné findById (celé číslo) {Movie movie = entityManager.find (Movie.class, id); vrátit film! = null? Optional.of (film): Optional.empty (); } public List findAll () {return entityManager.createQuery ("from Movie"). getResultList (); } public void deleteById (Integer id) {// Načíst film s tímto ID Movie movie = entityManager.find (Movie.class, id); if (film! = null) {try {// Zahájit transakci, protože změníme databázi entityManager.getTransaction (). begin (); // Odebrat všechny odkazy na tento film superhrdiny movie.getSuperHeroes (). ForEach (superHero -> {superHero.getMovies (). Remove (movie);}); // Nyní odeberte film entityManager.remove (film); // Potvrdit transakci entityManager.getTransaction (). Commit (); } catch (Výjimka e) {e.printStackTrace (); }}}} 

The Úložiště filmu je inicializován pomocí EntityManager, poté jej uloží do členské proměnné, která se použije v metodách perzistence. Zvažujeme každou z těchto metod.

Metody perzistence

Pojďme si to zopakovat Úložiště filmumetody vytrvalosti a uvidíte, jak interagují s EntityManagermetody vytrvalosti.

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