Эта страница переведена с помощью Cloud Translation API.
Switch to English

Сохранение данных

В этом документе рассматриваются четыре метода записи данных в базу данных Firebase Realtime: установка, обновление, отправка и поддержка транзакций.

Способы сохранения данных

задавать Записать или заменить данные по указанному пути , например messages/users/<username>
Обновить Обновить некоторые ключи для определенного пути без замены всех данных
От себя Добавить в список данных в базе данных. Каждый раз, когда вы помещаете новый узел в список, ваша база данных генерирует уникальный ключ, например messages/users/<unique-user-id>/<username>
сделка Используйте транзакции при работе со сложными данными, которые могут быть повреждены параллельными обновлениями

Сохранение данных

Базовая операция записи в базу данных - это набор, который сохраняет новые данные в указанной ссылке базы данных, заменяя любые существующие данные по этому пути. Чтобы понять сет, мы создадим простое приложение для ведения блога. Данные для вашего приложения хранятся по этой ссылке на базу данных:

Ява
final FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("server/saving-data/fireblog");
Node.js
// Import Admin SDK
var admin = require("firebase-admin");

// Get a database reference to our blog
var db = admin.database();
var 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')
Идти
// 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() чтобы сохранить объект пользователя в базе данных с именем пользователя, полным именем и днем ​​рождения. Вы можете передать 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
var 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'
    }
})
Идти

// 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 , мы увидим значение «Алан Тьюринг». Вы также можете сохранить данные прямо в дочернем расположении:

Ява
usersRef.child("alanisawesome").setValueAsync(new User("June 23, 1912", "Alan Turing"));
usersRef.child("gracehop").setValueAsync(new User("December 9, 1906", "Grace Hopper"));
Node.js
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'
})
Идти
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 значение каждого отдельного дочернего узла, оставив другие дочерние usersRef без изменений.

Обновление сохраненных данных

Если вы хотите одновременно писать в несколько дочерних узлов базы данных, не перезаписывая другие дочерние узлы, вы можете использовать метод обновления, как показано ниже:

Ява
DatabaseReference hopperRef = usersRef.child("gracehop");
Map<String, Object> hopperUpdates = new HashMap<>();
hopperUpdates.put("nickname", "Amazing Grace");

hopperRef.updateChildrenAsync(hopperUpdates);
Node.js
var hopperRef = usersRef.child("gracehop");
hopperRef.update({
  "nickname": "Amazing Grace"
});
Python
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)
}

Это обновит данные Грейс и добавит ее ник. Если бы вы использовали здесь set вместо update, он удалил бы как full_name и date_of_birth из вашей hopperRef .

База данных Firebase Realtime также поддерживает многопутевые обновления. Это означает, что теперь update может обновлять значения в нескольких местах в вашей базе данных одновременно, и это мощная функция, которая позволяет денормализовать ваши данные . Используя многопутевые обновления, вы можете добавлять псевдонимы и Грейс, и Алану одновременно:

Ява
Map<String, Object> userUpdates = new HashMap<>();
userUpdates.put("alanisawesome/nickname", "Alan The Machine");
userUpdates.put("gracehop/nickname", "Amazing Grace");

usersRef.updateChildrenAsync(userUpdates);
Node.js
usersRef.update({
  "alanisawesome/nickname": "Alan The Machine",
  "gracehop/nickname": "Amazing Grace"
});
Python
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);
Node.js
usersRef.update({
  "alanisawesome": {
    "nickname": "Alan The Machine"
  },
  "gracehop": {
    "nickname": "Amazing Grace"
  }
});
Python
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, если вы хотите знать, когда ваши данные были зафиксированы, вы можете добавить обратный вызов завершения. Оба метода set и update в этих 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", function(error) {
  if (error) {
    alert("Data could not be saved." + error);
  } else {
    alert("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
var postsRef = ref.child("posts");

var 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'
})
Идти

// 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"
});
Python
# 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()
var newPostRef = postsRef.push();

// Get the unique key generated by push()
var 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
Идти
// 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 операция транзакции блокируется, и поэтому она принимает только функцию обновления.

Функция обновления принимает текущее состояние данных в качестве аргумента и должна возвращать новое желаемое состояние, которое вы хотите записать. Например, если вы хотите увеличить количество голосов за конкретное сообщение в блоге, вы должны написать транзакцию, подобную следующей:

Ява
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
var upvotesRef = db.ref("server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes");
upvotesRef.transaction(function (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')
Идти
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 имеет язык безопасности, который позволяет вам определять, какие пользователи имеют доступ для чтения и записи к различным узлам ваших данных. Вы можете узнать больше об этом в разделе «Защита данных» .