Programmering

Java-vedholdenhed med JPA og dvale, del 2: Mange-til-mange-forhold

Den første halvdel af denne tutorial introducerede grundlæggende funktioner i Java Persistence API og viste dig, hvordan du konfigurerer en JPA-applikation ved hjælp af Hibernate 5.3.6 og Java 8. Hvis du har læst denne tutorial og studeret dens eksempelapplikation, kender du det grundlæggende i modellering af JPA-enheder og mange-til-en-forhold i JPA. Du har også haft nogle øvelser i at skrive navngivne forespørgsler med JPA Query Language (JPQL).

I denne anden halvdel af vejledningen går vi dybere sammen med JPA og Hibernate. Du lærer at modellere et mange-til-mange forhold mellem Film og SuperHero enheder, oprette individuelle arkiver for disse enheder og vedligeholde enhederne til H2-hukommelsesdatabasen. Du lærer også mere om rollen som kaskadeoperationer i JPA og får tip til valg af en CascadeType strategi for enheder i databasen. Endelig samler vi et fungerende program, som du kan køre i din IDE eller på kommandolinjen.

Denne tutorial fokuserer på JPA-grundlæggende, men sørg for at tjekke disse Java-tip, der introducerer mere avancerede emner i JPA:

  • Arveforhold i JPA og dvale
  • Kompositnøgler i JPA og dvale
download Hent koden Download kildekoden for eksempel applikationer, der bruges i denne vejledning. Oprettet af Steven Haines til JavaWorld.

Mange-til-mange relationer i JPA

Mange-til-mange relationer definere enheder, for hvilke begge sider af forholdet kan have flere referencer til hinanden. For vores eksempel skal vi modellere film og superhelte. I modsætning til eksemplet for forfattere og bøger fra del 1 kan en film have flere superhelte, og en superhelt kan vises i flere film. Vores superhelte, Ironman og Thor, vises begge i to film, "The Avengers" og "Avengers: Infinity War."

For at modellere dette mange-til-mange forhold ved hjælp af JPA har vi brug for tre tabeller:

  • FILM
  • SUPER_HERO
  • SUPERHERO_MOVIES

Figur 1 viser domænemodellen med de tre tabeller.

Steven Haines

Noter det SuperHero_Movies er en slutte sig til bordet imellem Film og SuperHero tabeller. I JPA er en sammenføjningstabel en speciel form for tabel, der letter forholdet mellem mange og mange.

Envejs eller tovejs?

I JPA bruger vi @ManyToMany kommentar for at modellere mange-til-mange-forhold. Denne type forhold kan være ensrettet eller tovejs:

  • I en ensrettet forhold kun en enhed i forholdet peger på den anden.
  • I en tovejs forhold begge enheder peger på hinanden.

Vores eksempel er tovejs, hvilket betyder at en film peger på alle sine superhelte, og en superhelt peger på alle deres film. I et tovejs, mange-til-mange forhold, en enhed ejer forholdet og det andet er kortlagt til forholdet. Vi bruger kortlagt af attribut for @ManyToMany kommentar for at oprette denne kortlægning.

Liste 1 viser kildekoden til SuperHero klasse.

Notering 1. SuperHero.java

 pakke com.geekcap.javaworld.jpa.model; importere javax.persistence.CascadeType; importere javax.persistence.Entity; importere javax.persistence.FetchType; import javax.persistence.GeneratedValue; importere javax.persistence.Id; importere javax.persistence.JoinColumn; importere javax.persistence.JoinTable; importere javax.persistence.ManyToMany; import javax.persistence.Table; importere java.util.HashSet; importere java.util.Set; importere java.util.stream.Collectors; @Entity @Table (name = "SUPER_HERO") offentlig klasse SuperHero {@Id @GeneratedValue privat heltal id; privat strengnavn; @ManyToMany (fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) @JoinTable (name = "SuperHero_Movies", joinColumns = {@JoinColumn (name = "superhero_id")}, inverseJoinColumn = {@JoinColumn (name = "movie_id) }) private sæt film = nye HashSet (); offentlig SuperHero () {} offentlig SuperHero (heltal-id, strengnavn) {this.id = id; dette.navn = navn; } offentlig SuperHero (strengnavn) {this.name = navn; } public Integer getId () {return id; } public void setId (Integer id) {this.id = id; } public String getName () {return name; } public void setName (String name) {this.name = name; } public Set getMovies () {returner film; } @Override public String toString () {return "SuperHero {" + "id =" + id + ", + name +" \ '' + ", + films.stream (). Map (Film :: getTitle) .collect (Collectors.toList ()) + "\ '' + '}'; }} 

Det SuperHero klasse har et par kommentarer, der skal være kendt fra del 1:

  • @Enhed identificerer SuperHero som en JPA-enhed.
  • @Bord kortlægger SuperHero enhed til tabellen "SUPER_HERO".

Bemærk også Heltalid felt, som angiver, at tabellens primære nøgle genereres automatisk.

Dernæst ser vi på @ManyToMany og @JoinTable kommentarer.

Henter strategier

Ting at bemærke i @ManyToMany kommentar er, hvordan vi konfigurerer hentningsstrategi, som kan være doven eller ivrig. I dette tilfælde har vi indstillet hente til IVRIGE, så når vi henter en SuperHero fra databasen henter vi også automatisk alle dens tilsvarende Films.

Hvis vi valgte at udføre en DOVEN hent i stedet, ville vi kun hente hver Film som det blev specifikt åbnet. Lazy hentning er kun muligt, mens SuperHero er knyttet til EntityManager; ellers får adgang til en superheltfilm en undtagelse. Vi ønsker at være i stand til at få adgang til en superheltfilm efter behov, så i dette tilfælde vælger vi IVRIGE hentningsstrategi.

CascadeType.PERSIST

Kaskadeoperationer definere, hvordan superhelte og deres tilsvarende film fortsættes til og fra databasen. Der er et antal kaskadetypekonfigurationer at vælge imellem, og vi vil tale mere om dem senere i denne vejledning. For nu skal du bare bemærke, at vi har indstillet kaskade attribut til CascadeType.PERSIST, hvilket betyder, at når vi gemmer en superhelt, gemmes dens film også.

Deltag i borde

Deltag i bordet er en klasse, der letter mange-til-mange-forholdet mellem SuperHero og Film. I denne klasse definerer vi tabellen, der gemmer de primære nøgler til begge SuperHero og Film enheder.

Liste 1 angiver, at tabelnavnet vil være SuperHero_Movies. Det Deltag i kolonne vil være superhelt_id, og invers sammenføjningskolonne vil være film_id. Det SuperHero enhed ejer forholdet, så sammenføjningskolonnen udfyldes med SuperHero's primære nøgle. Den inverse sammenføjningskolonne henviser derefter til enheden på den anden side af forholdet, hvilket er Film.

Baseret på disse definitioner i liste 1 forventer vi, at der oprettes en ny tabel med navnet SuperHero_Movies. Tabellen har to kolonner: superhelt_id, der henviser til id kolonne i SUPERHERO bord og film_id, der henviser til id kolonne i FILM bord.

Filmklassen

Liste 2 viser kildekoden til Film klasse. Husk, at i en tovejsforhold ejer en enhed forholdet (i dette tilfælde SuperHero) mens den anden kortlægges til forholdet. Koden i liste 2 inkluderer tilknytningskortlægningen anvendt på Film klasse.

Annonce 2. Movie.java

 pakke com.geekcap.javaworld.jpa.model; importere javax.persistence.CascadeType; importere javax.persistence.Entity; importere javax.persistence.FetchType; import javax.persistence.GeneratedValue; importere javax.persistence.Id; importere javax.persistence.ManyToMany; import javax.persistence.Table; importere java.util.HashSet; importere java.util.Set; @Entity @Table (name = "MOVIE") offentlig klasse film {@Id @GeneratedValue privat heltal id; privat streng titel; @ManyToMany (mappedBy = "film", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER) privat Sæt superHeroes = nyt HashSet (); public Movie () {} public Movie (Integer id, String title) {this.id = id; this.title = titel; } offentlig film (strengetitel) {this.title = title; } public Integer getId () {return id; } public void setId (Integer id) {this.id = id; } public String getTitle () {return title; } public void setTitle (String title) {this.title = title; } offentlig sæt getSuperHeroes () {returner superHeroes; } offentlig ugyldighed addSuperHero (SuperHero superHero) {superHeroes.add (superHero); superHero.getMovies (). tilføj (dette); } @ Overstyr offentlig streng til String () {returner "Film {" + "id =" + id + ", + titel +" \ '' + '}'; }}

Følgende egenskaber anvendes på @ManyToMany kommentar i liste 2:

  • kortlagt af henviser til feltnavnet på SuperHero klasse, der styrer det mange-til-mange forhold. I dette tilfælde henviser det til film felt, som vi definerede i liste 1 med det tilsvarende Deltag i bordet.
  • kaskade er konfigureret til CascadeType.PERSIST, hvilket betyder, at når en Film gemmes dens tilsvarende SuperHero enheder skal også gemmes.
  • hente fortæller EntityManager at det skal hente en films superhelte ivrigt: når det indlæses a Film, det skal også indlæse alle tilsvarende SuperHero enheder.

Noget andet at bemærke om Film klasse er dens tilføjSuperHero () metode.

Når du konfigurerer enheder til udholdenhed, er det ikke nok blot at tilføje en superhelt til en film; vi har også brug for at opdatere den anden side af forholdet. Dette betyder, at vi er nødt til at føje filmen til superhelt. Når begge sider af forholdet er konfigureret korrekt, så filmen har en henvisning til superhelt, og superhelt har en henvisning til filmen, vil sammenføjningstabellen også blive udfyldt korrekt.

Vi har defineret vores to enheder. Lad os nu se på de opbevaringssteder, vi bruger til at fastholde dem til og fra databasen.

Tip! Sæt begge sider af bordet

Det er en almindelig fejl at kun indstille den ene side af forholdet, fastholde enheden og derefter observere, at sammenføjningstabellen er tom. At indstille begge sider af forholdet løser dette.

JPA-arkiver

Vi kunne implementere al vores persistenskode direkte i prøveapplikationen, men ved at oprette lagerklasser kan vi adskille persistenskode fra applikationskode. Ligesom vi gjorde med applikationen Books & Authors i del 1, opretter vi en EntityManager og brug det derefter til at initialisere to arkiver, en for hver enhed, vi vedvarer.

Liste 3 viser kildekoden til MovieRepository klasse.

Annonce 3. MovieRepository.java

 pakke com.geekcap.javaworld.jpa.repository; import com.geekcap.javaworld.jpa.model.Movie; importere javax.persistence.EntityManager; importere java.util.List; import java.util.Optional; offentlig klasse MovieRepository {private EntityManager entityManager; offentlig MovieRepository (EntityManager entityManager) {this.entityManager = entityManager; } offentlig Valgfri gem (filmfilm) {prøv {entityManager.getTransaction (). start (); entityManager.persist (film); entityManager.getTransaction (). commit (); returnere Optional.of (film); } fange (Undtagelse e) {e.printStackTrace (); } returner Optional.empty (); } offentlig Valgfri findById (heltal-id) {filmfilm = entityManager.find (film.klasse, id); returner film! = null? Optional.of (film): Optional.empty (); } offentlig liste findAll () {return entityManager.createQuery ("fra film"). getResultList (); } public void deleteById (Integer id) {// Hent filmen med dette ID Film movie = entityManager.find (Movie.class, id); hvis (film! = null) {prøv {// Start en transaktion, fordi vi vil ændre database entityManager.getTransaction (). start (); // Fjern alle referencer til denne film af superhelte movie.getSuperHeroes (). ForEach (superHero -> {superHero.getMovies (). Remove (film);}); // Fjern nu filmen entityManager.remove (film); // Forpligt transaktionsenhedenManager.getTransaction (). Commit (); } fange (Undtagelse e) {e.printStackTrace (); }}}} 

Det MovieRepository initialiseres med en EntityManager, gemmer den derefter i en medlemsvariabel, der skal bruges i dens persistensmetoder. Vi overvejer hver af disse metoder.

Persistensmetoder

Lad os gennemgå MovieRepositorys vedholdenhedsmetoder og se, hvordan de interagerer med EntityManagers vedholdenhedsmetoder.