این سند چهار روش برای نوشتن دادهها در Firebase Realtime Database را پوشش میدهد: تنظیم، بهروزرسانی، ارسال، و پشتیبانی از تراکنشها.
روشهای ذخیره کردن دادهها
| تنظیم کردن | نوشتن یا جایگزین کردن دادهها در مسیر تعریفشده، مانند messages/users/<username> |
| بهروزرسانی | بهروزرسانی برخیاز کلیدها برای مسیر تعریفشده بدون جایگزین کردن همه دادهها |
| فشار دادن | به فهرست دادهها در پایگاه داده اضافه کنید. هربار که گره جدیدی را به فهرست اضافه میکنید، پایگاه داده شما کلید یکتایی مثل messages/users/<unique-user-id>/<username> تولید میکند |
| تراکنش | هنگام کار با دادههای پیچیدهای که ممکن است بهدلیل بهروزرسانیهای همزمان خراب شوند، از تراکنشها استفاده کنید |
درحال ذخیره کردن دادهها
عملیات نوشتن پایگاه داده اصلی مجموعهای است که دادههای جدید را در مرجع پایگاه داده مشخصشده ذخیره میکند و جایگزین هر داده موجود در آن مسیر میشود. برای درک set، برنامه وبلاگنویسی سادهای میسازیم. دادههای برنامهتان در این مرجع پایگاه داده ذخیره میشود:
جاوا
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 بهجای روش push استفاده کنید، زیرا قبلاً کلید را دارید و نیازی به ایجاد آن ندارید.
ابتدا، یک مرجع پایگاه داده برای دادههای کاربر خود ایجاد کنید. سپس از 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 بروید، مقدار «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 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) }
پساز این بهروزرسانی، نام مستعار هر دو 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، اگر میخواهید بدانید دادههایتان چه زمانی ثبت شده است، میتوانید یک فراخوانی برگشتی تکمیل اضافه کنید. هر دو روش تنظیم و بهروزرسانی در این کیتهای توسعه نرمافزار یک فراخوانی برگشتی تکمیل اختیاری را میپذیرند که هنگام ثبت نوشتن در پایگاه داده فراخوانی میشود. اگر تماس بهدلیلی ناموفق باشد، فراخوانی برگشتی یک شیء خطا را ارسال میکند که دلیل ناموفق بودن را نشان میدهد. در 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 به شما امکان میدهد با ارسال دادههایی که باید مستقیماً به push() تنظیم شوند، آنها را بهصورت زیر ترکیب کنید:
جاوا
// No Java equivalentNode.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 بخوانیم.
درحال ذخیره کردن دادههای تراکنشی
هنگام کار با دادههای پیچیده که ممکن است با تغییرات همزمان خراب شوند، مانند شمارندههای افزایشی، کیت توسعه نرمافزار یک عملیات تراکنش ارائه میدهد.
در 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(¤tValue); 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 Database دارای زبان امنیتی است که به شما امکان میدهد تعریف کنید کدام کاربران به گرههای مختلف دادههای شما دسترسی خواندن و نوشتن دارند. میتوانید در ایمن کردن دادهها درباره آن بیشتر بخوانید.