סוגים מותאמים אישית במצב שינה וביאור @Type

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

תרדמת שינה מפשטת את הטיפול בנתונים בין SQL ל- JDBC על ידי מיפוי המודל Object Oriented ב- Java עם המודל Relational במסדי נתונים. למרות ש מיפוי של שיעורי Java בסיסיים הוא מובנה ב- Hibernate, ומיפוי של סוגים מותאמים אישית לרוב מורכב.

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

2. סוגי מיפוי שינה

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

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

במדריך זה נתמקד במיפוי סוגי הערכים המסווגים עוד ל:

  • סוגי בסיסים - מיפוי עבור סוגי Java בסיסיים
  • ניתן להטמיעה - מיפוי לסוגי ג'אווה מרוכבים / POJO
  • אוספים - מיפוי לאוסף מסוג Java בסיסי ומורכב

3. תלות Maven

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

 org.hibernate hibernate-core 5.3.6.Final 

4. סוגים מותאמים אישית במצב שינה

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

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

4.1. יישום BasicType

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

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

אז בואו ניישם את שלנו LocalDateString סוג, שמאחסן LocalDate סוג Java כ- VARCHAR:

מחלקה ציבורית LocalDateStringType מרחיב AbstractSingleColumnStandardBasicType {גמר סטטי ציבורי LocalDateStringType INSTANCE = LocalDateStringType חדש (); public LocalDateStringType () {super (VarcharTypeDescriptor.INSTANCE, LocalDateStringJavaDescriptor.INSTANCE); } @ עקירה ציבורית מחרוזת getName () {להחזיר "LocalDateString"; }}

הדבר החשוב ביותר בקוד זה הוא הפרמטרים של הקונסטרוקטור. ראשית, הוא מופע של SqlTypeDescriptor, שהוא ייצוג מסוג SQL של ​​Hibernate, שהוא VARCHAR לדוגמא שלנו. והטיעון השני הוא מופע של JavaTypeDescriptor המייצג את סוג Java.

עכשיו, אנחנו יכולים ליישם א LocalDateStringJavaDescriptor לאחסון ואחזור LocalDate כמו VARCHAR:

מחלקה ציבורית LocalDateStringJavaDescriptor מרחיב AbstractTypeDescriptor {ציבורי סטטי ציבורי LocalDateStringJavaDescriptor INSTANCE = חדש LocalDateStringJavaDescriptor (); LocalDateStringJavaDescriptor () ציבורי () {super (LocalDate.class, ImmutableMutabilityPlan.INSTANCE); } // שיטות אחרות}

לאחר מכן, עלינו לעקוף לַעֲטוֹף ו לְגוֹלֵל שיטות להמרת סוג Java ל- SQL. נתחיל עם ה- לְגוֹלֵל:

@ ביטול ציבורי X לעטוף (ערך LocalDate, סוג מחלקה, אפשרויות WrapperOptions) {if (value == null) להחזיר null; אם (String.class.isAssignableFrom (type)) return (X) LocalDateType.FORMATTER.format (value); לזרוק לא ידוע (סוג); }

לאחר מכן, לַעֲטוֹף שיטה:

@Override לעטוף LocalDate ציבורי (ערך X, אפשרויות WrapperOptions) {if (value == null) להחזיר null; אם (String.class.isInstance (value)) להחזיר LocalDate.from (LocalDateType.FORMATTER.parse (((CharSequence) ערך)); לזרוק unknownWrap (value.getClass ()); }

לְגוֹלֵל() נקרא במהלך הצהרה מוכנה מחייב להמיר LocalDate לסוג מחרוזת, הממופה ל- VARCHAR. כְּמוֹ כֵן, לַעֲטוֹף() נקרא במהלך ResultSet אחזור להמרה חוּט לג'אווה LocalDate.

לבסוף, אנו יכולים להשתמש בסוג המותאם אישית שלנו בכיתת הישות שלנו:

@Entity @Table (name = "OfficeEmployee") משרדים ציבוריים OfficeEmployee {@Column @Type (type = "com.baeldung.hibernate.customtypes.LocalDateStringType") תאריך פרטי LocalDateOfJoining; // שדות ושיטות אחרות}

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

4.2. יישום UserType

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

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

מחלקה ציבורית PhoneNumberType מיישמת UserType {@Override public int [] sqlTypes () {return new int [] {Types.INTEGER, Types.INTEGER, Types.INTEGER}; } @Override Public Class returnClass () {return PhoneNumber.class; } // שיטות אחרות} 

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

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

קודם ה nullSafeGet שיטה:

@Override אובייקט ציבורי nullSafeGet (ResultSet rs, מחרוזת [] שמות, SharedSessionContractImplementor הפעלה, בעל אובייקט) זורק HibernateException, SQLException {int countryCode = rs.getInt (names [0]); אם (rs.wasNull ()) להחזיר null; int cityCode = rs.getInt (names [1]); מספר int = rs.getInt (שמות [2]); PhoneNumber employeeNumber = PhoneNumber חדש (countryCode, cityCode, מספר); החזר עובד מספר; }

לאחר מכן, nullSafeSet שיטה:

@Override public void nullSafeSet (PreparedStatement st, Value Object, int index, SharedSessionContractImplementor session) זורק HibernateException, SQLException {if (Objects.isNull (value)) {st.setNull (index, Types.INTEGER); st.setNull (אינדקס + 1, Types.INTEGER); st.setNull (אינדקס + 2, Types.INTEGER); } אחר ערך {PhoneNumber employeeNumber = (PhoneNumber); st.setInt (אינדקס, employeeNumber.getCountryCode ()); st.setInt (אינדקס + 1, employeeNumber.getCityCode ()); st.setInt (אינדקס + 2, employeeNumber.getNumber ()); }}

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

@Entity @Table (name = "OfficeEmployee") מחלקה ציבורית OfficeEmployee {@Columns (column = {@Column (name = "country_code"), @Column (name = "city_code"), @Column (name = "number") }) @Type (type = "com.baeldung.hibernate.customtypes.PhoneNumberType") פרטי טלפון מספר עובד מספר; // שדות ושיטות אחרות}

4.3. יישום CompositeUserType

יישום UserType עובד היטב עבור סוגים פשוטים. עם זאת, מיפוי סוגי Java מורכבים (עם אוספים וסוגים מרוכבים מדורגים) זקוקים לתחכום רב יותר. מצב שינה מאפשר לנו למפות סוגים כאלה על ידי יישום ה- CompositeUserType מִמְשָׁק.

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

class class AddressType מיישם CompositeUserType {@Override public String [] getPropertyNames () {return new String [] {"addressLine1", "addressLine2", "city", "country", "מיקוד"}; } @Override ציבור הקלד [] getPropertyTypes () {החזר סוג חדש [] {StringType.INSTANCE, StringType.INSTANCE, StringType.INSTANCE, StringType.INSTANCE, IntegerType.INSTANCE}; } // שיטות אחרות}

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

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

@Override אובייקט ציבורי getPropertyValue (רכיב אובייקט, מאפיין int) זורק HibernateException {Address empAdd = (Address) רכיב; switch (property) {case 0: return empAdd.getAddressLine1 (); מקרה 1: להחזיר empAdd.getAddressLine2 (); מקרה 2: להחזיר empAdd.getCity (); מקרה 3: להחזיר empAdd.getCountry (); מקרה 4: להחזיר Integer.valueOf (empAdd.getZipCode ()); } זרוק IllegalArgumentException (מאפיין + "הוא אינדקס נכסים לא חוקי עבור סוג המחלקה" + component.getClass (). getName ()); }

לבסוף, נצטרך ליישם nullSafeGet ו nullSafeSet שיטות להמרה בין סוגי Java ו- SQL. זה דומה למה שעשינו קודם לכן PhoneNumberType.

שים לב ש CompositeType'S מיושמים בדרך כלל כמנגנון מיפוי חלופי ל ניתן להטמיע סוגים.

4.4. הקלד פרמטריזציה

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

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

אז בואו נשתמש בפרמטרים שלנו SalaryType שמקבל מַטְבֵּעַ כפרמטר:

מחלקה ציבורית SalaryType מיישמת CompositeUserType, DynamicParameterizedType {private String localCurrency; @ Override public void setParameterValues ​​(Properties parameters) {this.localCurrency = parameters.getProperty ("currency"); } // יישומי שיטות אחרים מ- CompositeUserType}

שימו לב שדילגנו על ה- CompositeUserType שיטות מהדוגמה שלנו להתמקד בפרמטריזציה. כאן פשוט יישמנו את מצב שינה DynamicParameterizedType, ולדרוס את setParameterValues ​​() שיטה. עכשיו ה SalaryType לקבל א מַטְבֵּעַ הפרמטר וימיר כל סכום לפני אחסונו.

נעביר את מַטְבֵּעַ כפרמטר תוך הכרזה על שכר:

@Entity @Table (name = "OfficeEmployee") OfficeEmployee בכיתה ציבורית {@Type (type = "com.baeldung.hibernate.customtypes.SalaryType", parameters = {@Parameter (name = "currency", value = "USD") }) @ עמודות (עמודות = {@ עמוד (שם = "סכום"), @ עמוד (שם = "מטבע")}) משכורת פרטית משכורת; // שדות ושיטות אחרות}

5. רישום סוגים בסיסיים

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

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

סטטי פרטי SessionFactory makeSessionFactory () {ServiceRegistry serviceRegistry = StandardServiceRegistryBuilder () .applySettings (getProperties ()). build (); MetadataSources metadataSources = MetadataSources חדש (serviceRegistry); מטא-נתונים מטא-נתונים = metadataSources.getMetadataBuilder () .applyBasicType (LocalDateStringType.INSTANCE) .build (); להחזיר metadata.getSessionFactoryBuilder (). build ()} מאפיינים סטטיים פרטיים getProperties () {// להחזיר מאפייני שינה "

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

@Entity @Table (name = "OfficeEmployee") מחלקה ציבורית OfficeEmployee {@Column @Type (type = "LocalDateString") תאריך LocalDate פרטי פרטי; // שיטות אחרות}

פה, LocalDateString הוא המפתח שאליו ה- LocalDateStringType ממופה.

לחלופין, אנו יכולים לדלג על רישום סוג על ידי הגדרה TypeDefs:

@TypeDef (name = "PhoneNumber", typeClass = PhoneNumberType.class, defaultForType = PhoneNumber.class) @Entity @Table (name = "OfficeEmployee") מחלקה ציבורית OfficeEmployee {@Columns (עמודות = {@Column (name = "country_code") ), @Column (name = "city_code"), @Column (name = "number")}) פרטי טלפון מספר עובד מספר; // שיטות אחרות}

6. מסקנה

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

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


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