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
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 HainesNoter 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
identificererSuperHero
som en JPA-enhed.@Bord
kortlæggerSuperHero
enhed til tabellen "SUPER_HERO".
Bemærk også Heltal
id
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 Film
s.
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 tilsvarendeDeltag i bordet
.kaskade
er konfigureret tilCascadeType.PERSIST
, hvilket betyder, at når enFilm
gemmes dens tilsvarendeSuperHero
enheder skal også gemmes.hente
fortællerEntityManager
at det skal hente en films superhelte ivrigt: når det indlæses aFilm
, det skal også indlæse alle tilsvarendeSuperHero
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å MovieRepository
s vedholdenhedsmetoder og se, hvordan de interagerer med EntityManager
s vedholdenhedsmetoder.