מיפוי דינמי עם מצב שינה

1. הקדמה

במאמר זה נחקור כמה יכולות מיפוי דינמיות של מצב שינה עם ה- @נוּסחָה, @איפה, @לְסַנֵן ו @כל ביאורים.

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

2. הגדרת פרויקט

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

 org.hibernate hibernate-core 5.4.12. com.h2database h2 1.4.194 

לגרסה הנוכחית של ליבת שינה ספרייה, עברו למייבון סנטרל.

3. עמודות מחושבות עם @נוּסחָה

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

מעמד ציבורי @Entity עובד מיישם את מספר הסימנים הפרטי {@Id @GeneratedValue (אסטרטגיה = GenerationType.IDENTITY); הכנסה פרטית ארוכה ברוטו; מיסים פרטיים מסInPercents; ציבורי ארוך getTaxJavaWay () {החזר ברוטו הכנסה * taxInPercents / 100; }}

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

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

מחלקה ציבורית @Entity עובדים מיישמת {@Id @ GeneratedValue (אסטרטגיה = GenerationType.IDENTITY) מזהה שלם פרטי פרטי; הכנסה פרטית ארוכה ברוטו; מיסים פרטיים מסInPercents; @Formula ("grossIncome * taxInPercents / 100") מס פרטי פרטי; }

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

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

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

עובד שכיר = עובד חדש (10_000L, 25); session.save (עובד); session.flush (); session.clear (); עובד = session.get (שכיר עובד, עובד.גט איד ()); assertThat (employee.getTax ()). isEqualTo (2_500L);

4. סינון ישויות עם @איפה

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

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

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

@Entity @Where (clause = "delete = false") יישום עובדים בכיתה ציבורית ניתן לבצע סידור {// ...}

ה @איפה ביאור על שיטה מכיל סעיף SQL שיתווסף לכל שאילתה או שאילתת משנה לישות זו:

employee.setDeleted (נכון); session.flush (); session.clear (); עובד = session.find (שכיר עובד, עובד.גיט ()); assertThat (עובד) .isNull ();

כמו במקרה של @נוּסחָה ביאור, מכיוון שאנחנו עוסקים ב- SQL גולמי, ה- @איפה התנאי לא יוערך מחדש עד שנזרוק את הישות למסד הנתונים ונפנה אותה מההקשר.

עד למועד זה, הישות תישאר בהקשר ותהיה נגישה באמצעות שאילתות וחיפושים מאת תְעוּדַת זֶהוּת.

ה @איפה ביאור יכול לשמש גם עבור שדה אוסף. נניח שיש לנו רשימה של טלפונים שנמחקים:

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

ואז, מה עוֹבֵד בצד, נוכל למפות אוסף של מחיקות טלפונים כדלהלן:

מחלקה ציבורית מיישמת עובדים על פי סדר {// ... @OneToMany @JoinColumn (שם = "עובד_יד") @Where (סעיף = "מחק = שקר") טלפונים פרטיים קבעו = HashSet חדש (0); }

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

employee.getPhones (). iterator (). הבא (). setDeleted (נכון); session.flush (); session.clear (); עובד = session.find (שכיר עובד, עובד.גיט ()); assertThat (employee.getPhones ()). hasSize (1); רשימת fullPhoneList = session.createQuery ("מהטלפון"). GetResultList (); assertThat (fullPhoneList) .hasSize (2);

5. סינון פרמטרי עם @לְסַנֵן

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

ה @לְסַנֵן ההערה פועלת באותה צורה כמו @איפה, אך ניתן גם להפעיל או להשבית אותו ברמת ההפעלה, וגם לבצע פרמטרים.

5.1. הגדרת ה- @לְסַנֵן

כדי להדגים כיצד @לְסַנֵן עובד, בואו הוסף תחילה את הגדרת המסנן הבאה ל- עוֹבֵד יֵשׁוּת:

@FilterDef (name = "incomeLevelFilter", parameters = @ParamDef (name = "incomeLimit", type = "int")) @Filter (name = "incomeLevelFilter", condition = "grossIncome>: incomeLimit") סוג ציבורי עובד מיישמים {

ה @FilterDef ביאור מגדיר את שם המסנן וקבוצת הפרמטרים שלו שישתתפו בשאילתה. סוג הפרמטר הוא שמו של אחד מסוגי ה- Hibernate (Type, UserType או CompositeUserType), במקרה שלנו, int.

ה- @ FilterDef הערה יכולה להיות ממוקמת בסוג או ברמת החבילה. שים לב שהוא לא מציין את תנאי המסנן עצמו (אם כי נוכל לציין את defaultCondition פָּרָמֶטֶר).

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

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

5.2. גישה לגופים מסוננים

הבדל נוסף של @לְסַנֵן מ @איפה האם זה @לְסַנֵן אינו מופעל כברירת מחדל. עלינו לאפשר זאת ברמת ההפעלה באופן ידני ולספק את ערכי הפרמטרים עבורו:

session.enableFilter ("incomeLevelFilter") .setParameter ("incomeLimit", 11_000);

עכשיו נניח שיש לנו את שלושת העובדים הבאים במסד הנתונים:

session.save (עובד חדש (10_000, 25)); session.save (עובד חדש (12_000, 25)); session.save (עובד חדש (15_000, 25));

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

רשימת עובדים = session.createQuery ("מהעובד") .getResultList (); assertThat (עובדים) .hasSize (2);

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

session = HibernateUtil.getSessionFactory (). openSession (); עובדים = session.createQuery ("מהעובד"). getResultList (); assertThat (עובדים) .hasSize (3);

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

עובד שכיר = session.get (שכיר עובד, 1); assertThat (employee.getGrossIncome ()). isEqualTo (10_000);

5.3. @לְסַנֵן ואחסון במטמון ברמה שנייה

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

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

זו הסיבה ש @לְסַנֵן ביאור מבטל בעצם אחסון במטמון עבור הישות.

6. מיפוי כל הפניה ישות עם @כל

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

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

6.1. מיפוי יחס עם @כל

כך נוכל להגדיר התייחסות לכל ישות שמיישמת ניתן לבצע סדרתי (כלומר לכל ישות בכלל):

@Entity מחלקה ציבורית EntityDescription מיישמת באמצעות Serializable {פרטי מחרוזת תיאור; @Any (metaDef = "EntityDescriptionMetaDef", metaColumn = @Column (name = "entity_type")) @JoinColumn (name = "entity_id") ישות פרטית שניתנת לסידרליזציה; }

ה metaDef המאפיין הוא שם ההגדרה, ו metaColumn הוא שם העמודה שישמש להבחנה בין סוג הישות (שלא כמו העמודה המפלה במיפוי ההיררכיה של הטבלה היחידה).

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

ה ישות_יד העמודה בדרך כלל לא יכולה להיות ייחודית מכיוון שלטבלאות שונות יכולות להיות מזהים חוזרים.

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

6.2. הגדרת ה- @כל מיפוי עם @AnyMetaDef

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

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

הנה איך package-info.java קובץ עם @AnyMetaDef הערה תיראה כך:

@AnyMetaDef (name = "EntityDescriptionMetaDef", metaType = "string", idType = "int", metaValues ​​= {@MetaValue (value = "Employee", targetEntity = Employee.class), @MetaValue (value = "Phone", targetEntity = Phone.class)}) חבילה com.baeldung.hibernate.pojo;

כאן ציינו את סוג ה- ישות_סוג טור (חוּט), סוג ה- ישות_יד טור (int), הערכים המקובלים ב- ישות_סוג טור ("עוֹבֵד" ו "מכשיר טלפון") וסוגי הישות המתאימים.

נניח שיש לנו עובד עם שני טלפונים המתוארים כך:

עובד שכיר = עובד חדש (); טלפון טלפון 1 = טלפון חדש ("555-45-67"); טלפון טלפון 2 = טלפון חדש ("555-89-01"); עובד. getPhones (). הוסף (טלפון 1); עובד. getPhones (). הוסף (טלפון 2);

כעת נוכל להוסיף מטא נתונים תיאוריים לכל שלוש הישויות, למרות שיש להן סוגים שונים שאינם קשורים:

EntityDescription employeeDescription = EntityDescription חדש ("שלח לכנס בשנה הבאה", עובד); EntityDescription phone1Description = חדש EntityDescription ("טלפון ביתי (לא להתקשר אחרי השעה 22:00)", טלפון 1); EntityDescription phone2Description = חדש EntityDescription ("טלפון לעבודה", טלפון 1);

7. מסקנה

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

קוד המקור של המאמר זמין באתר GitHub.


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