מצב שינה לא הצליח לאתחל את ה- proxy - ללא מושב

1. סקירה כללית

בעבודה עם מצב שינה, אולי נתקלנו בשגיאה שאומרת: org.hibernate.LazyInitializationException: לא ניתן היה לאתחל את ה- proxy - אין הפעלה.

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

2 הבנת השגיאה

גישה לאובייקט טעון עצלן מחוץ להקשר של מושב שינה פתוח יביא לחריג זה.

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

  • מוֹשָׁב הוא הקשר התמדה המייצג שיחה בין יישום למסד הנתונים
  • טעינה איטית פירושו שהאובייקט לא יוטען אל ה- מוֹשָׁב הקשר עד לגישה אליו בקוד.
  • תרדמת שינה יוצרת דינמיקה אובייקט פרוקסי מחלקה משנה שתגיע למסד הנתונים רק כאשר אנו משתמשים באובייקט לראשונה.

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

3. דוגמה ל LazyInitializationException

בואו נראה את החריג בתרחיש קונקרטי.

אנחנו רוצים ליצור פשוט מִשׁתַמֵשׁ אובייקט עם תפקידים קשורים. בואו נשתמש ב- JUnit כדי להדגים את ה- LazyInitializationException שְׁגִיאָה.

3.1. שיעור שירות שינה

ראשית, נגדיר א HibernateUtil מחלקה ליצור SessionFactory עם תצורה.

נשתמש בזיכרון HSQLDB מאגר מידע.

3.2. ישויות

הנה שלנו מִשׁתַמֵשׁ ישות:

@Entity @Table (name = "user") משתמש בכיתה ציבורית {@Id @GeneratedValue (אסטרטגיה = GenerationType.IDENTITY) @Column (name = "id") מזהה פרטי פרטי; @Column (name = "first_name") פרטי מחרוזת שם פרטי; @Column (name = "last_name") שם משפחה פרטי מחרוזת; @OneToMany תפקידים סטים פרטיים; } 

והקשורים תַפְקִיד ישות:

@Entity @Table (name = "role") תפקיד בכיתה ציבורית {@Id @GeneratedValue (אסטרטגיה = GenerationType.IDENTITY) @Column (name = "id") מזהה פרטי פרטי; @Column (name = "role_name") תפקיד מחרוזת roleName; }

כפי שאנו רואים, יש קשר בין אחד לרבים מִשׁתַמֵשׁ ו תַפְקִיד.

3.3. יצירת משתמש עם תפקידים

לאחר מכן, בואו ניצור שניים תַפְקִיד חפצים:

מנהל תפקידים = תפקיד חדש ("מנהל מערכת"); תפקיד dba = תפקיד חדש ("DBA");

לאחר מכן, אנו יוצרים a מִשׁתַמֵשׁ עם התפקידים:

משתמש משתמש = משתמש חדש ("בוב", "סמית"); user.addRole (admin); user.addRole (dba);

לבסוף, אנו יכולים לפתוח מושב ולהתמיד באובייקטים:

מושב מושב = sessionFactory.openSession (); session.beginTransaction (); user.getRoles (). forEach (תפקיד -> session.save (תפקיד)); session.save (משתמש); session.getTransaction (). commit (); session.close ();

3.4. להביא תפקידים

בתרחיש הראשון נראה כיצד להביא תפקידי משתמש בצורה נכונה:

@Test ציבורי בטל כאשר AccessUserRolesInsideSession_thenSuccess () {משתמש detachedUser = createUserWithRoles (); מושב מושב = sessionFactory.openSession (); session.beginTransaction (); משתמש persistentUser = session.find (User.class, detachedUser.getId ()); Assert.assertEquals (2, persistentUser.getRoles (). Size ()); session.getTransaction (). commit (); session.close (); }

כאן, אנו ניגשים לאובייקט בתוך ההפעלה, ולכן אין שום שגיאה.

3.5. השגת כישלון תפקידים

בתרחיש השני, נקרא a getRoles שיטה מחוץ לפגישה:

@ מבחן ציבורי בטל כאשר AccessUserRolesOutsideSession_thenThrownException () {User detachedUser = createUserWithRoles (); מושב מושב = sessionFactory.openSession (); session.beginTransaction (); משתמש persistentUser = session.find (User.class, detachedUser.getId ()); session.getTransaction (). commit (); session.close (); throw.expect (LazyInitializationException.class); System.out.println (persistentUser.getRoles (). Size ()); }

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

4. כיצד להימנע מהשגיאה

בואו נסתכל על ארבעה פתרונות שונים כדי להתגבר על השגיאה.

4.1. מושב פתוח בשכבה עליונה

הנוהג הטוב ביותר הוא לפתוח מושב בשכבת ההתמדה, למשל באמצעות תבנית DAO.

אנו יכולים לפתוח את ההפעלה בשכבות העליונות כדי לגשת לאובייקטים המשויכים באופן בטוח. לדוגמא, אנו יכולים לפתוח את ההפעלה ב- נוף שִׁכבָה.

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

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

4.2. מדליק הפעל_עצלן_עבור_לא_עברות תכונה

מאפיין שינה זה משמש להצהרת מדיניות עולמית לאיסוף עצמים עם עצלן.

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

שימוש במאפיין זה כדי להימנע LazyInitializationException לא מומלץ להשתמש בשגיאה מכיוון שהיא תאט את ביצועי היישום שלנו. זה בגלל שאנחנו בסופו של דבר יש בעיה n + 1. במילים פשוטות, זה אומר SELECT אחד עבור מִשׁתַמֵשׁ ו- N בחירות נוספות כדי להשיג את התפקידים של כל משתמש.

גישה זו אינה יעילה ונחשבת גם למניעת תבנית.

4.3. באמצעות FetchType.EAGER אִסטרָטֶגִיָה

אנו יכולים להשתמש באסטרטגיה זו יחד עם א @אחד לרבים ביאור, למשל:

@OneToMany (fetch = FetchType.EAGER) @JoinColumn (name = "user_id") תפקידים סטים פרטיים;

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

אז זה הרבה יותר קל להכריז על לָהוּט סוג אחזור במקום לאסוף במפורש את האוסף עבור רוב הזרימות העסקיות השונות.

4.4. באמצעות הצטרפות אחזור

אנחנו יכולים להשתמש ב- הצטרף לאישוף הנחיה ב JPQL להביא את האוסף המשויך לפי דרישה, למשל:

בחר u מהמשתמש u JOIN FETCH u.roles

לחלופין, נוכל להשתמש בממשק ה- API של קריטריון שינה:

קריטריונים לקריטריונים = session.createCriteria (User.class); criteria.setFetchMode ("תפקידים", FetchMode.EAGER);

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

זהו הפיתרון היעיל והגרגירי ביותר להימנע מ- LazyInitializationException שְׁגִיאָה.

5. מסקנה

במאמר זה ראינו כיצד להתמודד עם ה- org.hibernate.LazyInitializationException: לא ניתן היה לאתחל את ה- proxy - אין הפעלה שְׁגִיאָה.

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

לבסוף, ראינו כיצד גישת ההצטרפות היא דרך טובה למנוע את השגיאה.

כמו תמיד, הקוד זמין ב- GitHub.