Room — это новый способ сохранить данные приложений в Android-приложении, представленный в этом году на Google I/O. Это часть новойAndroid Architecture, группа библиотек от Google, которые поддерживают уместную архитектуру приложений. Room предлагается в качестве альтернативы Realm, ORMLite, GreenDao и многим другим.


Room — это высокоуровневый интерфейс для низкоуровневых привязок SQLite, встроенных в Android, о которых вы можете узнать больше в документации. Он выполняет большую часть своей работы во время компиляции, создавая API-интерфейс поверх встроенного SQLite API, поэтому вам не нужно работать с Cursor или ContentResolver.


Использование Room


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


public class Person {
    String name;
    int age;
    String favoriteColor;
}

Чтобы рассказать Room о классе Person, добавляем аннотицию Entity к классу и @PrimaryKey к ключу:


@Entity
public class Person {
    @PrimaryKey String name;
    int age;
    String favoriteColor;
}

Благодаря этим двум аннотациям Room теперь знает, как создать таблицу для хранения экземпляров Person.


Важная вещь, которую следует учитывать при настройке ваших моделей: каждое поле, которое хранится в базе данных, должно быть общедоступным или иметь геттер и сеттер в стандартном стиле Java Beans (например, getName () и setName (имя строки)).


В классе Person теперь есть вся информация, которая требуется Room для создания таблиц, но у вас нет способа фактически добавлять, запрашивать или удалять данные из базы данных. Вот почему вам нужно будет сделать объект доступа к данным (DAO). DAO предоставляет интерфейс в самой базе данных и занимается манипулированием хранимыми данными Person.


Вот простой интерфейс DAO для класса Person:


@Dao
public interface PersonDao {

    // Добавление Person в бд
    @Insert
    void insertAll(Person... people);

    // Удаление Person из бд
    @Delete
    void delete(Person person);

    // Получение всех Person из бд
    @Query("SELECT * FROM person")
    List<Person> getAllPeople();

    // Получение всех Person из бд с условием
    @Query("SELECT * FROM person WHERE favoriteColor LIKE :color")
    List<Person> getAllPeopleWithFavoriteColor(String color);

}

Первое, что нужно заметить, это то, что PersonDaoэто интерфейс, а не класс. Другая интересная деталь — это инструкции SQL в аннотациях Query (). Операторы SQL говорят Room, какую информацию вы хотите получить из базы данных. Они также проверяются во время компиляции. Поэтому, если вы измените подпись метода List getAllPeopleWithFavoriteColor ( название цвета ) на List getAllPeopleWithFavoriteColor ( int color ), Room выдаст ошибку во время компиляции:

incompatible types: int cannot be converted to String

И если вы сделаете опечатку в выражении SQL, например, напишите favoriteColors ( множественное число ) вместо favoriteColor ( единственное число ), Room также выдаст ошибку компиляции:


There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (no such column: favoriteColors)

Вы не можете получить экземпляр PersonDao, потому что это интерфейс. Чтобы иметь возможность использовать классы DAO, вам необходимо создать класс базы данных. За кулисами этот класс будет отвечать за ведение самой базы данных и предоставление экземпляров DAO.


Вы можете создать свой класс базы данных всего за пару строк:


@Database(entities = {Person.class /*, AnotherEntityType.class, AThirdEntityType.class */}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract PersonDao getPersonDao();
}

Это лишь описание структуры базы данных, но сама база данных будет жить в одном файле. Чтобы получить экземпляр AppDatabase, сохраненный в файле с именем populus-database, вы должны написать:


AppDatabase db = Room.databaseBuilder(getApplicationContext(),
        AppDatabase.class, "populus-database").build();

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


List<Person> everyone = db.getPersonDao().getAllPeople();

Преимущества использования Room


В отличие от большинства ORM, Room использует обработчик аннотации для выполнения всей своей манеры сохранения данных. Это означает, что ни ваши классы приложений, ни классы моделей не должны ничего расширять в Room, в отличие от многих других ORM, включая Realm и SugarORM. Как вы видели при ошибках с аннотациями Query () выше, вы также получаете возможность проверки корректности SQL-запросов во время компиляции, что может сэкономить вам много хлопот.


Room также позволяет вам наблюдать за изменениями данных, интегрируя их как с API LiveData Архитектурных Компонентов, так и с RxJava 2. Это означает, что если у вас сложная схема, где изменения в базе данных должны появляться в нескольких местах вашего приложения, Room делает уведомления об изменениях. Это мощное дополнение может быть включено одной строкой. Все, что вам нужно сделать, это изменить тип возвращаемых значений.


Например, этот метод:


@Query("SELECT * FROM person")
List<Person> getAllPeople();

Становится следующим:


@Query("SELECT * FROM person")
LiveData<List<Person>> /* or Observable<List<Person>> */ getAllPeople();

Самое большое ограничение в Room: взаимосвязи


Самым большим ограничением в Room является то, что он не будет обрабатывать отношения с другими типами сущностей для вас автоматически, как и другие ORM. Это означает, что если вы хотите отслеживать домашних животных:


@Entity
public class Person {
    @PrimaryKey String name;
    int age;
    String favoriteColor;
    List<Pet> pets;
}

@Entity
public class Pet {
    @PrimaryKey String name;
    String breed;
}

То Room выдаст ошибку компиляци, так как не знает, как сохранить отношения между Person и Pet:


Cannot figure out how to save this field into database. You can consider adding a type converter for it.

Ошибка при компиляции предлагает конвертер типов, который преобразует объекты в примитивы, которые могут быть непосредственно сохранены в SQL. Поскольку List нельзя свести к примитиву, вам нужно сделать что-то другое. Это отношения «один ко многим», где у одного Person может быть много Pet. Room не может моделировать такие отношения, но она может справиться с обратными отношениями — у каждого Pet есть один Person. Чтобы смоделировать это, удалите поле для Pet в Person и добавьте поле ownerId в класс Pet:


@Entity
public class Person {
    @PrimaryKey String name;
    int age;
    String favoriteColor;
}

@Entity(foreignKeys = @ForeignKey(
            entity = Person.class,
            parentColumns = "name",
            childColumns = "ownerId"))
public class Pet {
    @PrimaryKey String name;
    String breed;
    String ownerId; // this ID points to a Person
}

Это приведет к тому, что Room обеспечит ограничение внешнего ключа между объектами. Room не будет вызывать отношения «один-ко-многим» и «много-к-одному», но она дает вам инструменты для выражения этих отношений.


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


@Query("SELECT * FROM pet WHERE ownderId IS :ownerId")
List<Pet> getPetsForOwner(String ownerId);

Стоит ли использовать Room?


Если вы уже настроили сохранение данных в своем приложении и довольны им, то ничего не изменяйте. Каждая ORM и встроенная реализация SQLite будут продолжать работать так же, как и раньше. Room — это всего лишь еще один вариант сохранения данных.


Если вы используете SQLite или собираетесь использовать его, вы должны попробовать Room. Он обладает всеми возможностями, необходимыми для выполнения расширенных запросов, одновременно устраняя необходимость писать SQL-запросы для поддержки базы данных самостоятельно.

Комментарии (7)


  1. Jukobob
    23.08.2017 09:29

    Room — это новый способ сохранить данные приложений в Android-приложении, представленный в этом году на Google I/O


    Мой дорогой друг, вот тут вы абсолютно не правы. Загляните в сгенерированные файлы. Вы увидите до боли известный нам «SQLiteOpenHelper».

    Room предлагается в качестве альтернативной альтернативы Realm, ORMLite, GreenDao

    Если приводить такое сравнение то можно добавить «сериализацию в SharedPreferences». Тут вопрос гораздо глубже. Если вы нуждаетесь в использовании ContentProvider или CursorLoades для примера, то с другими базами (для примера Realm) вам будет очень сложно его подружить.

    Стоит еще упомянуть что Room находится в alpha версии
    И можно было бы добавить несколько ссылок для примеров на Github


    1. kozhevnikovv
      23.08.2017 10:48

      Мой дорогой друг, вот тут вы абсолютно не правы. Загляните в сгенерированные файлы. Вы увидите до боли известный нам «SQLiteOpenHelper».


      Как раз тот факт того что Room находится на один уровень абстракции выше SQLiteOpenHelper и делает его новым способом сохранения данных в Android-приложении.


      1. Jukobob
        24.08.2017 12:37

        Исходя из этого, если я использую обертки вокруг SharedPreferences то это тоже «Новый способ хранения данных»? (PS. Это не холивар, просто хочу уточнить ваше понимание ситуации)


  1. Revertis
    23.08.2017 13:53
    +1

    getAllPeopleWithFavoriteColor ( цвет строки )
    Какой такой цвет строки?
    Комната делает уведомления об изменениях
    Комната! :)
    может быть включено с однострочным изменением
    Вот это вообще непонятно.
    Комната не будет вызывать
    Опять комната :)
    Если вы уже настроили сохранение данных в своем приложении и довольны им, вы должны его сохранить
    А это совсем не по-русски.

    Зачем делать переводы такого «качества»?

    Для простенького проектика «Комната» подойдет, наверное. Но что-то сложное, с более сложным кодом, так просто не заменить.


    1. velkonost Автор
      23.08.2017 15:46

      Ваши замечания учтены


  1. Sie
    23.08.2017 15:46

    Хорошая статья но в основном расскзывает о то что написано на офсайте.

    Я ожидал описания например как связынне оъекты выдать на на слой выше в пригодном виде (понятно что там делать 2 выборки и конструировать нвоый объект).
    Может быть есть средства или практики с использванием Arch Components — LiveData


  1. TechnoMag
    23.08.2017 17:27

    Еще очень сырой продукт, на мой взгляд. Например, Room не умеет распознать List, в корневом Entity. Хотелось бы более плотную связку с Moshi.
    Если объект Moshi сдержит поле null, сеттер сгенерированного класса не умеет его обработать.
    Также нет еще поддержки кастомных Update-запросов.