מדריך מהיר למאפיין מצב שינה_עבודה_לא_לא_טרנס

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

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

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

2. בעיות טעינה עצלן

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

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

הבעיה מתעוררת כאשר השלב השני מתרחש לאחר סגירת העסקה, מה שמוביל לא LazyInitializationException.

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

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

3. דוגמה לטעינה עצלה

בואו נסתכל על ההתנהגות של טעינה עצלה בכמה תרחישים.

3.1 הגדרת ישויות ושירותים

נניח שיש לנו שתי ישויות, מִשׁתַמֵשׁ ו מסמך. אחד מִשׁתַמֵשׁ יכול להיות שיש הרבה מסמךs, ואנחנו נשתמש @אחד לרבים לתאר את הקשר ההוא. כמו כן, נשתמש @Fetch (FetchMode.SUBSELECT) ליעילות.

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

בואו נגדיר כעת את שלנו מִשׁתַמֵשׁ יֵשׁוּת:

משתמש בכיתה ציבורית @Entity {// שדות אחרים הושמטו בקיצור @OneToMany (mappedBy = "userId") @Fetch (FetchMode.SUBSELECT) רשימת פרטי docs = ArrayList חדש (); }

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

@Service מחלקה ציבורית ServiceLayer {@ UserRepository UserRepository פרטי; @Transactional (readOnly = true) countAllDocsTransactional () {long returnAllDocs (); } count longllAllDocsNonTransactional () long countAllDocs (); } countAllDocs פרטיים ארוכים () {return userRepository.findAll () .stream () .map (User :: getDocs) .mapToLong (Collection :: size) .sum (); }}

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

3.2. טעינה עצלה עם עסקה מסביב

קודם כל, בואו נשתמש בהעמסה עצלה בצורה המומלצת. אז נקרא שלנו @ Transactional שיטה בשכבת השירות:

@Test ציבורי בטל כאשר CallTransactionalMethodWithPropertyOff_thenTestPass () {SQLStatementCountValidator.reset (); docsCount ארוך = serviceLayer.countAllDocsTransactional (); assertEquals (EXPECTED_DOCS_COLLECTION_SIZE, docsCount); SQLStatementCountValidator.assertSelectCount (2); }

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

3.3. טעינה עצלה מחוץ לעסקה

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

@Test (צפוי = LazyInitializationException.class) חלל ציבורי כאשר CallNonTransactionalMethodWithPropertyOff_thenThrowException () {serviceLayer.countAllDocsNonTransactional (); }

כצפוי, זה מביא לשגיאה כמו getDocs פונקציה של מִשׁתַמֵשׁ משמש מחוץ לעסקה.

3.4. טעינה עצלה עם עסקה אוטומטית

כדי לתקן זאת, אנו יכולים להפעיל את הנכס:

spring.jpa.properties.hibernate.enable_lazy_load_no_trans = true

כשהנכס מופעל, אנחנו כבר לא מקבלים LazyInitializationException.

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

@Test ציבורי בטל כאשר CallNonTransactionalMethodWithPropertyOn_thenGetNplusOne () {SQLStatementCountValidator.reset (); docsCount ארוך = serviceLayer.countAllDocsNonTransactional (); assertEquals (EXPECTED_DOCS_COLLECTION_SIZE, docsCount); SQLStatementCountValidator.assertSelectCount (EXPECTED_USERS_COUNT + 1); }

נתקלנו בסוגיית N + 1 הידועה לשמצה, למרות שקבענו אסטרטגיית אחזור כדי להימנע ממנה!

4. השוואת הגישות

בואו נדון בקצרה ביתרונות ובחסרונות.

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

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

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

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

בסך הכל, זו לא תכונה מוכנה לייצור, ותיעוד ה- Hibernate מזהיר אותנו:

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

5. מסקנה

במדריך זה בחנו את ההתמודדות עם טעינה עצלה.

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

כמו תמיד, כל דוגמאות הקוד זמינות ב- GitHub.


$config[zx-auto] not found$config[zx-overlay] not found