שמירת נתונים

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

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

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

שמירת נתונים

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

Java
final FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("server/saving-data/fireblog");
Node.js
// Import Admin SDK
const { getDatabase } = require('firebase-admin/database');

// Get a database reference to our blog
const db = getDatabase();
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")

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

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

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

Java
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 ללא שינוי.

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

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

Java
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)
}

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

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

Java
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"
    }
  }
}

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

Java
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 SDK, אם תרצה לדעת מתי הנתונים שלך הועברו, תוכל להוסיף התקשרות חוזרת. הן שיטות ההגדרה והן של העדכון ב-SDKs אלה לוקחות התקשרות אופציונלית להשלמה הנקראת כאשר הכתיבה הוגדרה למסד הנתונים. אם השיחה לא הצליחה מסיבה כלשהי, ההתקשרות חזרה מועברת אובייקט שגיאה המציין מדוע התרחש הכשל. ב- Python ו- Go Admin SDK, כל שיטות הכתיבה חוסמות. כלומר, שיטות הכתיבה לא חוזרות עד שהכתיבה מתחייבת למסד הנתונים.

Java
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() שמייצרת מפתח ייחודי עבור כל ילד חדש . על ידי שימוש במפתחות צאצאים ייחודיים, מספר לקוחות יכולים להוסיף ילדים לאותו מיקום בו-זמנית מבלי לדאוג להתנגשויות כתיבה.

Java
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() באופן הבא:

Java
// 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()

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

Java
// 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, אתה נותן לפעולת העסקה שתי התקשרויות: פונקציית עדכון ו-callback אופציונלי. ב- Python and Go פעולת העסקה חוסמת ולכן היא מקבלת רק את פונקציית העדכון.

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

Java
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 Realtime Database יש שפת אבטחה המאפשרת לך להגדיר לאילו משתמשים יש גישת קריאה וכתיבה לצמתים שונים של הנתונים שלך. תוכל לקרוא עוד על כך ב- Secure Your Data .