התמדה של Java עם JPA ו- Hibernate, חלק 2: מערכות יחסים רבות-רבות

המחצית הראשונה של מדריך זה הציגה את היסודות של ממשק ה- API של Java Persistence והראתה לך כיצד להגדיר יישום JPA באמצעות Hibernate 5.3.6 ו- Java 8. אם קראת את המדריך ולמדת את יישום הדוגמה שלו, אתה יודע את היסודות של דוגמנות ישויות JPA ויחסים רבים לאחד ב- JPA. היה לך קצת תרגול בכתיבת שאילתות בעלות שם באמצעות JPA Query Language (JPQL).

במחצית השנייה של ההדרכה נעמיק יותר עם JPA ו- Hibernate. תלמד כיצד מודל של רבים-לרבים היחסים בין Movieלבין SuperHeroגופים, להקים מאגרים בודדים לגופים אלה, וכן להתמיד הישויות למסד הנתונים H2 ב-זיכרון. תוכלו גם ללמוד עוד על תפקידן של פעולות המפל ב- JPA, ולקבל טיפים לבחירת CascadeTypeאסטרטגיה עבור ישויות במסד הנתונים. לבסוף, נרכיב יישום עובד שתוכל להריץ ב- IDE שלך או בשורת הפקודה.

הדרכה זו מתמקדת ביסודות JPA, אך הקפד לבדוק את הטיפים הבאים של Java המציגים נושאים מתקדמים יותר ב- JPA:

  • יחסי ירושה ב- JPA וב- Hibernate
  • מקשים מורכבים ב- JPA וב- Hibernate
הורד קבל את הקוד הורד את קוד המקור למשל יישומים המשמשים במדריך זה. נוצר על ידי סטיבן היינס עבור JavaWorld.

קשרים רבים-רבים ב- JPA

קשרים רבים-רבים מגדירים ישויות שבשני הצדדים של הקשר יכולות להיות מספר התייחסויות זו לזו. לדוגמא שלנו, אנחנו הולכים לדגמן סרטים וגיבורי על. בניגוד לדוגמא מחברים וספרים מחלק 1, סרט יכול להכיל מספר גיבורי על וגיבור על יכול להופיע במספר סרטים. גיבורי העל שלנו, איירונמן ות'ור, מופיעים שניהם בשני סרטים, "הנוקמים" ו"נוקמים: מלחמת האינסוף ".

כדי לדגמן מערכת יחסים רבים-רבים באמצעות JPA, נצטרך שלוש טבלאות:

  • סרט
  • גיבור על
  • SUPERHERO_MOVIES

איור 1 מציג את מודל התחום עם שלוש הטבלאות.

סטיבן היינס

שים לב SuperHero_Moviesשהוא טבלת צירוף בין הטבלאות Movieלבין SuperHero. ב- JPA, שולחן הצטרפות הוא סוג מיוחד של טבלה המאפשרת את הקשר בין רבים לרבים.

חד כיווני או דו כיווני?

ב- JPA אנו משתמשים @ManyToManyבהערה כדי לדגמן מערכות יחסים רבות-רבות. סוג זה של קשר יכול להיות חד כיווני או דו כיווני:

  • במערכת יחסים חד כיוונית רק ישות אחת בקשר מצביעה על השנייה.
  • במערכת יחסים דו כיוונית שתי הישויות מצביעות זו על זו.

הדוגמה שלנו היא דו כיוונית, כלומר סרט מצביע על כל גיבורי העל שלו, וגיבור על מצביע על כל הסרטים שלהם. במערכת יחסים דו-כיוונית, הרבה-רבים, ישות אחת מחזיקה בקשר והשנייה ממופה למערכת היחסים. אנו משתמשים mappedByבתכונה של @ManyToManyההערה כדי ליצור מיפוי זה.

רישום 1 מציג את קוד המקור של SuperHeroהכיתה.

רישום 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()) +"\'' + '}'; } } 

SuperHeroבכיתה יש כמה הסברים שצריכה להיות מוכרת מן חלק 1:

  • @Entityמזדהה SuperHeroכישות JPA.
  • @Tableממפה את SuperHeroהישות לטבלה "SUPER_HERO".

שימו לב גם Integeridלשדה, המציין שהמפתח הראשי של הטבלה ייווצר באופן אוטומטי.

הבא נצטרך להסתכל @ManyToManyואת @JoinTableההסברים.

השגת אסטרטגיות

הדבר שיש לשים לב אליו @ManyToManyבהערה הוא כיצד אנו מגדירים את אסטרטגיית האחזור , שיכולה להיות עצלה או להוטה. במקרה זה, אנחנו קבענו את fetchאל EAGER, כך שכאשר אנו לשלוף SuperHeroמבסיס הנתונים, נצטרך גם לאחזר את כל המתאימה אוטומטית Movieשל.

אם היינו בוחרים לבצע LAZYאחזור במקום זאת, היינו מאחזרים רק כל אחד Movieמכפי שגישה אליו באופן ספציפי. אחיזה עצלה אפשרית רק כאשר SuperHeroהיא מחוברת ל EntityManager; אחרת גישה לסרטים של גיבורי-על תשליך חריג. אנו רוצים להיות מסוגלים לגשת לסרטים של גיבורי על פי דרישה, ולכן במקרה זה אנו בוחרים EAGERבאסטרטגיית ההשגה.

CascadeType.PERSIST

פעולות אשד מגדירות כיצד נמשכים גיבורי העל וסרטיהם המתאימים אל מסד הנתונים וממנו. ישנן מספר תצורות מסוג מפל לבחירה, ונדבר עליהן בהמשך במדריך זה. לעת עתה, רק שים לב שהגדרנו את cascadeהתכונה ל- CascadeType.PERSIST, מה שאומר שכאשר נשמור גיבור-על הסרטים שלו יישמרו.

הצטרפו לשולחנות

JoinTableבכיתה היא שמסייעת רבים-לרבי יחסים בין SuperHeroלבין Movie. בשיעור זה, אנו מגדירים את השולחן כי ישמור את המפתחות העיקריים הוא SuperHeroואת Movieהגופים.

רישום 1 מציין ששם הטבלה יהיה SuperHero_Movies. להצטרף טור יהיה superhero_id, ואת הפוך להצטרף טור יהיה movie_id. SuperHeroישות מחזיקה את הקשר, כך להצטרף הטור יהיה מאוכלס SuperHeroהמפתח הראשי של". עמודת ההצטרפות ההפוכה ואז מתייחסת לישות בצד השני של היחסים, כלומר Movie.

בהתבסס על הגדרות אלה ברשימה 1, היינו מצפים לטבלה חדשה שנוצרה בשם SuperHero_Movies. בטבלה יהיו שתי עמודות: superhero_idהמפנות idלעמודה של SUPERHEROהטבלה, ואילו movie_idהמפנה idלעמודה של MOVIEהטבלה.

חוג הסרט

רישום 2 מציג את קוד המקור של Movieהכיתה. כזכור, ביחסים דו-כיווניים, ישות אחת מחזיקה בקשר (במקרה זה, SuperHero) ואילו השנייה ממופה לזוגיות. הקוד ברשימה 2 כולל את מיפוי היחסים שהוחל על Movieהכיתה.

רישום 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 +"\'' + '}'; } }

המאפיינים הבאים מוחלים על @ManyToManyההערה ברשימה 2:

  • mappedByמפנה את שם השדה SuperHeroבכיתה שמנהלת את מערכת היחסים בין רבים לרבים. במקרה זה, הוא מתייחס לשדה הסרטים , אותו הגדרנו ברשימה 1 עם המקביל JoinTable.
  • cascadeמוגדר ל- CascadeType.PERSIST, מה שאומר שכאשר Movieנשמר יש לשמור גם את SuperHeroהישויות המתאימות שלו .
  • fetchאומר EntityManagerכי עליו לאחזר את גיבורי העל בשקיקה : כאשר הוא טוען a Movie, עליו גם לטעון את כל SuperHeroהגופים המתאימים .

משהו אחר שצריך לציין לגבי Movieהשיעור הוא addSuperHero()השיטה שלו .

כשמגדירים תצורה של ישויות להתמדה, לא מספיק פשוט להוסיף גיבור-על לסרט; עלינו גם לעדכן את הצד השני של היחסים. זה אומר שאנחנו צריכים להוסיף את הסרט לגיבור העל. כאשר שני הצדדים של מערכת היחסים מוגדרים כראוי, כך שלסרט יש התייחסות לגיבור העל ולגיבור העל יש התייחסות לסרט, אז גם טבלת ההצטרפות תהיה מאוכלסת כהלכה.

הגדרנו את שתי הישויות שלנו. עכשיו בואו נסתכל על המאגרים שבהם נשתמש כדי להחזיק אותם אל מסד הנתונים וממנו.

עֵצָה! הגדר את שני צידי השולחן

זו טעות נפוצה להגדיר רק צד אחד של היחסים, להתמיד בישות, ואז לבחון כי טבלת ההצטרפות ריקה. הגדרת שני הצדדים של מערכת היחסים תפתור זאת.

מאגרי JPA

נוכל ליישם את כל קוד ההתמדה שלנו ישירות ביישום הדוגמה, אך יצירת מחלקות מאגר מאפשרת לנו להפריד בין קוד ההתמדה לקוד היישום. בדיוק כמו שעשינו ביישום ספרים ומחברים בחלק 1, ניצור EntityManagerולאחר מכן נשתמש בו לאתחול של שני מאגרים, אחד לכל ישות שאנו מתמידים בה.

רישום 3 מציג את קוד המקור של MovieRepositoryהכיתה.

רישום 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(); } } } } 

MovieRepositoryמאותחל עם EntityManager, ולאחר מכן שומר אותו למשתנה חבר להשתמש בשיטות התמדה שלה. נשקול כל אחת משיטות אלה.

שיטות התמדה

בואו נסקור MovieRepositoryאת שיטות ההתמדה ונראה כיצד הן מתקשרות עם EntityManagerשיטות ההתמדה של.