このドキュメントでは、Firebase Realtime Database にデータを書き込むための 4 つの方法 (set、update、push、および transactions サポート) について説明します。
データを保存する方法
設定 | 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")
いくつかのユーザーデータを保存することから始めましょう。各ユーザーを一意のユーザー名で保存し、氏名と生年月日も保存します。各ユーザーには一意のユーザー名があるため、ここでは push メソッドの代わりに set メソッドを使用するのが理にかなっています。これは、既にキーを取得しており、キーを作成する必要がないためです。
まず、ユーザー データへのデータベース参照を作成します。次に、 set()
/ setValue()
を使用して、ユーザー オブジェクトをデータベースに保存し、ユーザーのユーザー名、フル ネーム、および誕生日を指定します。 set 文字列、数値、ブール値、 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) }
上記の 2 つの例 (両方の値をオブジェクトとして同時に書き込み、子の場所に別々に書き込む) では、同じデータがデータベースに保存されます。
{ "users": { "alanisawesome": { "date_of_birth": "June 23, 1912", "full_name": "Alan Turing" }, "gracehop": { "date_of_birth": "December 9, 1906", "full_name": "Grace Hopper" } } }
最初の例では、データを監視しているクライアントで 1 つのイベントのみがトリガーされますが、2 番目の例では 2 つのイベントがトリガーされます。 usersRef
にデータが既に存在する場合、最初の方法ではデータが上書きされますが、2 番目の方法では個別の子ノードの値のみが変更され、 usersRef
の他の子ノードは変更されないことに注意してください。
保存データの更新
他の子ノードを上書きせずに、データベースの場所の複数の子に同時に書き込みたい場合は、以下に示すように update メソッドを使用できます。
ジャワ
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 のデータが更新され、彼女のニックネームが含まれるようになります。 update の代わりに set here を使用した場合、 date_of_birth
からfull_name
と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);
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" } } }
パスを含めてオブジェクトを書き込んでオブジェクトを更新しようとすると、動作が異なることに注意してください。 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 では、データがいつコミットされたかを知りたい場合は、完了コールバックを追加できます。これらの SDK の set メソッドと update メソッドはどちらも、書き込みがデータベースにコミットされたときに呼び出されるオプションの完了コールバックを受け取ります。何らかの理由で呼び出しが失敗した場合、コールバックには、失敗が発生した理由を示すエラー オブジェクトが渡されます。 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
として保存されます。これは、1 人の作成者だけが投稿を追加する場合には機能しますが、共同ブログ アプリケーションでは、多くのユーザーが同時に投稿を追加する可能性があります。 2 人の投稿者が/posts/2
に同時に書き込みを行った場合、投稿の 1 つが他の投稿者によって削除されます。
これを解決するために、 Firebase クライアントには、新しい child ごとに一意のキーを生成する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 では、トランザクション操作に更新関数とオプションの完了コールバックの 2 つのコールバックを指定します。 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
で呼び出すことができるためです。
上記のコードがトランザクション関数なしで実行され、2 つのクライアントが同時にそれをインクリメントしようとすると、両方とも新しい値として1
を書き込み、結果として 2 つではなく 1 つのインクリメントになります。
ネットワーク接続とオフライン書き込み
Firebase Node.js および Java クライアントは、アクティブなデータの独自の内部バージョンを維持します。データが書き込まれると、最初にこのローカル バージョンに書き込まれます。次にクライアントは、そのデータをデータベースおよび他のクライアントと「ベスト エフォート」ベースで同期します。
その結果、データベースへのすべての書き込みは、データがデータベースに書き込まれる前に、すぐにローカル イベントをトリガーします。つまり、Firebase を使用してアプリケーションを作成すると、ネットワークの遅延やインターネット接続に関係なく、アプリの応答性が維持されます。
接続が再確立されると、カスタム コードを記述することなく、クライアントが現在のサーバーの状態に「追いつく」ことができるように、適切な一連のイベントを受け取ります。
データの保護
Firebase Realtime Database には、データのさまざまなノードへの読み取りおよび書き込みアクセス権を持つユーザーを定義できるセキュリティ言語があります。詳細については、データの保護を参照してください。