ذخیره داده ها

این سند چهار روش برای نوشتن داده ها در Firebase Realtime Database شما را پوشش می دهد: تنظیم، به روز رسانی، فشار، و پشتیبانی تراکنش ها.

راه های ذخیره داده ها

مجموعه داده ها را در یک مسیر تعریف شده بنویسید یا جایگزین کنید، مانند 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 استفاده کنید، زیرا از قبل کلید را دارید و نیازی به ایجاد آن ندارید.

ابتدا یک مرجع پایگاه داده برای داده های کاربر خود ایجاد کنید. سپس از 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 Database همچنین از به روز رسانی های چند مسیری پشتیبانی می کند. این بدان معناست که به‌روزرسانی می‌تواند همزمان مقادیر را در چندین مکان در پایگاه داده شما به‌روزرسانی کند، یک ویژگی قدرتمند که به شما کمک می‌کند داده‌های خود را غیرعادی کنید . با استفاده از به‌روزرسانی‌های چند مسیره، می‌توانید نام مستعار را به 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)
}

پس از این به روز رسانی، هر دو آلن و گریس نام مستعار خود را اضافه کردند:

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

افزودن پاسخ به تماس تکمیلی

در 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"
    }
  }
}

در جاوا اسکریپت، پایتون و 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 عملیات تراکنش را ارائه می دهد.

در جاوا و Node.js، شما به عملیات تراکنش دو فراخوان می دهید: یک تابع به روز رسانی و یک فراخوان تکمیل اختیاری. در پایتون و گو، عملیات تراکنش مسدود می شود و بنابراین فقط تابع به روز رسانی را می پذیرد.

تابع به روز رسانی وضعیت فعلی داده ها را به عنوان آرگومان می گیرد و باید حالت دلخواه جدیدی را که می خواهید بنویسید برگرداند. برای مثال، اگر می‌خواهید تعداد رأی‌های موافق در یک پست وبلاگ خاص را افزایش دهید، تراکنشی مانند زیر می‌نویسید:

جاوا
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 و کلاینت های جاوا نسخه داخلی خود را از هر داده فعالی حفظ می کنند. وقتی داده نوشته می شود، ابتدا در این نسخه محلی نوشته می شود. سپس مشتری آن داده ها را با پایگاه داده و سایر مشتریان بر اساس "بهترین تلاش" همگام می کند.

در نتیجه، همه نوشته‌ها در پایگاه داده بلافاصله رویدادهای محلی را راه‌اندازی می‌کنند، حتی قبل از اینکه داده‌ای در پایگاه داده نوشته شود. این بدان معناست که وقتی برنامه ای را با استفاده از Firebase می نویسید، برنامه شما بدون در نظر گرفتن تاخیر شبکه یا اتصال به اینترنت، پاسخگو باقی می ماند.

پس از برقراری مجدد اتصال، مجموعه مناسبی از رویدادها را دریافت خواهیم کرد تا مشتری با وضعیت فعلی سرور، بدون نیاز به نوشتن کد سفارشی، "پیگیری" کند.

امنیت داده های شما

Firebase Realtime Database دارای یک زبان امنیتی است که به شما امکان می دهد تعیین کنید کدام کاربران به گره های مختلف داده شما دسترسی خواندن و نوشتن دارند. می توانید اطلاعات بیشتری در مورد آن در Secure Your Data بخوانید.