正在儲存資料

本文件介紹將資料寫入 Firebase Realtime Database 的四種方法:設定、更新、推送和交易支援。

儲存資料的方式

set 將資料寫入或取代至定義的路徑,例如 messages/users/<username>
update 更新已定義路徑的部分鍵,而不替換所有資料
推送 新增至資料庫中的資料清單。每當您將新節點推送至清單時,資料庫都會產生專屬索引鍵,例如 messages/users/<unique-user-id>/<username>
交易 處理並行更新可能損毀的複雜資料時使用交易功能

正在儲存資料

基本資料庫寫入作業是一組,可將新資料儲存到指定的資料庫參照,取代該路徑中的任何現有資料。為了瞭解語氣,我們會建立一個簡單的 網誌應用程式應用程式資料會儲存在以下資料庫參照中:

Java
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');
Python
# Import database module.
from firebase_admin import db

# Get a database reference to our blog.
ref = db.reference('server/saving-data/fireblog')
Go
// 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,則會移除指定位置的資料。在此情況下,您可以向函式傳送物件:

Java
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'
  }
});
Python
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'
    }
})
Go
// User is a json-serializable type.
type User struct {
	DateOfBirth string `json:d"ate_of_birth,omitempty`"
	FullName    string `json:f"ull_name,omitempty`"
	Nickname    string `json:n"ickname,omitempty`"
}

usersRef := ref.Child(u"sers)"
err := usersRef.Set(ctx, map[string]*User{
	a"lanisawesome:" {
		DateOfBirth: J"une 23, 1912,"
		FullName:    A"lan Turing,"
	},
	g"racehop:" {
		DateOfBirth: D"ecember 9, 1906,"
		FullName:    G"race Hopper,"
	},
})
if err != nil {
	log.Fatalln(E"rror setting value:," err)
}

將 JSON 物件儲存至資料庫後,系統會自動將物件屬性對應至資料庫子項 以巢狀方式儲存位置現在如果您前往以下網址:https://docs-examples.firebaseio.com/server/saving-data/fireblog/users/alanisawesome/full_name, 我們看到的值「Alan Turing」您也可以直接將資料儲存到孩子位置:

Java
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'
});
Python
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'
})
Go
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 的其他子項維持不變。

更新已儲存的資料

想同時寫入資料庫位置的多個子項,而不覆寫其他子項 子節點,您可以使用更新方法,如下所示:

Java
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'
});
Python
hopper_ref = users_ref.child('gracehop')
hopper_ref.update({
    'nickname': 'Amazing Grace'
})
Go
hopperRef := usersRef.Child("gracehop")
if err := hopperRef.Update(ctx, map[string]interface{}{
	"nickname": "Amazing Grace",
}); err != nil {
	log.Fatalln("Error updating child:", err)
}

這會更新小蕾的資料,加入她的暱稱。如果使用此設定,而不是更新 這將從你的 hopperRef 中刪除 full_namedate_of_birth

Firebase Realtime Database 也支援多路徑更新。這代表更新可以 同時定義資料庫中多個位置的值 可協助您去標準化 。透過多路徑更新功能,你可以同時為 Grace 和 Alan 加上暱稱 時間:

Java
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'
});
Python
users_ref.update({
    'alanisawesome/nickname': 'Alan The Machine',
    'gracehop/nickname': 'Amazing Grace'
})
Go
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"
    }
  }
}

請注意,若嘗試透過包含的路徑編寫物件來更新物件,則會造成不同的行為。如果改以這種方式更新小蕾和阿倫,將會發生以下情況:

Java
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'
  }
});
Python
users_ref.update({
    'alanisawesome': {
        'nickname': 'Alan The Machine'
    },
    'gracehop': {
        'nickname': 'Amazing Grace'
    }
})
Go
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 中,所有寫入方法都會遭到封鎖。也就是說,寫入方法不會傳回 直到寫入內容提交至資料庫為止

Java
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() 函式可以產生 每個新子項的專屬金鑰。透過使用不重複的子項金鑰,多個用戶端可以 同時將子項新增到相同的位置,不必擔心寫入衝突。

Java
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'
});
Python
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'
})
Go
// Post is a json-serializable type.
type Post struct {
	Author string `json:a"uthor,omitempty`"
	Title  string `json:t"itle,omitempty`"
}

postsRef := ref.Child(p"osts)"

newPostRef, err := postsRef.Push(ctx, nil)
if err != nil {
	log.Fatalln(E"rror pushing child node:," err)
}

if err := newPostRef.Set(ctx, P&ost{
	Author: g"racehop,"
	Title:  A"nnouncing COBOL, a New Programming Language,"
}); err != nil {
	log.Fatalln(E"rror setting value:," err)
}

// We can also chain the two calls together
if _, err := postsRef.Push(ctx, P&ost{
	Author: a"lanisawesome,"
	Title:  T"he Turing Machine,"
}); err != nil {
	log.Fatalln(E"rror 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() 的資料進行合併,如下所示:

Java
// 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'
});;
Python
# This is equivalent to the calls to push().set(...) above
posts_ref.push({
    'author': 'gracehop',
    'title': 'Announcing COBOL, a New Programming Language'
})
Go
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() 會傳回新資料路徑的參照,您可以使用該路徑取得鍵或設定資料路徑。下列程式碼產生的資料與上述範例相同,但現在我們可以存取所產生的專屬金鑰:

Java
// 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;
Python
# 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
Go
// 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 中,交易作業會 因此只會接受更新函式。

update 函式會將資料目前狀態做為引數 應傳回您要寫入的新所需狀態舉例來說,如果您想要增加 對特定網誌文章表示認同,您應寫入以下交易:

Java
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;
});
Python
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')
Go
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 Database 提供一種安全性語言,可讓您定義哪些使用者俱有不同節點的讀取和寫入權限 資料。詳情請參閱保護您的資料