حفظ البيانات

يغطي هذا المستند الطرق الأربع لكتابة البيانات إلى قاعدة بيانات 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 ، فسنرى القيمة "Alan Turing". يمكنك أيضًا حفظ البيانات مباشرةً في موقع الطفل:

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

في 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 Realtime على لغة أمان تتيح لك تحديد المستخدمين الذين قاموا بقراءة وكتابة الوصول إلى العقد المختلفة لبياناتك. يمكنك قراءة المزيد عنها في تأمين بياناتك .