این سند چهار روش برای نوشتن دادهها در 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");
نود جی اس
// 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 به set ارسال کنید. ارسال 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);
نود جی اس
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"));
نود جی اس
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 را بدون تغییر باقی میگذارد.
بهروزرسانی دادههای ذخیرهشده
اگر میخواهید همزمان روی چندین فرزند از یک مکان پایگاه داده بنویسید بدون اینکه سایر گرههای فرزند را رونویسی کنید، میتوانید از متد update مطابق شکل زیر استفاده کنید:
جاوا
DatabaseReference hopperRef = usersRef.child("gracehop"); Map<String, Object> hopperUpdates = new HashMap<>(); hopperUpdates.put("nickname", "Amazing Grace"); hopperRef.updateChildrenAsync(hopperUpdates);
نود جی اس
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) }
این کار دادههای گریس را بهروزرسانی میکند تا نام مستعار او را نیز شامل شود. اگر به جای update از set در اینجا استفاده میکردید، هم full_name و هم date_of_birth از hopperRef شما حذف میشدند.
Firebase Realtime Database همچنین از بهروزرسانیهای چند مسیری پشتیبانی میکند. این بدان معناست که اکنون update میتواند مقادیر را در چندین مکان در پایگاه داده شما به طور همزمان بهروزرسانی کند، یک ویژگی قدرتمند که به شما کمک میکند دادههای خود را از حالت نرمال خارج کنید . با استفاده از بهروزرسانیهای چند مسیری، میتوانید همزمان به Grace و Alan نام مستعار اضافه کنید:
جاوا
Map<String, Object> userUpdates = new HashMap<>(); userUpdates.put("alanisawesome/nickname", "Alan The Machine"); userUpdates.put("gracehop/nickname", "Amazing Grace"); usersRef.updateChildrenAsync(userUpdates);
نود جی اس
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);
نود جی اس
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"
}
}
}افزودن یک فراخوانی تکمیلشده
در SDK های Node.js و Java Admin، اگر میخواهید بدانید چه زمانی دادههای شما ثبت شدهاند، میتوانید یک تابع فراخوانی تکمیل (commission callback) اضافه کنید. هر دو متد set و update در این SDK ها یک تابع فراخوانی تکمیل اختیاری میگیرند که وقتی نوشتن در پایگاه داده ثبت شده است، فراخوانی میشود. اگر به هر دلیلی فراخوانی ناموفق باشد، یک شیء error به تابع فراخوانی ارسال میشود که نشان میدهد چرا این شکست رخ داده است. در SDK های Python و Go Admin، همه متدهای نوشتن مسدود میشوند. یعنی متدهای نوشتن تا زمانی که نوشتنها در پایگاه داده ثبت نشوند، بر نمیگردند.
جاوا
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."); } } });
نود جی اس
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"));
نود جی اس
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 به شما امکان میدهد با ارسال مستقیم دادههایی که قرار است تنظیم شوند به push() به صورت زیر، آنها را ترکیب کنید:
جاوا
// No Java equivalentنود جی اس
// 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();
نود جی اس
// 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"); } });
نود جی اس
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 به عنوان مقدار جدید مینوشتند که منجر به یک افزایش به جای دو افزایش میشد.
اتصال به شبکه و نوشتن آفلاین
کلاینتهای فایربیس Node.js و جاوا، نسخه داخلی خود را از هرگونه داده فعال نگهداری میکنند. وقتی دادهای نوشته میشود، ابتدا در این نسخه محلی نوشته میشود. سپس کلاینت، آن دادهها را با پایگاه داده و با سایر کلاینتها بر اساس «بهترین تلاش» همگامسازی میکند.
در نتیجه، تمام نوشتهها در پایگاه داده، بلافاصله و قبل از اینکه هیچ دادهای در پایگاه داده نوشته شود، رویدادهای محلی را فعال میکنند. این بدان معناست که وقتی با استفاده از Firebase برنامهای مینویسید، برنامه شما صرف نظر از تأخیر شبکه یا اتصال به اینترنت، پاسخگو خواهد ماند.
پس از برقراری مجدد اتصال، مجموعه رویدادهای مناسب را دریافت خواهیم کرد تا کلاینت بدون نیاز به نوشتن هیچ کد سفارشی، با وضعیت فعلی سرور «هماهنگ» شود.
ایمنسازی دادههای شما
Firebase Realtime Database دارای یک زبان امنیتی است که به شما امکان میدهد تعریف کنید کدام کاربران به گرههای مختلف دادههای شما دسترسی خواندن و نوشتن دارند. میتوانید اطلاعات بیشتر در مورد آن را در بخش «دادههای خود را ایمن کنید» مطالعه کنید.