Προγραμματισμός

Επιμονή Java με JPA και Hibernate, Μέρος 2: Πολλές σε πολλές σχέσεις

Το πρώτο μισό αυτού του σεμιναρίου εισήγαγε βασικές αρχές του Java Persistence API και σας έδειξε πώς να ρυθμίσετε μια εφαρμογή JPA χρησιμοποιώντας το Hibernate 5.3.6 και το Java 8. Αν έχετε διαβάσει αυτό το σεμινάριο και έχετε μελετήσει το παράδειγμα εφαρμογής του, τότε γνωρίζετε τα βασικά μοντελοποίηση οντοτήτων JPA και σχέσεων πολλά προς ένα στο JPA. Είχατε επίσης κάποια πρακτική σύνταξη ερωτημάτων με όνομα JPA Query Language (JPQL).

Σε αυτό το δεύτερο μισό του σεμιναρίου θα πάμε βαθύτερα με το JPA και το Hibernate. Θα μάθετε πώς να διαμορφώνετε μια σχέση μεταξύ πολλών και πολλών Ταινία και SuperHero οντότητες, δημιουργία μεμονωμένων αποθετηρίων για αυτές τις οντότητες και διατήρηση των οντοτήτων στη βάση δεδομένων H2 στη μνήμη. Θα μάθετε επίσης περισσότερα σχετικά με το ρόλο των διαδικασιών καταρράκτη στο JPA και θα λάβετε συμβουλές για την επιλογή ενός Τύπος Cascade στρατηγική για οντότητες στη βάση δεδομένων. Τέλος, θα συγκεντρώσουμε μια εφαρμογή που μπορείτε να εκτελέσετε στο IDE ή στη γραμμή εντολών.

Αυτό το σεμινάριο επικεντρώνεται στις βασικές αρχές του JPA, αλλά φροντίστε να δείτε αυτές τις συμβουλές Java που εισάγουν πιο προηγμένα θέματα στο JPA:

  • Σχέσεις κληρονομικότητας στο JPA και το Hibernate
  • Σύνθετα κλειδιά σε JPA και Hibernate
λήψη Λήψη του κώδικα Λήψη του πηγαίου κώδικα για παράδειγμα εφαρμογές που χρησιμοποιούνται σε αυτό το σεμινάριο. Δημιουργήθηκε από τον Steven Haines για το JavaWorld.

Πολλές σε πολλές σχέσεις στο JPA

Πολλές σε πολλές σχέσεις ορίστε οντότητες για τις οποίες και οι δύο πλευρές της σχέσης μπορούν να έχουν πολλαπλές αναφορές μεταξύ τους. Για παράδειγμα, πρόκειται να μοντελοποιήσουμε ταινίες και υπερήρωες. Σε αντίθεση με το παράδειγμα των συγγραφέων και των βιβλίων από το Μέρος 1, μια ταινία μπορεί να έχει πολλούς υπερήρωες και ένας υπερήρωας μπορεί να εμφανίζεται σε πολλές ταινίες. Οι υπερήρωες μας, Ironman και Thor, και οι δύο εμφανίζονται σε δύο ταινίες, "The Avengers" και "Avengers: Infinity War".

Για να μοντελοποιήσουμε αυτήν τη σχέση πολλά προς πολλά χρησιμοποιώντας το JPA, θα χρειαστούμε τρεις πίνακες:

  • ΤΑΙΝΙΑ
  • SUPER_HERO
  • SUPERHERO_MOVIES

Το σχήμα 1 δείχνει το μοντέλο τομέα με τους τρεις πίνακες.

Στίβεν Χάινς

Σημειώστε ότι SuperHero_Movies είναι ένα εγγραφείτε στο τραπέζι ανάμεσα σε Ταινία και SuperHero τραπέζια. Στο JPA, ένας πίνακας συμμετοχής είναι ένα ειδικό είδος πίνακα που διευκολύνει τη σχέση μεταξύ πολλών.

Μονοκατευθυντική ή αμφίδρομη;

Στο JPA χρησιμοποιούμε το @ManyToMany σχολιασμός για τη μοντελοποίηση σχέσεων μεταξύ πολλών. Αυτός ο τύπος σχέσης μπορεί να είναι μονοκατευθυντικός ή αμφίδρομος:

  • Σε ένα μονοκατευθυντική σχέση μόνο μια οντότητα στη σχέση δείχνει την άλλη.
  • Σε ένα αμφίδρομη σχέση και οι δύο οντότητες δείχνουν μεταξύ τους.

Το παράδειγμά μας είναι αμφίδρομο, που σημαίνει ότι μια ταινία δείχνει όλους τους υπερήρωες της και ένας υπερήρωας δείχνει όλες τις ταινίες τους. Σε μια αμφίδρομη σχέση, πολλά προς πολλά, μία οντότητα κατέχει η σχέση και η άλλη είναι χαρτογραφήθηκε σε η σχέση. Χρησιμοποιούμε το χαρτογραφημένος χαρακτηριστικό του @ManyToMany σχολιασμός για να δημιουργήσετε αυτήν τη χαρτογράφηση.

Η λίστα 1 εμφανίζει τον πηγαίο κώδικα για το SuperHero τάξη.

Λίστα 1. SuperHero.java

 πακέτο com.geekcap.javaworld.jpa.model; εισαγωγή javax.persistence.CascadeType; εισαγωγή javax.persistence.Entity; εισαγωγή javax.persistence.FetchType; εισαγωγή javax.persistence.GeneratedValue; εισαγωγή javax.persistence.Id; εισαγωγή javax.persistence.JoinColumn; εισαγωγή javax.persistence.JoinTable; εισαγωγή javax.persistence.ManyToMany; εισαγωγή javax.persistence.Table; εισαγωγή java.util.HashSet; εισαγωγή java.util.Set; εισαγωγή java.util.stream.Collectors; @Entity @Table (name = "SUPER_HERO") δημόσια τάξη SuperHero {@Id @GeneratedValue ιδιωτικό Integer id; ιδιωτικό όνομα συμβολοσειράς; @ManyToMany (fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) @JoinTable (name = "SuperHero_Movies", joinColumns = {@JoinColumn (name = "superhero_id")}, inverseJoinColumns = {@JoinColumn (όνομα =) }) ιδιωτικές ταινίες Set = νέο HashSet (); δημόσιο SuperHero () {} δημόσιο SuperHero (Integer id, String name) {this.id = id; this.name = όνομα; } δημόσιο SuperHero (όνομα συμβολοσειράς) {this.name = name; } δημόσιο Integer getId () {return id; } public void setId (Integer id) {this.id = id; } δημόσια συμβολοσειρά getName () {return name; } public void setName (όνομα συμβολοσειράς) {this.name = name; } Δημόσιο σύνολο getMovies () {επιστροφή ταινιών; } @Override public String toString () {return "SuperHero {" + "id =" + id + ", + name +" \ '' + ", + movies.stream (). Map (Ταινία :: getTitle) .collect (Collectors.toList ()) + "\ '' + '}'; }} 

ο SuperHero Η τάξη έχει μερικούς σχολιασμούς που πρέπει να είναι εξοικειωμένοι με το Μέρος 1:

  • @Οντότητα προσδιορίζει SuperHero ως οντότητα JPA.
  • @Τραπέζι χαρτογραφεί το SuperHero οντότητα στον πίνακα "SUPER_HERO".

Σημειώστε επίσης το Ακέραιος αριθμόςταυτότητα πεδίο, το οποίο καθορίζει ότι το πρωτεύον κλειδί του πίνακα θα δημιουργηθεί αυτόματα.

Στη συνέχεια θα δούμε το @ManyToMany και @JoinTable σχολιασμοί.

Ανάκτηση στρατηγικών

Το πράγμα που πρέπει να προσέξετε στο @ManyToMany ο σχολιασμός είναι ο τρόπος διαμόρφωσης του στρατηγική ανάκτησης, που μπορεί να είναι τεμπέλης ή πρόθυμος. Σε αυτήν την περίπτωση, έχουμε ορίσει το φέρω προς την ΠΡΟΘΥΜΟΣ, έτσι ώστε όταν ανακτούμε ένα SuperHero από τη βάση δεδομένων, θα ανακτήσουμε επίσης αυτόματα όλα τα αντίστοιχα Ταινίαμικρό.

Εάν επιλέξαμε να εκτελέσουμε ένα ΤΕΜΠΕΛΗΣ αντ 'αυτού, θα ανακτήσαμε μόνο το καθένα Ταινία καθώς είχε πρόσβαση συγκεκριμένα. Η αργή ανάκτηση είναι δυνατή μόνο όταν το SuperHero επισυνάπτεται στο EntityManager; Διαφορετικά, η πρόσβαση σε ταινίες ενός υπερήρωα θα αποτελεί εξαίρεση. Θέλουμε να έχουμε πρόσβαση στις ταινίες ενός υπερήρωα κατ 'απαίτηση, οπότε σε αυτήν την περίπτωση επιλέγουμε το ΠΡΟΘΥΜΟΣ στρατηγική ανάκτησης.

CascadeType.PERSIST

Καταρράκτες καθορίστε πώς οι υπερήρωες και οι αντίστοιχες ταινίες τους διατηρούνται από και προς τη βάση δεδομένων. Υπάρχουν διάφορες διαμορφώσεις τύπου cascade για να διαλέξετε και θα μιλήσουμε περισσότερο γι 'αυτές αργότερα σε αυτό το σεμινάριο. Προς το παρόν, απλώς σημειώστε ότι έχουμε ορίσει αλληλουχία αποδίδω σε CascadeType.PERSIST, που σημαίνει ότι όταν αποθηκεύουμε έναν υπερήρωα, οι ταινίες του θα αποθηκεύονται επίσης.

Συμμετοχή σε τραπέζια

Πίνακας Συμμετοχής είναι μια τάξη που διευκολύνει τη σχέση μεταξύ πολλών προς SuperHero και Ταινία. Σε αυτήν την τάξη, ορίζουμε τον πίνακα που θα αποθηκεύει τα κύρια κλειδιά και για τα δύο SuperHero και το Ταινία οντότητες.

Η λίστα 1 καθορίζει ότι θα είναι το όνομα του πίνακα SuperHero_Movies. ο εγγραφή στη στήλη θα είναι superhero_id, και το αντίστροφη στήλη σύνδεσης θα είναι movie_id. ο SuperHero η οντότητα κατέχει τη σχέση, οπότε η στήλη σύνδεσης θα συμπληρωθεί με SuperHeroπρωτεύον κλειδί. Η στήλη αντίστροφη ένωση στη συνέχεια αναφέρεται στην οντότητα στην άλλη πλευρά της σχέσης, η οποία είναι Ταινία.

Με βάση αυτούς τους ορισμούς στην Καταχώριση 1, αναμένουμε να δημιουργηθεί ένας νέος πίνακας, που θα ονομάζεται SuperHero_Movies. Ο πίνακας θα έχει δύο στήλες: superhero_id, που αναφέρεται στο ταυτότητα στήλη του ΣΟΥΦΕΡΡΟ τραπέζι και movie_id, που αναφέρεται στο ταυτότητα στήλη του ΤΑΙΝΙΑ τραπέζι.

Η τάξη της ταινίας

Η λίστα 2 δείχνει τον πηγαίο κώδικα για το Ταινία τάξη. Θυμηθείτε ότι σε αμφίδρομη σχέση, μια οντότητα κατέχει τη σχέση (σε αυτήν την περίπτωση, SuperHero) ενώ ο άλλος χαρτογραφείται στη σχέση. Ο κωδικός στη λίστα 2 περιλαμβάνει τη χαρτογράφηση σχέσεων που εφαρμόζεται στο Ταινία τάξη.

Λίστα 2. Movie.java

 πακέτο com.geekcap.javaworld.jpa.model; εισαγωγή javax.persistence.CascadeType; εισαγωγή javax.persistence.Entity; εισαγωγή javax.persistence.FetchType; εισαγωγή javax.persistence.GeneratedValue; εισαγωγή javax.persistence.Id; εισαγωγή javax.persistence.ManyToMany; εισαγωγή javax.persistence.Table; εισαγωγή java.util.HashSet; εισαγωγή java.util.Set; @Entity @Table (name = "MOVIE") δημόσια τάξη Ταινία {@Id @GeneratedValue ιδιωτικό Integer id; ιδιωτικός τίτλος συμβολοσειράς; @ManyToMany (mappedBy = "ταινίες", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER) ιδιωτικό σύνολο superHeroes = νέο HashSet (); δημόσια ταινία () {} δημόσια ταινία (Integer id, String title) {this.id = id; this.title = τίτλος; } δημόσια ταινία (τίτλος συμβολοσειράς) {this.title = title; } δημόσιο Integer getId () {return id; } public void setId (Integer id) {this.id = id; } δημόσια συμβολοσειρά getTitle () {return title; } public void setTitle (String title) {this.title = τίτλος; } Δημόσιο σύνολο getSuperHeroes () {return superHeroes; } public void addSuperHero (SuperHero superHero) {superHeroes.add (superHero); superHero.getMovies (). προσθέστε (αυτό); } @Override public String toString () {return "Movie {" + "id =" + id + ", + title +" \ '' + '}'; }}

Οι ακόλουθες ιδιότητες εφαρμόζονται στο @ManyToMany σχολιασμός στην καταχώριση 2:

  • χαρτογραφημένος αναφέρεται στο όνομα του πεδίου στο SuperHero τάξη που διαχειρίζεται τη σχέση πολλά προς πολλά. Σε αυτήν την περίπτωση, αναφέρεται στο κινηματογράφος πεδίο, το οποίο ορίσαμε στην Λίστα 1 με το αντίστοιχο Πίνακας Συμμετοχής.
  • αλληλουχία έχει ρυθμιστεί σε CascadeType.PERSIST, που σημαίνει ότι όταν Ταινία αποθηκεύεται το αντίστοιχο SuperHero οντότητες θα πρέπει επίσης να σωθούν.
  • φέρω λέει στο EntityManager ότι πρέπει να ανακτήσει τους υπερήρωες μιας ταινίας με ενθουσιασμό: όταν φορτώνει a Ταινία, θα πρέπει επίσης να φορτώνει όλα τα αντίστοιχα SuperHero οντότητες.

Κάτι άλλο που πρέπει να σημειωθεί σχετικά με το Ταινία η τάξη είναι δική της addSuperHero () μέθοδος.

Κατά τη διαμόρφωση οντοτήτων για επιμονή, δεν αρκεί να προσθέσετε απλά έναν υπερήρωα σε μια ταινία. πρέπει επίσης να ενημερώσουμε την άλλη πλευρά της σχέσης. Αυτό σημαίνει ότι πρέπει να προσθέσουμε την ταινία στον υπερήρωα. Όταν και οι δύο πλευρές της σχέσης έχουν ρυθμιστεί σωστά, έτσι ώστε η ταινία να έχει αναφορά στον υπερήρωα και ο υπερήρωας να έχει αναφορά στην ταινία, τότε ο πίνακας συμμετοχής θα συμπληρωθεί επίσης σωστά.

Ορίσαμε τις δύο οντότητες μας. Τώρα ας δούμε τα αποθετήρια που θα χρησιμοποιήσουμε για να τα διατηρήσουμε από και προς τη βάση δεδομένων.

Υπόδειξη! Ρυθμίστε και τις δύο πλευρές του τραπεζιού

Είναι ένα κοινό λάθος να ορίζετε μόνο τη μία πλευρά της σχέσης, να διατηρείτε την οντότητα και, στη συνέχεια, να παρατηρείτε ότι ο πίνακας συμμετοχής είναι κενός. Ο καθορισμός και των δύο πλευρών της σχέσης θα το διορθώσει.

Αποθετήρια JPA

Θα μπορούσαμε να εφαρμόσουμε όλο τον κώδικα εμμονής μας απευθείας στο δείγμα εφαρμογής, αλλά η δημιουργία κλάσεων αποθετηρίου μας επιτρέπει να διαχωρίσουμε τον κώδικα επιμονής από τον κώδικα εφαρμογής. Όπως κάναμε με την εφαρμογή Βιβλία & Συγγραφείς στο Μέρος 1, θα δημιουργήσουμε ένα EntityManager και στη συνέχεια χρησιμοποιήστε το για να προετοιμάσετε δύο αποθετήρια, ένα για κάθε οντότητα που επιμένουμε.

Η λίστα 3 δείχνει τον πηγαίο κώδικα για το MovieRepository τάξη.

Λίστα 3. MovieRepository.java

 πακέτο com.geekcap.javaworld.jpa.repository; εισαγωγή com.geekcap.javaworld.jpa.model.Movie; εισαγωγή javax.persistence.EntityManager; εισαγωγή java.util.List; εισαγωγή java.util. Προαιρετικό; δημόσια κλάση MovieRepository {private EntityManager entityManager; δημόσιο MovieRepository (EntityManager entityManager) {this.entityManager = entityManager; } δημόσια Προαιρετική αποθήκευση (Ταινία ταινίας) {δοκιμάστε {entityManager.getTransaction (). begin (); entityManager.persist (ταινία); entityManager.getTransaction (). commit (); επιστροφή Optional.of (ταινία); } catch (Εξαίρεση e) {e.printStackTrace (); } επιστροφή Optional.empty (); } δημόσια Προαιρετική εύρεσηById (Integer id) {Movie movie = entityManager.find (Movie.class, id); επιστροφή ταινίας! = null; Προαιρετικό.of (ταινία): Optional.empty (); } δημόσια λίστα findAll () {return entityManager.createQuery ("from Movie"). getResultList (); } public void deleteById (Integer id) {// Ανάκτηση της ταινίας με αυτό το αναγνωριστικό Movie movie = entityManager.find (Movie.class, id); if (movie! = null) {try {// Ξεκινήστε μια συναλλαγή επειδή πρόκειται να αλλάξουμε τη βάση δεδομένων entityManager.getTransaction (). begin (); // Καταργήστε όλες τις αναφορές σε αυτήν την ταινία από superheroes movie.getSuperHeroes (). ForEach (superHero -> {superHero.getMovies (). Remove (movie);}); // Τώρα καταργήστε την ταινία entityManager.remove (ταινία); // Δέσμευση της συναλλαγής οντότηταςManager.getTransaction (). Commit (); } catch (Εξαίρεση e) {e.printStackTrace (); }}}} 

ο MovieRepository αρχικοποιείται με ένα EntityManager, στη συνέχεια το αποθηκεύει σε μια μεταβλητή μέλους για χρήση στις μεθόδους επιμονής της. Θα εξετάσουμε καθεμία από αυτές τις μεθόδους.

Μέθοδοι εμμονής

Ας αξιολογήσουμε MovieRepositoryτις μεθόδους επιμονής και δείτε πώς αλληλεπιδρούν με το EntityManagerμέθοδοι επιμονής.