GORM – מבט מקרוב

בפוסט האחרון ראינו שפלטפורמת grails עובדת מול בסיס הנתונים בעזרת GORM – טכנולוגית ORM מבוססת hibernate.

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

הכנת האפליקציה לעבודה עם בסיס נתונים

ניצור פרויקט בשם cars. ניצור domain class בשם Car עם השדה הבא:

String plateNumber

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

כדי לעשות זאת יש להגיד סכמה והרשאות משתמש מתאימות, לשים את הדרייבר בספרית lib של האפליקציה, ולהגדיר את החיבור בקובץ DataSource. את הערך dbCreate נשנה לcreate-drop כדי ליצור בכל פעם את הסכימה מחדש, ולמחוק אותה בסוף הריצה של האפליקציה.

כמו כן נוסיף את ההוראה logSql=true כדי להדפיס לקונסולה את השאילתות שרצות מול בסיס הנתונים.

אתחול המערכת והכנת נתונים

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

נוסיף את הקוד הבא למטודה init

new cars.Car(plateNumber:"12-345-67").save()

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

סנכרון מול בסיס הנתונים

ניצור controller כלשהו (בדוגמא TestController) ונגדיר את המטודה  insert שתראה כך:

 def car = Car.findByPlateNumber("12-345-67")

car.plateNumber="xxx"

car.save()

println "after save 1"

car.plateNumber="yyy"

car.save()

println "after save 2"

render "O.K"

println "after render"

נפנה לכתובת

<app-ctx>/test/insert

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

קודם כל, שימו לב לצורה בה שלפנו את האוביקט – קראנו למטודה שלא הגדרנו בשום מקום!

זה נקרא  dynamic finder ומוסבר בפוסט על תכנות דינמי.

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

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

ירושה

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

נגדיר domain class נוסף, שיראה כך:

class SportsCar extends Car{

int maximumSpeed

boolean convertible

}

נריץ את האפליקציה.

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

נוסיף לcontroller שלנו עוד מטודה polymorphQuery שתראה כך:

def list = Car.findAll()

list.each{println it.class.name}

render "OK"

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

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

static mapping = {

                tablePerHierarchy false

}

עכשיו הירושה ממומשת בצורה שונה – הטבלה car מכילה את כל השדות של מחלקת האב, והטבלה sports_car מכילה את השדות של המחלקה היורשת. כל רשומה של SportsCar מתפרסת על שתי טבלאות ושליפה דורשת join.

נריץ שוב את הcontroller ונראה את השינוי בשאילתות.

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

Second level cache

נשנה את מחלקת Car שתראה כך:

static mapping = {

 cache true

         tablePerHierarchy false //this line may be commented out at your choice

}

ניצור מטודה חדשה בcontroller – testCache:

def car = Car.get(2)

render "plate number is ${car.plateNumber}"

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

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

הנושא של caching הוא נרחב ואפשרויות השליטה בו הן רבות. הזכרתי אותו משתי סיבות:

  1. כשעושים שימוש בתשתית חזקה, מקבלים ב"חינם" תכונות מתקדמות שיהיה קשה ובחלק מהפרויקטים בלתי אפשרי לפתח לבד.
  2. אחת הטענות נגד ORM היא פגיעה בביצועים. ניתן לגבות טענה כזו בבנצ'מרק פשוט – יוצרים סקריפט של שליפה או הכנסה ומראים שהוא רץ מהר יותר ללא ORM. זוהי מתודולוגיה פגומה. נכון שישנו מחיר קטן ברמת הפעולה הבודדת, אולם הדרך הנכונה היא לחשוב על המערכת בצורה הוליסטית ולא כאוסף של פעולות בדידות. צמצום מספר הגישות לבסיס הנתונים ושימוש בתכונות כגון caching יכולים ליצור שינוי שמפצה על הפגיעה בביצועים ואף עולה עליה.

כתיבת תגובה

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

הלוגו של WordPress.com

אתה מגיב באמצעות חשבון WordPress.com שלך. לצאת מהמערכת / לשנות )

תמונת Twitter

אתה מגיב באמצעות חשבון Twitter שלך. לצאת מהמערכת / לשנות )

תמונת Facebook

אתה מגיב באמצעות חשבון Facebook שלך. לצאת מהמערכת / לשנות )

תמונת גוגל פלוס

אתה מגיב באמצעות חשבון Google+ שלך. לצאת מהמערכת / לשנות )

מתחבר ל-%s