הכנס / עדכון אצווה עם מצב שינה / JPA

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

במדריך זה, נבדוק כיצד אנו יכולים להוסיף או לעדכן ישויות באמצעות אצבע שינה / JPA.

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

2. התקנה

2.1. מודל נתונים לדוגמא

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

ראשית, ניצור בית ספר יֵשׁוּת:

בית ספר בכיתה ציבורית @Entity {@Id @GeneratedValue (אסטרטגיה = GenerationType.SEQUENCE) מזהה ארוך פרטי; שם מחרוזת פרטי; @OneToMany (mappedBy = "school") תלמידים פרטיים ברשימה; // גדרים וקובעים ...}

כל אחד בית ספר יהיה אפס או יותר סטוּדֶנטs:

סטודנט בכיתה ציבורית @Entity {@Id @GeneratedValue (אסטרטגיה = GenerationType.SEQUENCE) מזהה ארוך פרטי; שם מחרוזת פרטי; בית ספר פרטי לבית הספר ManyToOne; // גדרים וקובעים ...}

2.2. מעקב אחר שאילתות SQL

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

מחלקה סטטית פרטית ProxyDataSourceInterceptor מיישם את MethodInterceptor {dataSource הסופי הפרטי DataSource; ProxyDataSourceInterceptor ציבורי (סופי DataSource dataSource) {this.dataSource = ProxyDataSourceBuilder.create (dataSource) .name ("Batch-Insert-Logger") .asJson (). countQuery (). logQueryToSysOut (). build (); } // שיטות אחרות ...}

3. התנהגות ברירת מחדל

מצב שינה אינו מאפשר אצווה כברירת מחדל. המשמעות היא שהיא תשלח הצהרת SQL נפרדת לכל פעולת הוספה / עדכון:

@Transactional @Test ציבורי בטל כאשרNotConfigured_ThenSendsInsertsSeparately () {for (int i = 0; i <10; i ++) {School school = createSchool (i); entityManager.persist (בית ספר); } entityManager.flush (); }

הנה, התמידנו 10 בית ספר ישויות. אם נסתכל ביומני השאילתות, נוכל לראות ש- Hibernate שולח כל הצהרת הכניסה בנפרד

"querySize": 1, "batchSize": 0, "queryS: [" הכנס לערכי בית הספר (שם, מזהה) (?,?) "]," params ": [[" School1 "," 1 "]] "querySize": 1, "batchSize": 0, "query": ["הכנס לערכי בית הספר (שם, מזהה) (?,?)"], "params": [["School2", "2"]] "querySize": 1, "batchSize": 0, "query": ["הכנס לערכי בית הספר (שם, מזהה) (?,?)"], "params": [["School3", "3"]] "querySize": 1, "batchSize": 0, "query": ["הכנס לערכי בית הספר (שם, מזהה) (?,?)"], "params": [["School4", "4"]] "querySize": 1, "batchSize": 0, "query": ["הכנס לערכי בית הספר (שם, מזהה) (?,?)"], "params": [["School5", "5"]] "querySize": 1, "batchSize": 0, "query": ["הכנס לערכי בית הספר (שם, מזהה) (?,?)"], "params": [["School6", "6"]] "querySize": 1, "batchSize": 0, "query": ["הכנס לערכי בית הספר (שם, מזהה) (?,?)"], "params": [["School7", "7"]] "querySize": 1, "batchSize": 0, "queryS: [" הכנס לערכי בית הספר (שם, מזהה) (?,?) "]," params ": [[" School8 "," 8 "]] "querySize": 1, "batchSize": 0, "queryS: [" הכנס לערכי בית הספר (שם, מזהה) (?,?) "]," params ": [[" School9 "," 9 "]]" querySize ": 1," batchSize ": 0," query ": [" הכנס לערכי בית הספר (שם, מזהה) (?,?) "]," params ": [[" School10 "," 10 "]]

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

אם אנחנו יוצרים EntityManager באופן ידני, עלינו להוסיף hibernate.jdbc.batch_size למאפייני שינה:

public Properties hibernateProperties () {Properties Properties = מאפיינים חדשים (); properties.put ("hibernate.jdbc.batch_size", "5"); // מאפיינים אחרים ... מאפייני החזרה; }

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

spring.jpa.properties.hibernate.jdbc.batch_size = 5

4. הכנסת אצווה לשולחן יחיד

4.1. הכנסת אצווה ללא שטיפה מפורשת

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

נשתמש בדוגמת הקוד הקודמת, אך הפעם אצווה מופעל:

@Transactional @Test ציבורי בטל כאשר מכניסים SingleTypeOfEntity_thenCreatesSingleBatch () {עבור (int i = 0; i <10; i ++) {School School = createSchool (i); entityManager.persist (בית ספר); }}

הנה התמידנו 10 בית ספר ישויות. כשאנחנו מסתכלים על היומנים, אנחנו יכולים לוודא ש- Hibernate שולח הצהרות הוספה בקבוצות:

"batch": true, "querySize": 1, "batchSize": 5, "query": ["הכנס לערכי בית הספר (שם, מזהה) (?,?)"], "params": [["School1" , "1"], ["School2", "2"], ["School3", "3"], ["School4", "4"], ["School5", "5"]] "batch": true, "querySize": 1, "batchSize": 5, "query": ["הכנס לערכי בית הספר (שם, מזהה) (?,?)"], "params": [["School6", "6" ], ["School7", "7"], ["School8", "8"], ["School9", "9"], ["School10", "10"]]

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

4.2. הכנס אצווה עם שטיפה מפורשת

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

קודם כל, ההקשר להתמדה מאחסן ישויות שזה עתה נוצרו וגם אלה שהשתנו בזיכרון. Hibernate שולח שינויים אלה למסד הנתונים כאשר העסקה מסונכרנת. בדרך כלל זה קורה בסוף העסקה. למרות זאת, יִעוּד EntityManager.flush () מפעיל גם סנכרון עסקאות.

שנית, הקשר ההתמדה משמש כמטמון ישויות, ובכך מכונה גם מטמון ברמה הראשונה. כדי לנקות ישויות בהקשר ההתמדה, אנחנו יכולים להתקשר EntityManager.clear ().

לכן, כדי להפחית את עומס הזיכרון במהלך אצווה, אנו יכולים להתקשר EntityManager.flush () ו EntityManager.clear () על קוד היישום שלנו, בכל פעם שמגיעים לגודל האצווה:

@Transactional @Test ציבורי בטל whenFlushingAfterBatch_ThenClearsMemory () {for (int i = 0; i 0 && i% BATCH_SIZE == 0) {entityManager.flush (); entityManager.clear (); } בית ספר בית ספר = createSchool (i); entityManager.persist (בית ספר); }}

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

5. הכנסת אצווה למספר טבלאות

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

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

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

@Transactional @Test ציבורי בטל כאשרThereAreMultipleEntities_ThenCreatesNewBatch () {עבור (int i = 0; i 0 && i% BATCH_SIZE == 0) {entityManager.flush (); entityManager.clear (); } בית ספר בית ספר = createSchool (i); entityManager.persist (בית ספר); תלמיד firstStudent = createStudent (בית ספר); סטודנט secondStudent = createStudent (בית ספר); entityManager.persist (firstStudent); entityManager.persist (secondStudent); }}

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

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

"batch": true, "querySize": 1, "batchSize": 1, "query": ["הכנס לערכי בית הספר (שם, מזהה) (?,?)"], "params": [["School1" , "1"]] "אצווה": נכון, "querySize": 1, "batchSize": 2, "query": ["הכנס לתלמידים (שם, school_id, id) ערכים (?,?,?)"] , "params": [["Student-School1", "1", "2"], ["Student-School1", "1", "3"]] "אצווה": true, "querySize": 1, "batchSize": 1, "query": ["הכנס לערכי בית הספר (שם, מזהה) (?,?)"], "params": [["School2", "4"]] "batch": true, "querySize": 1, "batchSize": 2, "query": ["הכנס לתלמיד (שם, school_id, id) ערכים (?,?,?)"], "params": [["Student-School2" , "4", "5"], ["Student-School2", "4", "6"]] "אצווה": נכון, "querySize": 1, "batchSize": 1, "query": [" הכנס לערכי בית הספר (שם, מזהה) (?,?) "]," params ": [[" School3 "," 7 "]]" batch ": true," querySize ": 1," batchSize ": 2, "query": ["הכנס לתלמיד (שם, school_id, id) ערכים (?,?,?)"], "params": [["Student-School3", "7", "8"], [" Student-School3 "," 7 "," 9 "]] שורות יומן אחרות ...

כדי לאצווה את כל הכנס הצהרות מאותו סוג ישות, עלינו להגדיר את hibernate.order_inserts תכונה.

אנו יכולים להגדיר את מאפיין ה- Hibernate באופן ידני באמצעות EntityManagerFactory:

public Properties hibernateProperties () {Properties Properties = מאפיינים חדשים (); properties.put ("hibernate.order_inserts", "true"); // נכסים אחרים ... נכסי החזרה; }

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

spring.jpa.properties.hibernate.order_inserts = נכון

לאחר הוספת נכס זה, נקבל אצווה אחת עבור בית ספר הוספות ו -2 מנות עבור סטוּדֶנט הוספות:

"batch": true, "querySize": 1, "batchSize": 5, "query": ["הכנס לערכי בית הספר (שם, מזהה) (?,?)"], "params": [["School6" , "16"], ["School7", "19"], ["School8", "22"], ["School9", "25"], ["School10", "28"]] "batch": true, "querySize": 1, "batchSize": 5, "query": ["הכנס לערכי התלמיד (שם, school_id, id) (?,?,?)"], "params": [["Student- School6 "," 16 "," 17 "], [" Student-School6 "," 16 "," 18 "], [" Student-School7 "," 19 "," 20 "], [" Student-School7 " , "19", "21"], ["Student-School8", "22", "23"]] "batch": true, "querySize": 1, "batchSize": 5, "query": [" הכנס לתלמיד (שם, school_id, id) ערכים (?,?,?) "]," params ": [[" Student-School8 "," 22 "," 24 "], [" Student-School9 "," 25 "," 26 "], [" Student-School9 "," 25 "," 27 "], [" Student-School10 "," 28 "," 29 "], [" Student-School10 "," 28 " , "30"]]

6. עדכון אצווה

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

כדי לאפשר זאת, נגדיר hibernate.order_updates ו hibernate.jdbc.batch_versioned_data נכסים.

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

public Properties hibernateProperties () {Properties Properties = מאפיינים חדשים (); properties.put ("hibernate.order_updates", "true"); properties.put ("hibernate.batch_versioned_data", "true"); // מאפיינים אחרים ... מאפייני החזרה; }

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

spring.jpa.properties.hibernate.order_updates = אמת spring.jpa.properties.hibernate.batch_versioned_data = true

לאחר קביעת התצורה של מאפיינים אלה, מצב שינה צריך לקבץ הצהרות עדכון בקבוצות:

@Transactional @Test ציבורי בטל כאשרUpdatingEntities_thenCreatesBatch () {TypedQuery schoolQuery = entityManager.createQuery ("SELECT s from School s", School.class); רשימת allSchools = schoolQuery.getResultList (); עבור (בית ספר בית הספר: allSchools) {school.setName ("עודכן_" + school.getName ()); }}

כאן עדכנו את ישויות בית הספר ו- Hibernate שולח הצהרות SQL בשתי קבוצות בגודל 5:

"אצווה": נכון, "querySize": 1, "batchSize": 5, "שאילתה": ["עדכן שם ערכת בית הספר =? איפה id =?"], "params": [["עודכן_סקול 1", "1" ], ["Updated_School2", "2"], ["Updated_School3", "3"], ["Updated_School4", "4"], ["Updated_School5", "5"]] "אצווה": נכון, "querySize ": 1," batchSize ": 5," query ": [" עדכן שם ערכת בית הספר =? איפה id =? "]," Params ": [[" Updated_School6 "," 6 "], [" Updated_School7 "," 7 "], [" Updated_School8 "," 8 "], [" Updated_School9 "," 9 "], [" Updated_School10 "," 10 "]]

7. @תְעוּדַת זֶהוּת אסטרטגיית דור

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

מאז ישויות בדוגמאות שלנו להשתמש GenerationType.SEQUENCE מחולל מזהים, מצב שינה מאפשר פעולות אצווה:

@Id @GeneratedValue (אסטרטגיה = GenerationType.SEQUENCE) מזהה ארוך פרטי;

8. סיכום

במאמר זה, בדקנו הוספות ועדכונים אצווהיים באמצעות Hibernate / JPA.

בדוק את דוגמאות הקוד עבור מאמר זה ב- Github.