Ustrajnost Java s JPA i Hibernate, 2. dio: Odnosi mnogo-do-mnogo

Prva polovica ovog vodiča predstavila je osnove Java Persistent API-ja i pokazala vam kako konfigurirati JPA aplikaciju pomoću Hibernate 5.3.6 i Java 8. Ako ste pročitali taj vodič i proučavali njegovu primjeru aplikacije, tada znate osnove modeliranje JPA entiteta i odnosa više-prema-jednom u JPA. Također ste vježbali pisanje imenovanih upita s JPA jezikom upita (JPQL).

U ovoj drugoj polovici tutorijala ćemo dublje raspraviti o JPA i Hibernate. Vi ćete naučiti kako model više-prema-više odnos Moviei SuperHeroentiteta, postaviti pojedinačne spremišta za ove osobe, i dalje postoje entiteta na H2 u memoriju podataka. Također ćete saznati više o ulozi kaskadnih operacija u JPA i dobiti savjete za odabir CascadeTypestrategije za entitete u bazi podataka. Na kraju ćemo sastaviti radnu aplikaciju koju možete pokrenuti u svom IDE-u ili na naredbenom retku.

Ovaj se vodič usredotočuje na JPA osnove, ali svakako pogledajte ove Java savjete koji uvode naprednije teme u JPA:

  • Nasljedni odnosi u JPA i hibernaciji
  • Sastavljene tipke u JPA i Hibernate
preuzimanje Preuzmite kod Preuzmite izvorni kod za primjere aplikacija korištenih u ovom vodiču. Stvorio Steven Haines za JavaWorld.

Odnosi mnogo prema mnogim u JPA

Odnosi mnogo prema mnogo definiraju entitete za koje obje strane odnosa mogu imati višestruke reference jedna na drugu. Za naš ćemo primjer modelirati filmove i superheroje. Za razliku od primjera Autori i knjige iz 1. dijela, film može imati više superheroja, a superheroj se može pojaviti u više filmova. Naši superheroji, Ironman i Thor, obojica se pojavljuju u dva filma, "Osvetnici" i "Osvetnici: beskrajni rat".

Da bismo modelirali ovaj odnos mnogo-prema-mnogo pomoću JPA, trebat će nam tri tablice:

  • FILM
  • SUPERHEROJ
  • SUPERHEROJI_FILMOVI

Slika 1 prikazuje model domene s tri tablice.

Steven Haines

Imajte na umu da SuperHero_Moviesje pridružiti stol između Moviei SuperHerotablice. U JPA, tablica pridruživanja posebna je vrsta tablice koja olakšava odnos mnogo-prema-mnogima.

Jednosmjerni ili dvosmjerni?

U JPA @ManyToManybilješku koristimo za modeliranje odnosa mnogo prema mnogo. Ova vrsta odnosa može biti jednosmjerna ili dvosmjerna:

  • U jednosmjernom odnosu samo jedan entitet u odnosu ukazuje na drugi.
  • U dvosmjernom odnosu oba entiteta ukazuju jedan na drugog.

Naš primjer je dvosmjeran, što znači da film pokazuje na sve svoje superheroje, a superheroj na sve njihove filmove. U dvosmjernom odnosu "mnogi prema mnogima" jedan je entitet vlasnik odnosa, a drugi je preslikan u odnos. mappedByAtribut @ManyToManybilješke koristimo za stvaranje ovog mapiranja.

Popis 1 prikazuje izvorni kod za SuperHeroklasu.

Popis 1. SuperHero.java

 package 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") public class SuperHero { @Id @GeneratedValue private Integer id; private String name; @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) @JoinTable( name = "SuperHero_Movies", joinColumns = {@JoinColumn(name = "superhero_id")}, inverseJoinColumns = {@JoinColumn(name = "movie_id")} ) private Set movies = new HashSet(); public SuperHero() { } public SuperHero(Integer id, String name) { this.id = id; this.name = name; } public SuperHero(String name) { this.name = name; } 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() { return movies; } @Override public String toString() { return "SuperHero{" + "id=" + id + ", + name +"\'' + ", + movies.stream().map(Movie::getTitle).collect(Collectors.toList()) +"\'' + '}'; } } 

SuperHeroKlasa ima par komentare koji bi trebao biti upoznat od 1. dio:

  • @Entityidentificira SuperHerokao JPA entitet.
  • @Tablepreslikava SuperHeroentitet u tablicu "SUPER_HERO".

Također zabilježite Integeridpolje koje određuje da će se primarni ključ tablice automatski generirati.

Zatim ćemo pogledati na @ManyToManyi @JoinTablenapomena.

Dohvaćanje strategija

Ono što treba primijetiti u @ManyToManybilješci je kako konfiguriramo strategiju dohvaćanja koja može biti lijena ili željna. U ovom smo slučaju postavili fetchna EAGER, tako da ćemo, kada dohvatimo a SuperHeroiz baze podataka, automatski preuzeti i sve odgovarajuće Movies.

Ako bismo LAZYumjesto toga odlučili izvršiti dohvaćanje, dohvatili bismo samo svaki onaj Moviekojem je posebno pristupljeno. Lijeno dohvaćanje moguće je samo dok SuperHeroje pričvršćeno na EntityManager; inače će pristup filmovima o superheroju izuzetak. Želimo imati pristup filmovima superheroja na zahtjev, pa u ovom slučaju odabiremo EAGERstrategiju dohvaćanja.

CascadeType.PERSIST

Kaskadne operacije definiraju kako se superheroji i njihovi odgovarajući filmovi održavaju u i iz baze podataka. Postoji nekoliko konfiguracija kaskadnog tipa, a o njima ćemo više govoriti kasnije u ovom vodiču. Za sada samo imajte na umu da smo cascadeatribut postavili na CascadeType.PERSIST, što znači da će, kad spremimo superheroja, biti sačuvani i njegovi filmovi.

Spoji tablice

JoinTableje klasa koja olakšava odnos mnogi-prema-mnogima između SuperHeroi Movie. U ovoj klasi definiramo tablicu koja će pohraniti primarne ključeve SuperHeroi za Movieentitete i za entitete.

Popis 1 navodi da će naziv tablice biti SuperHero_Movies. Pridružiti stupac će biti superhero_idi obrnut pridružiti stupac će biti movie_id. SuperHeroSubjekt posjeduje odnos, tako da se pridruži stupac će biti popunjeno s SuperHeroprimarnim ključem „s. Stupac inverznog spajanja zatim upućuje na entitet s druge strane odnosa, što jest Movie.

Na temelju ovih definicija u Popisu 1, očekivali bismo izrađenu novu tablicu s imenom SuperHero_Movies. Tablica će imati dva stupca:, superhero_idkoji upućuje na idstupac SUPERHEROtablice i movie_idkoji upućuje na idstupac MOVIEtablice.

Predavanje filma

Popis 2 prikazuje izvorni kod za Movieklasu. Sjetimo se da je u dvosmjernom odnosu jedan entitet vlasnik odnosa (u ovom slučaju SuperHero), dok je drugi mapiran na odnos. Kôd u Popisu 2 uključuje mapiranje odnosa primijenjeno na Movieklasu.

Unos 2. Movie.java

 package 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") public class Movie { @Id @GeneratedValue private Integer id; private String title; @ManyToMany(mappedBy = "movies", 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; } public Movie(String title) { 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; } public Set getSuperHeroes() { return superHeroes; } public void addSuperHero(SuperHero superHero) { superHeroes.add(superHero); superHero.getMovies().add(this); } @Override public String toString() { return "Movie{" + "id=" + id + ", + title +"\'' + '}'; } }

Sljedeća svojstva primjenjuju se na @ManyToManybilješku na popisu 2:

  • mappedBy references the field name on the SuperHero class that manages the many-to-many relationship. In this case, it references the movies field, which we defined in Listing 1 with the corresponding JoinTable.
  • cascade is configured to CascadeType.PERSIST, which means that when a Movie is saved its corresponding SuperHero entities should also be saved.
  • fetch tells the EntityManager that it should retrieve a movie's superheroes eagerly: when it loads a Movie, it should also load all corresponding SuperHero entities.

Something else to note about the Movie class is its addSuperHero() method.

When configuring entities for persistence, it isn't enough to simply add a superhero to a movie; we also need to update the other side of the relationship. This means we need to add the movie to the superhero. When both sides of the relationship are configured properly, so that the movie has a reference to the superhero and the superhero has a reference to the movie, then the join table will also be properly populated.

We've defined our two entities. Now let's look at the repositories we'll use to persist them to and from the database.

Tip! Set both sides of the table

It's a common mistake to only set one side of the relationship, persist the entity, and then observe that the join table is empty. Setting both sides of the relationship will fix this.

JPA repositories

Sav naš kôd postojanosti mogli bismo implementirati izravno u uzorak aplikacije, ali stvaranje klasa spremišta omogućuje nam odvajanje koda postojanosti od koda aplikacije. Baš kao što smo učinili s aplikacijom Knjige i autori u 1. dijelu, stvorit ćemo EntityManageri zatim ga koristiti za inicijalizaciju dva spremišta, po jedno za svaki entitet u kojem postojimo.

Popis 3 prikazuje izvorni kod za MovieRepositoryklasu.

Popis 3. MovieRepository.java

 package com.geekcap.javaworld.jpa.repository; import com.geekcap.javaworld.jpa.model.Movie; import javax.persistence.EntityManager; import java.util.List; import java.util.Optional; public class MovieRepository { private EntityManager entityManager; public MovieRepository(EntityManager entityManager) { this.entityManager = entityManager; } public Optional save(Movie movie) { try { entityManager.getTransaction().begin(); entityManager.persist(movie); entityManager.getTransaction().commit(); return Optional.of(movie); } catch (Exception e) { e.printStackTrace(); } return Optional.empty(); } public Optional findById(Integer id) { Movie movie = entityManager.find(Movie.class, id); return movie != null ? Optional.of(movie) : Optional.empty(); } public List findAll() { return entityManager.createQuery("from Movie").getResultList(); } public void deleteById(Integer id) { // Retrieve the movie with this ID Movie movie = entityManager.find(Movie.class, id); if (movie != null) { try { // Start a transaction because we're going to change the database entityManager.getTransaction().begin(); // Remove all references to this movie by superheroes movie.getSuperHeroes().forEach(superHero -> { superHero.getMovies().remove(movie); }); // Now remove the movie entityManager.remove(movie); // Commit the transaction entityManager.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); } } } } 

The MovieRepositoryse inicijalizira s EntityManager, a zatim ga sprema u varijablu člana koja će se koristiti u njegovim metodama trajanja. Razmotrit ćemo svaku od ovih metoda.

Metode postojanosti

Pogledajmo MovieRepositorymetode postojanosti i vidjet ćemo kako one djeluju na EntityManagermetode postojanosti.