Menyimpan Data

Dokumen ini membahas empat metode untuk menulis data ke Firebase Realtime Database: set, update, push, dan dukungan transaksi.

Cara Menyimpan Data

set Menulis atau mengganti data ke jalur yang ditentukan, seperti messages/users/<username>
update Mengupdate beberapa kunci untuk jalur yang ditentukan tanpa mengganti semua data
push Menambahkan ke daftar data dalam database. Setiap kali Anda mendorong node baru ke daftar, database Anda menghasilkan kunci unik, seperti messages/users/<unique-user-id>/<username>
transaction Menggunakan fitur transaksi ketika menangani data kompleks yang bisa rusak karena update serentak

Menyimpan Data

Operasi tulis database dasar adalah set, yang menyimpan data baru ke referensi database yang ditetapkan, sehingga menggantikan data yang ada di jalur tersebut. Untuk memahami set, kita akan membuat aplikasi blogging sederhana. Data untuk aplikasi Anda disimpan di referensi database ini:

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")

Mari mulai dengan menyimpan beberapa data pengguna. Kita akan menyimpan setiap pengguna dengan nama pengguna yang unik, dan kita juga akan menyimpan nama lengkap dan tanggal lahir mereka. Karena setiap pengguna memiliki nama pengguna yang unik, kita gunakan metode set dan bukan metode push karena kita sudah memiliki kunci dan tidak perlu membuatnya lagi.

Pertama, buat referensi database ke data pengguna. Kemudian, gunakan set() / setValue() untuk menyimpan objek pengguna ke database dengan nama pengguna, nama lengkap, dan tanggal lahir pengguna. Anda bisa meneruskan string, angka, boolean, null, array, atau objek JSON ke set. Meneruskan null akan menghapus data di lokasi yang ditentukan. Dalam hal ini, Anda akan meneruskan sebuah objek:

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:"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)
}

Ketika objek JSON disimpan ke database, properti objek secara otomatis dipetakan ke lokasi turunan database secara bertingkat. Sekarang, jika Anda membuka URL https://docs-examples.firebaseio.com/server/saving-data/fireblog/users/alanisawesome/full_name, Anda akan melihat nilai "Alan Turing". Anda juga bisa menyimpan data secara langsung ke lokasi turunan:

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)
}

Kedua contoh di atas, yaitu menulis kedua nilai secara bersamaan sebagai objek dan menuliskannya secara terpisah ke lokasi turunan, akan menyebabkan data yang sama disimpan ke database Anda:

{
  "users": {
    "alanisawesome": {
      "date_of_birth": "June 23, 1912",
      "full_name": "Alan Turing"
    },
    "gracehop": {
      "date_of_birth": "December 9, 1906",
      "full_name": "Grace Hopper"
    }
  }
}

Contoh pertama hanya akan memicu satu peristiwa pada klien yang mengamati data, sedangkan contoh kedua akan memicu dua peristiwa. Perlu diperhatikan bahwa jika data sudah ada di usersRef, pendekatan pertama akan menimpanya, tetapi metode kedua hanya akan memodifikasi nilai dari setiap node turunan yang terpisah dan tidak mengubah turunan usersRef yang lain.

Mengupdate Data Tersimpan

Jika ingin menulis ke beberapa turunan lokasi database secara bersamaan tanpa menimpa node turunan lainnya, Anda bisa menggunakan metode update seperti ditunjukkan di bawah ini:

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)
}

Ini akan memperbarui data Grace agar memasukkan nama panggilannya. Jika Anda telah menggunakan set di sini dan bukan update, tindakan ini akan menghapus full_name dan date_of_birth dari hopperRef Anda.

Firebase Realtime Database juga mendukung pembaruan multi-jalur. Artinya, sekarang update bisa memperbarui nilai di beberapa lokasi dalam database Anda secara bersamaan. Ini adalah fitur andal yang akan membantu Anda melakukan denormalisasi data. Dengan menggunakan update multi-jalur, Anda bisa menambahkan nama julukan untuk Grace dan Alan secara bersamaan:

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)
}

Setelah pembaruan ini, nama julukan akan ditambahkan untuk Alan dan 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"
    }
  }
}

Perhatikan bahwa mencoba memperbarui objek dengan menulis objek beserta jalurnya akan menghasilkan perilaku yang berbeda. Mari kita lihat apa yang terjadi jika Anda mencoba memperbarui Grace dan Alan dengan cara ini:

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)
}

Hal ini menyebabkan perilaku yang berbeda, yaitu menimpa seluruh node /users:

{
  "users": {
    "alanisawesome": {
      "nickname": "Alan The Machine"
    },
    "gracehop": {
      "nickname": "Amazing Grace"
    }
  }
}

Menambahkan Callback Penyelesaian

Di Node.js dan Java Admin SDK, jika Anda ingin tahu kapan data di-commit, Anda bisa menambahkan callback penyelesaian. Baik metode set maupun update dalam SDK ini menggunakan callback penyelesaian opsional yang dipanggil ketika operasi tulis telah di-commit ke database. Jika panggilan tidak berhasil karena beberapa alasan, objek error akan diteruskan ke callback untuk menunjukkan penyebab kegagalan. Di Python dan Go Admin SDK, semua metode penulisan memblokir data. Artinya, metode tulis tidak menampilkan data sampai tulisan di-commit ke database.

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.');
  }
});

Menyimpan Daftar Data

Ketika membuat daftar data, penting untuk mengingat sifat multi-pengguna sebagian besar aplikasi dan menyesuaikan struktur daftar Anda berdasarkan sifat tersebut. Dengan memperluas contoh di atas, mari kita tambahkan entri blog ke aplikasi. Biasanya, tindakan yang pertama dilakukan adalah menggunakan set untuk menyimpan turunan dengan indeks integer dengan penambahan inkremental otomatis, seperti berikut:

// NOT RECOMMENDED - use push() instead!
{
  "posts": {
    "0": {
      "author": "gracehop",
      "title": "Announcing COBOL, a New Programming Language"
    },
    "1": {
      "author": "alanisawesome",
      "title": "The Turing Machine"
    }
  }
}

Jika pengguna menambahkan postingan baru, postingan tersebut akan disimpan sebagai /posts/2. Ini berlaku jika hanya ada satu penulis yang menambahkan postingan. Namun, dalam aplikasi blogging kolaborasi, banyak pengguna bisa menambahkan postingan secara bersamaan. Jika dua penulis menulis ke /posts/2 secara bersamaan, salah satu postingan akan dihapus oleh yang lain.

Untuk mengatasi hal ini, klien Firebase menyediakan fungsi push() yang menghasilkan kunci unik untuk setiap turunan baru. Dengan menggunakan kunci turunan yang unik, beberapa klien dapat menambahkan turunan ke lokasi yang sama secara bersamaan tanpa mengkhawatirkan konflik penulisan.

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:"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)
}

Kunci unik didasarkan pada stempel waktu, sehingga daftar item akan otomatis diurutkan secara kronologis. Karena Firebase menghasilkan kunci unik untuk setiap entri blog, konflik tulis tidak akan terjadi ketika beberapa pengguna menambahkan postingan secara bersamaan. Data dari database Anda sekarang terlihat seperti ini:

{
  "posts": {
    "-JRHTHaIs-jNPLXOQivY": {
      "author": "gracehop",
      "title": "Announcing COBOL, a New Programming Language"
    },
    "-JRHTHaKuITFIhnj02kE": {
      "author": "alanisawesome",
      "title": "The Turing Machine"
    }
  }
}

Pada JavaScript, Python, dan Go, pola pemanggilan push() yang langsung diikuti dengan pemanggilan set() merupakan praktik yang sangat umum. Karena itulah Firebase SDK menyediakan fungsi untuk menggabungkan keduanya dengan meneruskan data yang akan ditetapkan langsung ke push() seperti berikut:

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)
}

Memperoleh kunci unik yang dihasilkan oleh push()

Memanggil push() akan menampilkan referensi ke lokasi data baru, yang dapat Anda gunakan untuk mendapatkan kunci atau menetapkan data ke dalamnya. Kode berikut akan menghasilkan data yang sama seperti contoh di atas, tetapi sekarang kita akan memiliki akses ke kunci unik yang dihasilkan:

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

Seperti yang ditampilkan, Anda bisa mendapatkan nilai kunci unik dari referensi push().

Di bagian berikutnya pada artikel Mengambil Data, kita akan belajar cara membaca data ini dari database Firebase.

Menyimpan Data Transaksional

Ketika menangani data kompleks yang bisa rusak karena perubahan serentak, seperti penghitung pertambahan inkremental, SDK menyediakan operasi transaction.

Pada Java dan Node.js, Anda memberikan dua callback ke operasi transaction: fungsi update dan callback penyelesaian opsional. Pada Python dan Go, operasi transaction ini memblokir data, sehingga Python dan Go hanya menerima fungsi update.

Fungsi update mengambil status data saat ini sebagai argumen dan akan menampilkan status baru yang diinginkan untuk Anda tulis. Misalnya, jika Anda ingin menaikkan jumlah suara positif pada entri blog tertentu, Anda akan menulis transaction seperti berikut:

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)
}

Contoh di atas memeriksa apakah penghitung bernilai null atau belum bertambah, karena transaction bisa dipanggil dengan null jika tidak ada nilai default yang ditulis.

Jika kode di atas dijalankan tanpa fungsi transaction dan dua klien mencoba untuk menaikkannya secara bersamaan, kedua klien akan menulis 1 sebagai nilai baru, yang mengakibatkan kenaikan satu angka, bukan dua.

Konektivitas Jaringan dan Penulisan Offline

Klien Firebase Node.js dan Java menyimpan versi internalnya masing-masing untuk setiap data aktif. Ketika data ditulis, data akan dituliskan ke versi lokal terlebih dahulu. Klien kemudian melakukan sinkronisasi data tersebut dengan database dan klien lain atas dasar 'upaya terbaik'.

Akibatnya, semua operasi tulis ke database akan segera memicu peristiwa lokal, sebelum data ditulis ke database. Ini berarti bahwa ketika Anda menulis aplikasi menggunakan Firebase, aplikasi Anda akan tetap responsif terlepas dari kondisi latensi jaringan atau sambungan Internet.

Setelah konektivitas tersambung kembali, kita akan menerima kumpulan peristiwa yang tepat sehingga klien bisa "mengejar ketertinggalan" dengan kondisi server saat ini, tanpa harus menulis kode khusus.

Melindungi Data Anda

Firebase Realtime Database memiliki bahasa keamanan yang memungkinkan Anda menentukan pengguna mana yang memiliki akses baca dan tulis untuk node yang berbeda dari data Anda. Anda bisa membaca lebih lanjut mengenai hal tersebut di artikel Mengamankan Data.