מדריך למסגרת החשיפה לקוטלין

1. הקדמה

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

Exposed היא ספריית קוד פתוח (רישיון אפאצ'י) שפותחה על ידי JetBrains, המספקת ממשק API Kotlin אידיומטי לכמה יישומי מסדי נתונים יחסיים תוך החלקה של ההבדלים בין ספקי מסדי נתונים.

ניתן להשתמש ב- Exposed גם כ- DSL ברמה גבוהה על SQL וגם כ- ORM קל (מיפוי-יחס). לפיכך, נסקור את שני השימושים במהלך הדרכה זו.

2. הגדרת מסגרת חשופה

Exposed עדיין לא נמצא ב- Maven Central, לכן עלינו להשתמש במאגר ייעודי:

  חשוף חשוף //dl.bintray.com/kotlin/exposed 

לאחר מכן נוכל לכלול את הספרייה:

 org.jetbrains.exposed חשוף 0.10.4 

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

 com.h2database h2 1.4.197 

אנו יכולים למצוא את הגרסה האחרונה של Exposed on Bintray ואת הגרסה האחרונה של H2 ב- Maven Central.

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

אנו מגדירים חיבורי בסיס נתונים עם ה- מאגר מידע מעמד:

Database.connect ("jdbc: h2: mem: test", driver = "org.h2.Driver")

אנחנו יכולים גם לציין a מִשׁתַמֵשׁ ו סיסמה כפרמטרים בשם:

Database.connect ("jdbc: h2: mem: test", driver = "org.h2.Driver", user = "myself", password = "secret")

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

3.1. פרמטרים נוספים

אם עלינו לספק פרמטרים אחרים של חיבור, נשתמש בעומס יתר שונה על ה- לְחַבֵּר שיטה שנותנת לנו שליטה מלאה ברכישת חיבור בסיס נתונים:

Database.connect ({DriverManager.getConnection ("jdbc: h2: mem: test; MODE = MySQL")})

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

3.2. באמצעות א מקור מידע

אם במקום זאת אנו מתחברים למסד הנתונים באמצעות מקור מידע, כפי שקורה בדרך כלל ביישומים ארגוניים (למשל, להפיק תועלת מאיחוד חיבורים), אנו יכולים להשתמש במתאים לְחַבֵּר להעמיס יותר מדי:

Database.connect (מקור נתונים)

4. פתיחת עסקה

כל פעולת בסיס נתונים ב- Exposed זקוקה לעסקה פעילה.

ה עִסקָה מתבצעת סגירה ומפעילה אותה בעסקה פעילה:

עסקה {// לעשות דברים מגניבים}

ה עִסקָה מחזיר כל מה שהסגירה מחזירה. לאחר מכן, Exposed סוגרת אוטומטית את העסקה לאחר סיום ביצוע החסימה.

4.1. להתחייב ולהחזיר

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

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

לפיכך, יש לנו לְבַצֵעַ ו גלגל לאחור שיטה זמינה:

עסקה {// האם כמה דברים מתחייבים () // האם דברים אחרים}

4.2. הצהרות רישום

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

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

עסקה {addLogger (StdOutSqlLogger) // לעשות דברים}

5. הגדרת טבלאות

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

אנו מייצגים כל טבלה עם מופע של שולחן מעמד:

אובייקט StarWarsFilms: טבלה ()

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

אובייקט StarWarsFilms: טבלה ("STAR_WARS_FILMS")

5.1. עמודות

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

אובייקט StarWarsFilms: טבלה () {val id = מספר שלם ("id"). autoIncrement (). primaryKey () val sequelId = מספר שלם ("sequel_id"). uniqueIndex () שם val = varchar ("שם", 50) מנהל val = varchar ("במאי", 50)}

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

5.2. מפתחות ראשיים

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

עם זאת, במקרה המקובל של טבלה עם מפתח ראשי שלם, Exposed מספק שיעורים IntIdTable ו LongIdTable המגדירים את המפתח עבורנו:

אובייקט StarWarsFilms: IntIdTable () {val sequelId = מספר שלם ("sequel_id"). uniqueIndex () val name = varchar ("name", 50) val director = varchar ("director", 50)}

יש גם א UUIDTable; יתר על כן, אנו יכולים להגדיר גרסאות משלנו על ידי סיווג משנה IdTable.

5.3. מפתחות זרים

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

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

אובייקט שחקנים: טבלה () {val sequelId = מספר שלם ("sequel_id") .uniqueIndex (). הפניות (StarWarsFilms.sequelId) שם val = varchar ("שם", 50)}

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

val sequelId = הפניה ("sequel_id", StarWarsFilms.sequelId) .uniqueIndex ()

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

val filmId = הפניה ("film_id", StarWarsFilms)

5.4. יצירת שולחנות

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

עסקה {SchemaUtils.create (StarWarsFilms, Players) // לעשות דברים}

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

6. שאילתות

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

6.1. בחר הכל

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

שאילתת val = StarWarsFilms.selectAll ()

שאילתה היא ניתן לנידון, אז זה תומך לכל אחד:

query.forEach {assertTrue {it [StarWarsFilms.sequelId]> = 7}}

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

6.2. בחירת קבוצת משנה של עמודות

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

StarWarsFilms.slice (StarWarsFilms.name, StarWarsFilms.director) .selectAll () .forEach {assertTrue {it [StarWarsFilms.name] .startsWith ("The")}}

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

StarWarsFilms.slice (StarWarsFilms.name.countDistinct ())

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

6.3. סינון עם איפה ביטויים

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

זהו ביטוי שבו:

{(StarWarsFilms.director כמו "J.J.%") ו- (StarWarsFilms.sequelId שווה 7)}

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

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

val select = StarWarsFilms.select {...} assertEquals (1, select.count ())

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

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

val sequelNo = 7 StarWarsFilms.select {StarWarsFilms.sequelId> = sequelNo}

6.4. סינון מתקדם

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

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

query.withDistinct (true) .forEach {...}

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

query.limit (20, offset = 40) .forEach {...}

שיטות אלה מחזירות חדשה שאילתאכדי שנוכל לשרשר אותם בקלות.

6.5. להזמיןעל ידי ו קְבוּצָהעל ידי

ה Query.orderBy השיטה מקבלת רשימת עמודות הממופה ל- סדר המיון ערך המציין אם המיון צריך להיות עולה או יורד:

query.orderBy (StarWarsFilms.name to SortOrder.ASC)

בעוד שהקיבוץ לפי עמודה אחת או יותר, שימושי במיוחד בעת שימוש בפונקציות צבירות (ראה סעיף 6.2), מושג באמצעות groupBy שיטה:

StarWarsFilms .slice (StarWarsFilms.sequelId.count (), StarWarsFilms.director) .selectAll () .groupBy (StarWarsFilms.director)

6.6. מצטרף

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

(נגני StarWarsFilms innerJoin) .selectAll ()

הנה הראינו הצטרף פנימי, אך יש לנו גם שמאל, ימין וצירוף עם אותו עיקרון.

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

(נגני StarWarsFilms innerJoin). בחר {StarWarsFilms.sequelId eq Players.sequelId}

במקרה הכללי, צורת ההצטרפות המלאה היא הבאה:

val complexJoin = הצטרפות (StarWarsFilms, שחקנים, onColumn = StarWarsFilms.sequelId, otherColumn = Players.sequelId, joinType = JoinType.INNER, additionalConstraint = {StarWarsFilms.sequelId שווה 8}) complexJoin.selectAll ()

6.7. כינוס

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

(נגני StarWarsFilms innerJoin) .selectAll () .forEach {assertEquals (זה [StarWarsFilms.sequelId], זה [Players.sequelId])}

למעשה, בדוגמה שלעיל, StarWarsFilms.sequelId ו Players.sequelId הם עמודות שונות.

עם זאת, כאשר אותה טבלה מופיעה יותר מפעם אחת בשאילתה, ייתכן שנרצה לתת לה כינוי. לשם כך אנו משתמשים ב- כינוי פוּנקצִיָה:

המשך val = StarWarsFilms.alias ("המשך")

נוכל להשתמש בכינוי קצת כמו טבלה:

הצטרף (StarWarsFilms, sequel, additionalConstraint = {sequel [StarWarsFilms.sequelId] eq StarWarsFilms.sequelId + 1}). SelectAll (). עבור כל {assertEquals (זה [sequel [StarWarsFilms.sequelId]], זה [StarWarsFilms.sequel. )}

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

סרט המשך [StarWarsFilms.sequelId]

7. הצהרות

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

7.1. הכנסת נתונים

כדי להוסיף נתונים, אנו קוראים לאחת הגרסאות של ה- לְהַכנִיס פוּנקצִיָה. כל הגרסאות מסתגרות:

StarWarsFilms.insert {it [name] = "הג'די האחרון" זה [sequelId] = 8 זה [במאי] = "ריאן ג'ונסון"}

בסגירה למעלה מעורבים שני אובייקטים בולטים:

  • זֶה (הסגר עצמו) הוא מופע של StarWarsFilms מעמד; לכן אנו יכולים לגשת לעמודות, שהם מאפיינים, לפי שמם הבלתי מוסמך
  • זה (פרמטר הסגירה) הוא InsertStatement; אניt הוא מבנה דמוי מפה עם חריץ להכנסת כל עמודה

7.2. חילוץ ערכי עמודות לעלייה אוטומטית

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

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

val id = StarWarsFilms.insertAndGetId {it [name] = "הג'די האחרון" it [sequelId] = 8 it [director] = "Rian Johnson"} assertEquals (1, id.value)

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

val insert = StarWarsFilms.insert {it [name] = "The Force Awakens" it [sequelId] = 7 it [director] = "J.J. Abrams"} assertEquals (2, הכניס [ערך StarWarsFilms.id] ?.)

7.3. עדכון נתונים

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

StarWarsFilms.update ({StarWarsFilms.sequelId שווה 8}) {it [name] = "פרק VIII - הג'די האחרון"}

אנו יכולים לראות את השימוש בביטוי שבו בשילוב עם UpdateStatement סגירת מעגל. למעשה, UpdateStatement ו InsertStatement לשתף את מרבית ה- API וההיגיון באמצעות מעמד-על נפוץ, UpdateBuilder, המספק את היכולת לקבוע את ערך העמודה באמצעות סוגריים מרובעים אידיומטיים.

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

StarWarsFilms.update ({StarWarsFilms.sequelId שווה 8}) {עם (SqlExpressionBuilder) {it.update (StarWarsFilms.sequelId, StarWarsFilms.sequelId + 1)}}

זהו אובייקט המספק מפעילי אינפיקס (כמו ועוד, מִינוּס וכן הלאה) שנוכל לבנות הוראות עדכון.

7.4. מחיקת נתונים

לבסוף, אנו יכולים למחוק נתונים באמצעות ה- deleteWhere שיטה:

StarWarsFilms.deleteWhere ({StarWarsFilms.sequelId שווה 8})

8. ה- API של DAO, ORM קל משקל

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

עם זאת, ל- Exposed יש גם API DAO ברמה גבוהה יותר המהווה ORM פשוט. בואו עכשיו נצלול לזה.

8.1. ישויות

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

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

class StarWarsFilm (id: EntityID): Entity (id) {object object: EntityClass (StarWarsFilms) var sequelId by StarWarsFilms.sequelId var name by StarWarsFilms.name var director by StarWarsFilms.director}

בואו ננתח את ההגדרה לעיל חלק אחר חלק.

בשורה הראשונה אנו יכולים לראות כי ישות היא מחלקה המרחיבה יֵשׁוּת. יש לזהות עם סוג מסוים, במקרה זה, Int.

class StarWarsFilm (id: EntityID): Entity (id) {

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

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

אובייקט נלווה: EntityClass (StarWarsFilms)

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

var sequelId מאת StarWarsFilms.sequelId var var על ידי StarWarsFilms.name מנהל var על ידי StarWarsFilms.director

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

8.2. הכנסת נתונים

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

val theLastJedi = StarWarsFilm.new {name = "The Jedi Last" sequelId = 8 במאי = "ריאן ג'ונסון"}

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

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

assertEquals (1, theLastJedi.id.value) // קריאת המזהה גורמת לשטיפה

השווה התנהגות זו עם ה- לְהַכנִיס שיטה מסעיף 7.1., אשר מוציא מיד הצהרה כנגד מסד הנתונים. כאן אנו עובדים ברמה גבוהה יותר של הפשטה.

8.3. עדכון ומחיקה של אובייקטים

כדי לעדכן שורה, אנו פשוט מקצים למאפיינים שלה:

theLastJedi.name = "פרק VIII - הג'די האחרון"

בזמן למחוק אובייקט שאנו מכנים לִמְחוֹק על זה:

theLastJedi.delete ()

כמו עם חָדָשׁ, העדכון והפעולות מבוצעים בעצלתיים.

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

8.4. שאילתות

באמצעות ה- API של DAO, אנו יכולים לבצע שלושה סוגים של שאילתות.

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

val סרטים = StarWarsFilm.all ()

כדי לטעון אובייקט יחיד לפי מזהה אליו אנו קוראים findById:

val theLastJedi = StarWarsFilm.findById (1)

אם אין אובייקט עם תעודת הזהות הזו, findById החזרות ריק.

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

val films = StarWarsFilm.find {StarWarsFilms.sequelId שווה 8}

8.5. עמותות רבות לאחד

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

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

אובייקט משתמשים: IntIdTable () {val name = varchar ("name", 50)} אובייקט UserRatings: IntIdTable () {val value = long ("value") val film = reference ("film", StarWarsFilms) val user = reference ("משתמש", משתמשים)}

לאחר מכן נכתוב את הישויות המתאימות. בואו נשמיט את מִשׁתַמֵשׁ הישות, שהיא טריוויאלית, ועוברים ישר ל דירוג משתמש מעמד:

מחלקה UserRating (id: EntityID): IntEntity (id) {אובייקט נלווה: IntEntityClass (UserRatings) var ערך לפי UserRatings.value var סרט מאת StarWarsFilm הפניה On UserRatings.film var משתמש על ידי משתמש שהוזכר On UserRatings.user}

בפרט, שימו לב ל הפניה מופעלת שיטת infix קוראים למאפיינים המייצגים אסוציאציות. התבנית היא הבאה: א var הַצהָרָה, על ידי הישות המוזכרת, הפניה מופעלת עמודת ההפניה.

מאפיינים שהוכרזו כך מתנהגים כמאפיינים רגילים, אך ערכם הוא האובייקט המשויך:

val someUser = User.new {name = "Some User"} val rating = UserRating.new {value = 9 user = someUser film = theLastJedi} assertEquals (theLastJedi, rating.film)

8.6. אגודות אופציונליות

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

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

val user = reference ("user", Users) .nullable ()

ואז נשתמש optionalReferencedOn במקום הפניה מופעלת בישות:

משתמש var לפי משתמש optionalReferencedOn UserRatings.user

בדרך זו, ה מִשׁתַמֵשׁ הנכס יהיה בטל.

8.7. עמותות אחד לרבים

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

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

class StarWarsFilm (id: EntityID): Entity (id) {// מאפיינים אחרים הוסיפו דירוגי val על ידי UserRating referrersOn UserRatings.film}

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

theLastJedi.ratings.forEach {...}

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

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

UserRating.new {value = 8 user = someUser film = theLastJedi}

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

8.8. עמותות רבות-רבות

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

אובייקט שחקנים: IntIdTable () {val firstname = varchar ("firstname", 50) val lastname = varchar ("lastname", 50)} class Actor (id: EntityID): IntEntity (id) {אובייקט נלווה: IntEntityClass (Actors) שם פרטי var על ידי Actors.firstname var lastname על ידי Actors.lastname}

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

אובייקט StarWarsFilmActors: טבלה () {val starWarsFilm = הפניה ("starWarsFilm", StarWarsFilms) .primaryKey (0) val actor = הפניה ("שחקן", שחקנים) .primaryKey (1)}

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

לבסוף, אנו יכולים לחבר את טבלת השיוך עם ה- StarWarsFilm יֵשׁוּת:

מחלקה StarWarsFilm (id: EntityID): IntEntity (id) {אובייקט נלווה: IntEntityClass (StarWarsFilms) // מאפיינים אחרים שהוגדרו על ידי השחקן באמצעות StarWarsFilmActors}

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

למעשה, עלינו להשתמש במספר עסקאות:

// ראשית, צור את הסרט val film = transaction {StarWarsFilm.new {name = "The Last Jedi" sequelId = 8 במאי = "ריאן ג'ונסון" r}} // ואז, צור את השחקן val actor = transaction {Actor.new {firstname = "Daisy" lastname = "Ridley"}} // לבסוף, קשר את שני העסקים יחד {film.actors = SizedCollection (listOf (שחקן))}

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

9. מסקנה

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

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


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