שמירת נתונים

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

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

מַעֲרֶכֶת כתוב או להחליף נתונים נתיב מוגדר, כמו 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"
    }
  }
}

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

ב- SDKs של Admin של Node.js ו- Java, אם ברצונך לדעת מתי הנתונים שלך בוצעו, אתה יכול להוסיף התקשרות חוזרת. הן שיטות ההגדרה והן העדכון ב- 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.');
  }
});

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

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

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

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