حفظ البيانات

يغطي هذا المستند الطرق الأربع لكتابة البيانات إلى قاعدة بيانات 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)
}

سيؤدي هذا إلى تحديث بيانات غريس لتشمل لقبها. إذا كنت قد استخدمت set هنا بدلاً من التحديث، فسيؤدي ذلك إلى حذف كل من 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 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 ()

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