Catch up on everything we announced at this year's Firebase Summit. Learn more

حفظ البيانات

يغطي هذا المستند الطرق الأربع لكتابة البيانات إلى قاعدة بيانات Firebase Realtime: تعيين وتحديث ودفع ودعم المعاملات.

طرق حفظ البيانات

يضع الكتابة أو استبدال البيانات إلى مسار محددة، مثل 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 { 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")

لنبدأ بحفظ بعض بيانات المستخدم. سنخزن كل مستخدم باسم مستخدم فريد ، وسنقوم أيضًا بتخزين الاسم الكامل وتاريخ الميلاد. نظرًا لأن كل مستخدم سيكون له اسم مستخدم فريد ، فمن المنطقي استخدام الطريقة المحددة هنا بدلاً من طريقة الدفع نظرًا لأن لديك المفتاح بالفعل ولا تحتاج إلى إنشاء واحد.

أولاً ، قم بإنشاء مرجع قاعدة بيانات لبيانات المستخدم الخاصة بك. ثم استخدام 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 في قاعدة البيانات ، يتم تعيين خصائص الكائن تلقائيًا إلى المواقع الفرعية لقاعدة البيانات بطريقة متداخلة. الآن إذا كنت انتقل إلى URL 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)
}

سيؤدي هذا إلى تحديث بيانات Grace لتشمل لقبها. إذا كنت قد استخدمت مجموعة هنا بدلا من التحديث، كان يمكن أن يكون حذف كلا full_name و date_of_birth من هاتفك hopperRef .

تدعم قاعدة بيانات Firebase Realtime أيضًا التحديثات متعددة المسارات. وهذا يعني أن التحديث يمكن الآن تحديث القيم في مواقع متعددة في قاعدة البيانات في نفس الوقت، وهي ميزة قوية التي تسمح يساعدك على إلغاء تطبيع البيانات الخاصة بك . باستخدام التحديثات متعددة المسارات ، يمكنك إضافة ألقاب لكل من Grace و Alan في نفس الوقت:

جافا
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)
}

بعد هذا التحديث ، تمت إضافة ألقاب كل من Alan و Grace:

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

لاحظ أن محاولة تحديث الكائنات عن طريق كتابة الكائنات مع المسارات المضمنة ستؤدي إلى سلوك مختلف. دعنا نلقي نظرة على ما يحدث إذا حاولت بدلاً من ذلك تحديث Grace و Alan بهذه الطريقة:

جافا
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 ، إذا كنت ترغب في معرفة وقت الالتزام ببياناتك ، فيمكنك إضافة رد اتصال مكتمل. تأخذ كل من أساليب التعيين والتحديث في مجموعات 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"
    }
  }
}

في جافا سكريبت، بيثون والعودة، ونمط تدعو push() ثم استدعاء فورا set() هو شائع بحيث SDK Firebase يتيح لك الجمع بينهما عن طريق تمرير البيانات إلى وضعها مباشرة إلى 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 Realtime على لغة أمان تتيح لك تحديد المستخدمين الذين قاموا بقراءة وكتابة الوصول إلى العقد المختلفة لبياناتك. يمكنك قراءة المزيد حول هذا الموضوع في تأمين البيانات الخاصة بك .