שמירת נתונים

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

דרכים לשמירת נתונים

מַעֲרֶכֶת כתוב או להחליף נתונים נתיב מוגדר, כמו messages/users/<username>
עדכון עדכן חלק מהמפתחות לנתיב מוגדר מבלי להחליף את כל הנתונים
לִדחוֹף הוסף רשימה של נתונים במסד הנתונים. בכל פעם שאתה דוחף צומת חדש על רשימה, מסד הנתונים שלך מייצר מפתח ייחודי, כמו messages/users/<unique-user-id>/<username>
עִסקָה השתמש בעסקאות בעת עבודה עם נתונים מורכבים שעלולים להיפגם על ידי עדכונים במקביל

שמירת נתונים

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

ג'אווה
final FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("server/saving-data/fireblog");
Node.js
// Import Admin SDK
const admin = require('firebase-admin');

// Get a database reference to our blog
const db = admin.database();
const ref = db.ref('server/saving-data/fireblog');
פִּיתוֹן
# Import database module.
from firebase_admin import db

# Get a database reference to our blog.
ref = db.reference('server/saving-data/fireblog')
ללכת
// Create a database client from App.
client, err := app.Database(ctx)
if err != nil {
	log.Fatalln("Error initializing database client:", err)
}

// Get a database reference to our blog.
ref := client.NewRef("server/saving-data/fireblog")

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

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

ג'אווה
public static class User {

  public String date_of_birth;
  public String full_name;
  public String nickname;

  public User(String dateOfBirth, String fullName) {
    // ...
  }

  public User(String dateOfBirth, String fullName, String nickname) {
    // ...
  }

}

DatabaseReference usersRef = ref.child("users");

Map<String, User> users = new HashMap<>();
users.put("alanisawesome", new User("June 23, 1912", "Alan Turing"));
users.put("gracehop", new User("December 9, 1906", "Grace Hopper"));

usersRef.setValueAsync(users);
Node.js
const usersRef = ref.child('users');
usersRef.set({
  alanisawesome: {
    date_of_birth: 'June 23, 1912',
    full_name: 'Alan Turing'
  },
  gracehop: {
    date_of_birth: 'December 9, 1906',
    full_name: 'Grace Hopper'
  }
});
פִּיתוֹן
users_ref = ref.child('users')
users_ref.set({
    'alanisawesome': {
        'date_of_birth': 'June 23, 1912',
        'full_name': 'Alan Turing'
    },
    'gracehop': {
        'date_of_birth': 'December 9, 1906',
        'full_name': 'Grace Hopper'
    }
})
ללכת

// User is a json-serializable type.
type User struct {
	DateOfBirth string `json:"date_of_birth,omitempty"`
	FullName    string `json:"full_name,omitempty"`
	Nickname    string `json:"nickname,omitempty"`
}

usersRef := ref.Child("users")
err := usersRef.Set(ctx, map[string]*User{
	"alanisawesome": {
		DateOfBirth: "June 23, 1912",
		FullName:    "Alan Turing",
	},
	"gracehop": {
		DateOfBirth: "December 9, 1906",
		FullName:    "Grace Hopper",
	},
})
if err != nil {
	log.Fatalln("Error setting value:", err)
}

כאשר אובייקט JSON נשמר במסד הנתונים, מאפייני האובייקט ממופים באופן אוטומטי למיקומים של בת מסד הנתונים בצורה מקוננת. עכשיו, אם אתה מנווט את כתובת האתר https://docs-examples.firebaseio.com/server/saving-data/fireblog/users/alanisawesome/full_name , נצטרך לראות את הערך "אלן טיורינג". תוכל גם לשמור נתונים ישירות במיקום ילד:

ג'אווה
usersRef.child("alanisawesome").setValueAsync(new User("June 23, 1912", "Alan Turing"));
usersRef.child("gracehop").setValueAsync(new User("December 9, 1906", "Grace Hopper"));
Node.js
const usersRef = ref.child('users');
usersRef.child('alanisawesome').set({
  date_of_birth: 'June 23, 1912',
  full_name: 'Alan Turing'
});
usersRef.child('gracehop').set({
  date_of_birth: 'December 9, 1906',
  full_name: 'Grace Hopper'
});
פִּיתוֹן
users_ref.child('alanisawesome').set({
    'date_of_birth': 'June 23, 1912',
    'full_name': 'Alan Turing'
})
users_ref.child('gracehop').set({
    'date_of_birth': 'December 9, 1906',
    'full_name': 'Grace Hopper'
})
ללכת
if err := usersRef.Child("alanisawesome").Set(ctx, &User{
	DateOfBirth: "June 23, 1912",
	FullName:    "Alan Turing",
}); err != nil {
	log.Fatalln("Error setting value:", err)
}

if err := usersRef.Child("gracehop").Set(ctx, &User{
	DateOfBirth: "December 9, 1906",
	FullName:    "Grace Hopper",
}); err != nil {
	log.Fatalln("Error setting value:", err)
}

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

{
  "users": {
    "alanisawesome": {
      "date_of_birth": "June 23, 1912",
      "full_name": "Alan Turing"
    },
    "gracehop": {
      "date_of_birth": "December 9, 1906",
      "full_name": "Grace Hopper"
    }
  }
}

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

עדכון הנתונים השמורים

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

ג'אווה
DatabaseReference hopperRef = usersRef.child("gracehop");
Map<String, Object> hopperUpdates = new HashMap<>();
hopperUpdates.put("nickname", "Amazing Grace");

hopperRef.updateChildrenAsync(hopperUpdates);
Node.js
const usersRef = ref.child('users');
const hopperRef = usersRef.child('gracehop');
hopperRef.update({
  'nickname': 'Amazing Grace'
});
פִּיתוֹן
hopper_ref = users_ref.child('gracehop')
hopper_ref.update({
    'nickname': 'Amazing Grace'
})
ללכת
hopperRef := usersRef.Child("gracehop")
if err := hopperRef.Update(ctx, map[string]interface{}{
	"nickname": "Amazing Grace",
}); err != nil {
	log.Fatalln("Error updating child:", err)
}

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

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

ג'אווה
Map<String, Object> userUpdates = new HashMap<>();
userUpdates.put("alanisawesome/nickname", "Alan The Machine");
userUpdates.put("gracehop/nickname", "Amazing Grace");

usersRef.updateChildrenAsync(userUpdates);
Node.js
const usersRef = ref.child('users');
usersRef.update({
  'alanisawesome/nickname': 'Alan The Machine',
  'gracehop/nickname': 'Amazing Grace'
});
פִּיתוֹן
users_ref.update({
    'alanisawesome/nickname': 'Alan The Machine',
    'gracehop/nickname': 'Amazing Grace'
})
ללכת
if err := usersRef.Update(ctx, map[string]interface{}{
	"alanisawesome/nickname": "Alan The Machine",
	"gracehop/nickname":      "Amazing Grace",
}); err != nil {
	log.Fatalln("Error updating children:", err)
}

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

{
  "users": {
    "alanisawesome": {
      "date_of_birth": "June 23, 1912",
      "full_name": "Alan Turing",
      "nickname": "Alan The Machine"
    },
    "gracehop": {
      "date_of_birth": "December 9, 1906",
      "full_name": "Grace Hopper",
      "nickname": "Amazing Grace"
    }
  }
}

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

ג'אווה
Map<String, Object> userNicknameUpdates = new HashMap<>();
userNicknameUpdates.put("alanisawesome", new User(null, null, "Alan The Machine"));
userNicknameUpdates.put("gracehop", new User(null, null, "Amazing Grace"));

usersRef.updateChildrenAsync(userNicknameUpdates);
Node.js
const usersRef = ref.child('users');
usersRef.update({
  'alanisawesome': {
    'nickname': 'Alan The Machine'
  },
  'gracehop': {
    'nickname': 'Amazing Grace'
  }
});
פִּיתוֹן
users_ref.update({
    'alanisawesome': {
        'nickname': 'Alan The Machine'
    },
    'gracehop': {
        'nickname': 'Amazing Grace'
    }
})
ללכת
if err := usersRef.Update(ctx, map[string]interface{}{
	"alanisawesome": &User{Nickname: "Alan The Machine"},
	"gracehop":      &User{Nickname: "Amazing Grace"},
}); err != nil {
	log.Fatalln("Error updating children:", err)
}

תוצאות זה בהתנהגות שונה, כלומר להחליף את כולו /users צומת:

{
  "users": {
    "alanisawesome": {
      "nickname": "Alan The Machine"
    },
    "gracehop": {
      "nickname": "Amazing Grace"
    }
  }
}

הוספת התקשרות חוזרת להשלמה

ב- Node.js ו- Java Admin SDKs, אם אתה רוצה לדעת מתי הנתונים שלך בוצעו, תוכל להוסיף התקשרות חזרה להשלמה. הן שיטות ההגדרה והן העדכון במערכות SDK אלה לוקחות שיחת חזרה להשלמה אופציונלית הנקראת כאשר הכתיבה הועברה למסד הנתונים. אם השיחה לא הצליחה מסיבה כלשהי, החזרה מועברת אובייקט שגיאה המציין מדוע התרחשה הכשל. ב- Python ו- Go Admin SDK, כל שיטות הכתיבה חוסמות. כלומר, שיטות הכתיבה אינן חוזרות עד שהכתיבה מחויבת למסד הנתונים.

ג'אווה
DatabaseReference dataRef = ref.child("data");
dataRef.setValue("I'm writing data", new DatabaseReference.CompletionListener() {
  @Override
  public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
    if (databaseError != null) {
      System.out.println("Data could not be saved " + databaseError.getMessage());
    } else {
      System.out.println("Data saved successfully.");
    }
  }
});
Node.js
dataRef.set('I\'m writing data', (error) => {
  if (error) {
    console.log('Data could not be saved.' + error);
  } else {
    console.log('Data saved successfully.');
  }
});

שמירת רשימות נתונים

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

// NOT RECOMMENDED - use push() instead!
{
  "posts": {
    "0": {
      "author": "gracehop",
      "title": "Announcing COBOL, a New Programming Language"
    },
    "1": {
      "author": "alanisawesome",
      "title": "The Turing Machine"
    }
  }
}

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

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

ג'אווה
public static class Post {

  public String author;
  public String title;

  public Post(String author, String title) {
    // ...
  }

}

DatabaseReference postsRef = ref.child("posts");

DatabaseReference newPostRef = postsRef.push();
newPostRef.setValueAsync(new Post("gracehop", "Announcing COBOL, a New Programming Language"));

// We can also chain the two calls together
postsRef.push().setValueAsync(new Post("alanisawesome", "The Turing Machine"));
Node.js
const newPostRef = postsRef.push();
newPostRef.set({
  author: 'gracehop',
  title: 'Announcing COBOL, a New Programming Language'
});

// we can also chain the two calls together
postsRef.push().set({
  author: 'alanisawesome',
  title: 'The Turing Machine'
});
פִּיתוֹן
posts_ref = ref.child('posts')

new_post_ref = posts_ref.push()
new_post_ref.set({
    'author': 'gracehop',
    'title': 'Announcing COBOL, a New Programming Language'
})

# We can also chain the two calls together
posts_ref.push().set({
    'author': 'alanisawesome',
    'title': 'The Turing Machine'
})
ללכת

// Post is a json-serializable type.
type Post struct {
	Author string `json:"author,omitempty"`
	Title  string `json:"title,omitempty"`
}

postsRef := ref.Child("posts")

newPostRef, err := postsRef.Push(ctx, nil)
if err != nil {
	log.Fatalln("Error pushing child node:", err)
}

if err := newPostRef.Set(ctx, &Post{
	Author: "gracehop",
	Title:  "Announcing COBOL, a New Programming Language",
}); err != nil {
	log.Fatalln("Error setting value:", err)
}

// We can also chain the two calls together
if _, err := postsRef.Push(ctx, &Post{
	Author: "alanisawesome",
	Title:  "The Turing Machine",
}); err != nil {
	log.Fatalln("Error pushing child node:", err)
}

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

{
  "posts": {
    "-JRHTHaIs-jNPLXOQivY": {
      "author": "gracehop",
      "title": "Announcing COBOL, a New Programming Language"
    },
    "-JRHTHaKuITFIhnj02kE": {
      "author": "alanisawesome",
      "title": "The Turing Machine"
    }
  }
}

ב- JavaScript, Python ו- Go, דפוס קורא push() ומיד קורא set() הוא כל כך נפוץ כי Firebase SDK מאפשרת לשלב אותם על ידי העברת הנתונים כדי להיות מוגדר ישירות push() כדלקמן:

ג'אווה
// No Java equivalent
Node.js
// This is equivalent to the calls to push().set(...) above
postsRef.push({
  author: 'gracehop',
  title: 'Announcing COBOL, a New Programming Language'
});;
פִּיתוֹן
# This is equivalent to the calls to push().set(...) above
posts_ref.push({
    'author': 'gracehop',
    'title': 'Announcing COBOL, a New Programming Language'
})
ללכת
if _, err := postsRef.Push(ctx, &Post{
	Author: "gracehop",
	Title:  "Announcing COBOL, a New Programming Language",
}); err != nil {
	log.Fatalln("Error pushing child node:", err)
}

קבלת המפתח הייחודי שנוצר על ידי דחיפה ()

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

ג'אווה
// Generate a reference to a new location and add some data using push()
DatabaseReference pushedPostRef = postsRef.push();

// Get the unique ID generated by a push()
String postId = pushedPostRef.getKey();
Node.js
// Generate a reference to a new location and add some data using push()
const newPostRef = postsRef.push();

// Get the unique key generated by push()
const postId = newPostRef.key;
פִּיתוֹן
# Generate a reference to a new location and add some data using push()
new_post_ref = posts_ref.push()

# Get the unique key generated by push()
post_id = new_post_ref.key
ללכת
// Generate a reference to a new location and add some data using Push()
newPostRef, err := postsRef.Push(ctx, nil)
if err != nil {
	log.Fatalln("Error pushing child node:", err)
}

// Get the unique key generated by Push()
postID := newPostRef.Key

כפי שאתה יכול לראות, אתה יכול לקבל את הערך של המפתח הייחודי שלך push() הפניה.

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

שמירת נתוני עסקאות

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

ב- Java וב- Node.js, אתה נותן לפעולת העסקה שני שיחות טלפון חוזרות: פונקציית עדכון וחיוג חוזר להשלמה. ב- Python and Go פעולת העסקה נחסמת ולכן היא מקבלת רק את פונקציית העדכון.

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

ג'אווה
DatabaseReference upvotesRef = ref.child("server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes");
upvotesRef.runTransaction(new Transaction.Handler() {
  @Override
  public Transaction.Result doTransaction(MutableData mutableData) {
    Integer currentValue = mutableData.getValue(Integer.class);
    if (currentValue == null) {
      mutableData.setValue(1);
    } else {
      mutableData.setValue(currentValue + 1);
    }

    return Transaction.success(mutableData);
  }

  @Override
  public void onComplete(
      DatabaseError databaseError, boolean committed, DataSnapshot dataSnapshot) {
    System.out.println("Transaction completed");
  }
});
Node.js
const upvotesRef = db.ref('server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes');
upvotesRef.transaction((current_value) => {
  return (current_value || 0) + 1;
});
פִּיתוֹן
def increment_votes(current_value):
    return current_value + 1 if current_value else 1

upvotes_ref = db.reference('server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes')
try:
    new_vote_count = upvotes_ref.transaction(increment_votes)
    print('Transaction completed')
except db.TransactionAbortedError:
    print('Transaction failed to commit')
ללכת
fn := func(t db.TransactionNode) (interface{}, error) {
	var currentValue int
	if err := t.Unmarshal(&currentValue); err != nil {
		return nil, err
	}
	return currentValue + 1, nil
}

ref := client.NewRef("server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes")
if err := ref.Transaction(ctx, fn); err != nil {
	log.Fatalln("Transaction failed to commit:", err)
}

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

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

קישוריות רשת וכתיבה לא מקוונת

לקוחות Firebase Node.js ו- Java שומרים על גרסה פנימית משלהם של כל נתונים פעילים. כאשר הנתונים נכתבים, הם נכתבים תחילה לגרסה המקומית הזו. לאחר מכן הלקוח מסנכרן נתונים אלה עם מסד הנתונים ועם לקוחות אחרים על בסיס 'המאמץ הטוב ביותר'.

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

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

אבטחת הנתונים שלך

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