Урок 5. Room. Основы
Библиотека Room предоставляет нам удобную обертку для работы с базой данных SQLite. В этом уроке рассмотрим основы. Как подключить к проекту. Как получать, вставлять, обновлять и удалять данные.
Полный список уроков курса:
- Урок 1. Lifecycle
- Урок 2. LiveData
- Урок 3. LiveData. Дополнительные возможности
- Урок 4. ViewModel
- Урок 5. Room. Основы
- Урок 6. Room. Entity
- Урок 7. Room. Insert, Update, Delete, Transaction
- Урок 8. Room. Query
- Урок 9. Room. RxJava
- Урок 10. Room. Запрос из нескольких таблиц. Relation
- Урок 11. Room. Type converter
- Урок 12. Room. Миграция версий базы данных
- Урок 13. Room. Тестирование
- Урок 14. Paging Library. Основы
- Урок 15. Paging Library. PagedList и DataSource. Placeholders.
- Урок 16. Paging Library. LivePagedListBuilder. BoundaryCallback.
- Урок 17. Paging Library. Виды DataSource
- Урок 18. Android Data Binding. Основы
- Урок 19. Android Data Binding. Код в layout. Доступ к View
- Урок 20. Android Data Binding. Обработка событий
- Урок 21. Android Data Binding. Observable поля. Двусторонний биндинг.
- Урок 22. Android Data Binding. Adapter. Conversion.
- Урок 23. Android Data Binding. Использование с include, ViewStub и RecyclerView.
- Урок 24. Navigation Architecture Component. Введение
- Урок 25. Navigation. Передача данных. Type-safe аргументы.
- Урок 26. Navigation. Параметры навигации
- Урок 27. Navigation. NavigationUI.
- Урок 28. Navigation. Вложенный граф. Global Action. Deep Link.
- Урок 29. WorkManager. Введение
- Урок 30. WorkManager. Критерии запуска задачи.
- Урок 31. WorkManager. Последовательность выполнения задач.
- Урок 32. WorkManager. Передача и получение данных
- Урок 33. Практика. О чем это будет.
- Урок 34. Практика. TodoApp. Список задач.
- Урок 35. Практика. TodoApp. Просмотр задачи
Подключение к проекту
В build.gradle файл проекта добавьте репозитарий google()
allprojects < repositories < jcenter() google() >. >
В build.gradle файле модуля добавьте dependencies:
dependencies
Если у вас студия ниже 3.0 и старые версии Gradle и Android Plugin, то подключение будет выглядеть так:
buildscript < repositories < jcenter() maven < url 'https://maven.google.com' >> . >
dependencies
Room
Room имеет три основных компонента: Entity, Dao и Database. Рассмотрим их на небольшом примере, в котором будем создавать базу данных для хранения данных по сотрудникам (англ. — employee).
При работе с Room нам необходимо будет писать SQL запросы. Если вы не знакомы с ними, то имеет смысл прочесть хотя бы основы.
Entity
Аннотацией Entity нам необходимо пометить объект, который мы хотим хранить в базе данных. Для этого создаем класс Employee, который будет представлять собой данные сотрудника: id, имя, зарплата:
@Entity public class Employee
Класс помечается аннотацией Entity. Объекты класса Employee будут использоваться при работе с базой данных. Например, мы будем получать их от базы при запросах данных и отправлять их в базу при вставке данных.
Этот же класс Employee будет использован для создания таблицы в базе. В качестве имени таблицы будет использовано имя класса. А поля таблицы будут созданы в соответствии с полями класса.
Аннотацией PrimaryKey мы помечаем поле, которое будет ключом в таблице.
В следующих уроках мы рассмотрим возможности Entity более подробно.
Dao
В объекте Dao мы будем описывать методы для работы с базой данных. Нам нужны будут методы для получения списка сотрудников и для добавления/изменения/удаления сотрудников.
Описываем их в интерфейсе с аннотацией Dao.
@Dao public interface EmployeeDao < @Query("SELECT * FROM employee") ListgetAll(); @Query("SELECT * FROM employee WHERE Employee getById(long id); @Insert void insert(Employee employee); @Update void update(Employee employee); @Delete void delete(Employee employee); >
Методы getAll и getById позволяют получить полный список сотрудников или конкретного сотрудника по id. В аннотации Query нам необходимо прописать соответствующие SQL-запросы, которые будут использованы для получения данных.
Обратите внимание, что в качестве имени таблицы мы используем employee. Напомню, что имя таблицы равно имени Entity класса, т.е. Employee, но в SQLite не важен регистр в именах таблиц, поэтому можем писать employee.
Для вставки/обновления/удаления используются методы insert/update/delete с соответствующими аннотациями. Тут никакие запросы указывать не нужно. Названия методов могут быть любыми. Главное — аннотации.
В следующих уроках мы рассмотрим возможности Dao и его аннотаций более подробно.
Database
Аннотацией Database помечаем основной класс по работе с базой данных. Этот класс должен быть абстрактным и наследовать RoomDatabase.
@Database(entities = , version = 1) public abstract class AppDatabase extends RoomDatabase
В параметрах аннотации Database указываем, какие Entity будут использоваться, и версию базы. Для каждого Entity класса из списка entities будет создана таблица.
В Database классе необходимо описать абстрактные методы для получения Dao объектов, которые вам понадобятся.
Практика
Все необходимые для работы объекты созданы. Давайте посмотрим, как использовать их для работы с базой данных.
Database объект — это стартовая точка. Его создание выглядит так:
AppDatabase db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "database").build();
Используем Application Context, а также указываем AppDatabase класс и имя файла для базы.
Учитывайте, что при вызове этого кода Room каждый раз будет создавать новый экземпляр AppDatabase. Эти экземпляры очень тяжелые и рекомендуется использовать один экземпляр для всех ваших операций. Поэтому вам необходимо позаботиться о синглтоне для этого объекта. Это можно сделать с помощью Dagger, например.
Если вы не используете Dagger (или другой DI механизм), то можно использовать Application класс для создания и хранения AppDatabase:
public class App extends Application < public static App instance; private AppDatabase database; @Override public void onCreate() < super.onCreate(); instance = this; database = Room.databaseBuilder(this, AppDatabase.class, "database") .build(); >public static App getInstance() < return instance; >public AppDatabase getDatabase() < return database; >>
Не забудьте добавить App класс в манифест
В коде получение базы будет выглядеть так:
AppDatabase db = App.getInstance().getDatabase();
Из Database объекта получаем Dao.
EmployeeDao employeeDao = db.employeeDao();
Теперь мы можем работать с Employee объектами. Но эти операции должны выполняться не в UI потоке. Иначе мы получим Exception.
Добавление нового сотрудника в базу будет выглядеть так:
Employee employee = new Employee(); employee.id = 1; employee.name = "John Smith"; employee.salary = 10000; employeeDao.insert(employee);
Метод getAll вернет нам всех сотрудников в List
List employees = employeeDao.getAll();
Получение сотрудника по id:
Employee employee = employeeDao.getById(1);
Обновление данных по сотруднику.
employee.salary = 20000; employeeDao.update(employee);
Room будет искать в таблице запись по ключевому полю, т.е. по id. Если в объекте employee не заполнено поле id, то по умолчанию в нашем примере оно будет равно нулю и Room просто не найдет такого сотрудника (если, конечно, у вас нет записи с >
employeeDao.delete(employee);
Аналогично обновлению, Room будет искать запись по ключевому полю, т.е. по id
Давайте для примера добавим еще один тип объекта — Car.
Описываем Entity объект
@Entity public class Car
Теперь Dao для работы с Car объектом
@Dao public interface CarDao < @Query("SELECT * FROM car") ListgetAll(); @Insert void insert(Car car); @Delete void delete(Car car); >
Будем считать, что нам надо только читать все записи, добавлять новые и удалять старые.
В Database необходимо добавить Car в список entities и новый метод для получения CarDao
@Database(entities = , version = 1) public abstract class AppDatabase extends RoomDatabase
Т.к. мы добавили новую таблицу, изменилась структура базы данных. И нам необходимо поднять версию базы данных до 2. Но об этом мы подробно поговорим в Уроке 12. А пока можно оставить версию равной 1, удалить старую версию приложения и поставить новую.
UI поток
Повторюсь, операции по работе с базой данных — синхронные, и должны выполняться не в UI потоке.
В случае с Query операциями мы можем сделать их асинхронными используя LiveData или RxJava. Об этом еще поговорим в следующих уроках.
В случае insert/update/delete вы можете обернуть эти методы в асинхронный RxJava. В моем блоге есть статья на эту тему.
Также, вы можете использовать allowMainThreadQueries в билдере создания AppDatabase
AppDatabase db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "database") .allowMainThreadQueries() .build();
В этом случае вы не будете получать Exception при работе в UI потоке. Но вы должны понимать, что это плохая практика, и может добавить ощутимых тормозов вашему приложению.
Переход на Room
Если вы надумали с SQLite мигрировать на Room, то вот пара полезных ссылок по этой теме:
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
Introduction
SQLite and Room are both database solutions for Android applications, but they serve different purposes and offer different features. While SQLite is a powerful and widely-used database engine, Room offers a higher-level abstraction, better developer experience, and improved support for modern Android development practices. Room is particularly beneficial for developers who prefer an ORM approach, value compile-time safety, and want to take advantage of the Android Architecture Components.
Room database has some extra advantages which SQLite do not provide, such as:
Compile-Time Verification: Room provides compile-time verification of SQL queries. If there are any issues with your queries, the compiler catches them during the build process, reducing the chance of runtime errors.
LiveData and RxJava Integration: Room integrates seamlessly with LiveData and RxJava. This allows you to observe changes in the database and automatically update the UI when the data changes, making it easier to implement reactive UIs.
I’ll write another article some other day on LiveData and RxJava and how you can implement them easily in Android.
Let’s explore the power of Room database in a simple login application.
Step 1: Add Room Database Dependencies
In your app-level build.gradle file, add the following dependencies:
implementation "androidx.room:room-runtime:2.6.1"
annotationProcessor "androidx.room:room-compiler:2.6.1"
Step 2: Create the Entity Class
Define the structure of your User entity class, representing the user profile information.
@Entity(tableName = "user_table")
public class User @PrimaryKey(autoGenerate = true)
public int id;
@ColumnInfo(name = "login_id")
public String loginId;
@ColumnInfo(name = "password")
public String password;
@ColumnInfo(name = "full_name")
public String fullName;
@ColumnInfo(name = "contact")
public String contact;
>
Step 3: Create the DAO (Data Access Object)
Define the Data Access Object interface to perform CRUD operations on the User entity.
@Dao
public interface UserDao @Insert
void insert(User user);
@Update
void update(User user);
@Delete
void delete(User user);
@Query("SELECT * FROM user_table WHERE login_id = :userId")
User getUserByLoginId(String userId);
@Query("SELECT * FROM " + DbConfig.USER_TABLE)
List getAllUsers();
>
Step 4: Create the Room Database
Build the Room Database by extending RoomDatabase and include the DAO.
@Database(entities = , version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase public abstract UserDao userDao();
private static volatile AppDatabase INSTANCE;
public static AppDatabase getDatabase(final Context context) if (INSTANCE == null) synchronized (AppDatabase.class) if (INSTANCE == null) INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, DbConfig.ROOM_DB_NAME)
.build();
>
>
>
return INSTANCE;
>
>
Step 5: Implement CRUD Operations in Your Application
In your activities or fragments, use the UserDao methods to perform database operations.
Step 6: Initialize the Database
Initialize the database in your application class or the entry point of your app.
public class InitDb extends Application public static AppDatabase appDatabase;
@Override
public void onCreate() super.onCreate();
appDatabase = AppDatabase.getDatabase(this);
>
>
Step 7: Create an Activity
In this example, we’re creating a login activity class to perform the database task. But one important thing you must notice, performing a database query on the main thread in Room is not allowed. To fix this issue, database operations must be performed on a background thread and you should use the Executors class or Kotlin Coroutines for background threading.
public class LoginActivity extends AppCompatActivity
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
private EditText etLoginId;
private EditText etPassword;
private Button btnLogin;
private UserDao userDao;
@Override
protected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// Initialize the UserDao
userDao = InitDb.appDatabase.userDao();
// Initialize UI components
etLoginId = findViewById(R.id.etLoginId);
etPassword = findViewById(R.id.etPassword);
btnLogin = findViewById(R.id.btnLogin);
// Set onClickListener for the login button
btnLogin.setOnClickListener(new View.OnClickListener() @Override
public void onClick(View view) login();
>
>);
// Initialize the UserDao
userDao = InitDb.appDatabase.userDao();
// Insert a test user for demonstration purposes
insertTestUser();
>
private void login() String loginId = etLoginId.getText().toString().trim();
String password = etPassword.getText().toString().trim();
if (TextUtils.isEmpty(loginId) || TextUtils.isEmpty(password)) Toast.makeText(this, "Please enter login credentials", Toast.LENGTH_SHORT).show();
return;
>
// Execute the database query on a background thread
executorService.execute(new Runnable() @Override
public void run() final User user = userDao.getUserByLoginId(loginId);
// Handle the result on the main thread
runOnUiThread(new Runnable() @Override
public void run() if (user != null && password.equals(user.getPassword())) // Successful login, navigate to the main activity
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
finish();
> else // Invalid login credentials
Toast.makeText(LoginActivity.this, "Invalid login credentials", Toast.LENGTH_SHORT).show();
>
>
>);
>
>);
>
private void insertTestUser() executorService.execute(new Runnable() @Override
public void run() // Check if the test user 'admin' already exists in the db
if (userDao.getUserByLoginId("admin") == null) // Insert the test user
User testUser = new User();
testUser.setLoginId("admin");
testUser.setPassword("admin"); // Note: In a real application, passwords should be hashed
testUser.setFullName("Admin User");
testUser.setContact("admin@example.com");
userDao.insert(testUser);
>
>
>);
>
>
Congratulations! You’ve successfully set up and implemented a Room database in your Android application. Feel free to adapt and expand upon this example based on your specific use case and requirements.
You can download complete project from my Github repository:
7 шагов к использованию Room. Пошаговое руководство по миграции приложения на Room
Room — это библиотека, которая является частью архитектурных компонентов Android. Она облегчает работу с объектами SQLiteDatabase в приложении, уменьшая объём стандартного кода и проверяя SQL-запросы во время компиляции.
У вас уже есть Android-проект, который использует SQLite для хранения данных? Если это так, то вы можете мигрировать его на Room. Давайте посмотрим, как взять уже существующий проект и отрефакторить его для использования Room за 7 простых шагов.
TL;DR: обновите зависимости gradle, создайте свои сущности, DAO и базу данных, замените вызовы SQLiteDatabase вызовами методов DAO, протестируйте всё, что вы создали или изменили, и удалите неиспользуемые классы. Вот и всё!
В нашем примере приложения для миграции мы работаем с объектами типа User . Мы использовали product flavors для демонстрации различных реализаций уровня данных:
- sqlite — использует SQLiteOpenHelper и традиционные интерфейсы SQLite.
- room — заменяет реализацию на Room и обеспечивает миграцию.
Каждый вариант использует один и тот же слой пользовательского интерфейса, который работает с классом UserRepository благодаря паттерну MVP.
В варианте sqlite вы увидите много кода, который часто дублируется и использует базу данных в классах UsersDbHelper и LocalUserDataSource . Запросы строятся с помощью ContentValues , а данные, возвращаемые объектами Cursor , читаются столбец за столбцом. Весь этот код способствует появлению неявных ошибок. Например, можно пропустить добавление столбца в запрос или неправильно собрать объект из базы данных.
Давайте посмотрим, как Room улучшит наш код. Изначально мы просто копируем классы из варианта sqlite и постепенно будем изменять их.
Шаг 1. Обновление зависимостей gradle
Зависимости для Room доступны через новый Google Maven-репозиторий. Просто добавьте его в список репозиториев в вашем основном файле build.gradle :
allprojects < repositories < google() jcenter() >>
Определите версию библиотеки Room в том же файле. Пока она находится в альфа-версии, но следите за обновлениями версий на страницах для разработчиков:
В вашем файле app/build.gradle добавьте зависимости для Room:
dependencies
Чтобы мигрировать на Room, нам нужно увеличить версию базы данных, а для сохранения пользовательских данных нам потребуется реализовать класс Migration. Чтобы протестировать миграцию, нам нужно экспортировать схему. Для этого добавьте следующий код в файл app/build.gradle :
android < defaultConfig < . // used by Room, to test migrations javaCompileOptions < annotationProcessorOptions < arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] >> > // used by Room, to test migrations sourceSets < androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) >.
Шаг 2. Обновление классов модели до сущностей
Room создаёт таблицу для каждого класса, помеченного @Entity. Поля в классе соответствуют столбцам в таблице. Следовательно, классы сущностей, как правило, представляют собой небольшие классы моделей, которые не содержат никакой логики. Наш класс User представляет модель для данных в базе данных. Итак, давайте обновим его, чтобы сообщить Room, что он должен создать таблицу на основе этого класса:
- Аннотируйте класс с помощью @Entity и используйте свойство tableName , чтобы задать имя таблицы.
- Задайте первичный ключ, добавив аннотацию @PrimaryKey в правильные поля — в нашем случае это идентификатор пользователя.
- Задайте имя столбцов для полей класса, используя аннотацию @ColumnInfo(name = «column_name») . Этот шаг можно пропустить, если ваши поля уже названы так, как следует назвать столбец.
- Если в классе несколько конструкторов, добавьте аннотацию @Ignore , чтобы указать Room, какой следует использовать, а какой — нет.
@Entity(tableName = "users") public class User < @PrimaryKey @ColumnInfo(name = "userid") private String mId; @ColumnInfo(name = "username") private String mUserName; @ColumnInfo(name = "last_update") private Date mDate; @Ignore public User(String userName) < mId = UUID.randomUUID().toString(); mUserName = userName; mDate = new Date(System.currentTimeMillis()); >public User(String id, String userName, Date date) < this.mId = id; this.mUserName = userName; this.mDate = date; >. >
Примечание: для плавной миграции обратите пристальное внимание на имена таблиц и столбцов в исходной реализации и убедитесь, что вы правильно устанавливаете их в аннотациях @Entity и @ColumnInfo .
Шаг 3. Создание объектов доступа к данным (DAO)
DAO отвечают за определение методов доступа к базе данных. В первоначальной реализации нашего проекта на SQLite все запросы к базе данных выполнялись в классе LocalUserDataSource , где мы работали с объектами Cursor . В Room нам не нужен весь код, связанный с курсором, и мы можем просто определять наши запросы, используя аннотации в классе UserDao .
Например, при запросе всех пользователей из базы данных Room выполняет всю «тяжелую работу», и нам нужно только написать:
@Query(“SELECT * FROM Users”) List getUsers();
Шаг 4. Создание базы данных
Мы уже определили нашу таблицу Users и соответствующие ей запросы, но мы ещё не создали базу данных, которая объединит все эти составляющие Room. Для этого нам нужно определить абстрактный класс, который расширяет RoomDatabase . Этот класс помечен @Database , в нём перечислены объекты, содержащиеся в базе данных, и DAO, которые обращаются к ним. Версия базы данных должна быть увеличена на 1 в сравнении с первоначальным значением, поэтому в нашем случае это будет 2.
@Database(entities = , version = 2) @TypeConverters(DateConverter.class) public abstract class UsersDatabase extends RoomDatabase < private static UsersDatabase INSTANCE; public abstract UserDao userDao();
Поскольку мы хотим сохранить пользовательские данные, нам нужно реализовать класс Migration , сообщающий Room, что он должен делать при переходе с версии 1 на 2. В нашем случае, поскольку схема базы данных не изменилась, мы просто предоставим пустую реализацию:
static final Migration MIGRATION_1_2 = new Migration(1, 2) < @Override public void migrate(SupportSQLiteDatabase database) < // Поскольку мы не изменяли таблицу, здесь больше ничего не нужно делать. >>;
Создайте объект базы данных в классе UsersDatabase , определив имя базы данных и миграцию:
database = Room.databaseBuilder(context.getApplicationContext(), UsersDatabase.class, "Sample.db") .addMigrations(MIGRATION_1_2) .build();
Чтобы узнать больше о том, как реализовать миграцию баз данных и как они работают под капотом, посмотрите этот пост.
Шаг 5. Обновление репозитория для использования Room
Мы создали нашу базу данных, нашу таблицу пользователей и запросы, так что теперь пришло время их использовать. На этом этапе мы обновим класс LocalUserDataSource для использования методов UserDao . Для этого мы сначала обновим конструктор: удалим Context и добавим UserDao . Конечно, любой класс, который создаёт экземпляр LocalUserDataSource , также должен быть обновлен.
Далее мы обновим методы LocalUserDataSource , которые делают запросы с помощью вызова методов UserDao . Например, метод, который запрашивает всех пользователей, теперь выглядит так:
public List getUsers()
А теперь время запустить то, что у нас получилось.
Одна из лучших функций Room — это то, что если вы выполняете операции с базой данных в главном потоке, то ваше приложение упадёт со следующим сообщением об ошибке:
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
Один надёжный способ переместить операции ввода-вывода из основного потока — это создать новый Runnable , который будет создавать новый поток для каждого запроса к базе данных. Поскольку мы уже используем этот подход в варианте sqlite, никаких изменений не потребовалось.
Шаг 6. Тестирование на устройстве
Мы создали новые классы — UserDao и UsersDatabase и изменили наш LocalUserDataSource для использования базы данных Room. Теперь нам нужно их протестировать.
Тестирование UserDao
Чтобы протестировать UserDao , нам нужно создать тестовый класс AndroidJUnit4 . Потрясающая особенность Room — это возможность создавать базу данных в памяти. Это исключает необходимость очистки после каждого теста.
@Before public void initDb() throws Exception
Нам также нужно убедиться, что мы закрываем соединение с базой данных после каждого теста.
@After public void closeDb() throws Exception
Например, чтобы протестировать вход пользователя, мы добавим пользователя, а затем проверим, сможем ли мы получить этого пользователя из базы данных.
@Test public void insertAndGetUser() < // Добавление пользователя в базу данных mDatabase.userDao().insertUser(USER); // Проверка возможности получения пользователя из базы данных Listusers = mDatabase.userDao().getUsers(); assertThat(users.size(), is(1)); User dbUser = users.get(0); assertEquals(dbUser.getId(), USER.getId()); assertEquals(dbUser.getUserName(), USER.getUserName()); >
Тестирование использования UserDao в LocalUserDataSource
Убедиться, что LocalUserDataSource по-прежнему работает правильно, легко, поскольку у нас уже есть тесты, которые описывают поведение этого класса. Всё, что нам нужно сделать, это создать базу данных в памяти, получить из нее объект UserDao и использовать его в качестве параметра для конструктора LocalUserDataSource .
@Before public void initDb() throws Exception
Опять же, нам нужно убедиться, что мы закрываем базу данных после каждого теста.
Тестирование миграции базы данных
Подробнее почитать о том, как реализовать тесты миграции баз данных, а также, как работает MigrationTestHelper , можно в этом посте.
Вы также можете посмотреть код из более детального примера приложения миграции.
Шаг 7. Удаление всего ненужного
Удалите все неиспользуемые классы и строки кода, которые теперь заменены функциональностью Room. В нашем проекте нам просто нужно удалить класс UsersDbHelper , который расширял класс SQLiteOpenHelper .
Если у вас есть большая и более сложная база данных, и вы хотите постепенно перейти на Room, то рекомендуем этот пост.
Теперь количество стандартного кода, подверженного ошибкам, уменьшилось, запросы проверяются во время компиляции, и всё тестируется. За 7 простых шагов мы смогли мигрировать наше существующее приложение на Room. Пример приложения можете посмотреть здесь.
- android development
- android
- андроид
- libraries
- libs
- библиотеки
- базы данных
- room
- архитектурные компоненты
- перевод с английского
- программирование
- разработка
- devcolibri
- никто не читает теги
- Программирование
- Разработка мобильных приложений
- Разработка под Android
Room: Хранение данных на Android для всех и каждого
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
Чтобы рассказать Room о классе Person, добавляем аннотицию Entity к классу и @PrimaryKey к ключу:
@Entity public class Person
Благодаря этим двум аннотациям 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") ListgetAllPeople(); // Получение всех Person из бд с условием @Query("SELECT * FROM person WHERE favoriteColor LIKE :color") List 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 = , version = 1) public abstract class AppDatabase extends RoomDatabase
Это лишь описание структуры базы данных, но сама база данных будет жить в одном файле. Чтобы получить экземпляр AppDatabase, сохраненный в файле с именем populus-database, вы должны написать:
AppDatabase db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "populus-database").build();
Если вы хотите получить все данные обо всех Person, которые находятся в базе данных, вы могли бы написать:
List everyone = db.getPersonDao().getAllPeople();
Преимущества использования Room
В отличие от большинства ORM, Room использует обработчик аннотации для выполнения всей своей манеры сохранения данных. Это означает, что ни ваши классы приложений, ни классы моделей не должны ничего расширять в Room, в отличие от многих других ORM, включая Realm и SugarORM. Как вы видели при ошибках с аннотациями Query () выше, вы также получаете возможность проверки корректности SQL-запросов во время компиляции, что может сэкономить вам много хлопот.
Room также позволяет вам наблюдать за изменениями данных, интегрируя их как с API LiveData Архитектурных Компонентов, так и с RxJava 2. Это означает, что если у вас сложная схема, где изменения в базе данных должны появляться в нескольких местах вашего приложения, Room делает уведомления об изменениях. Это мощное дополнение может быть включено одной строкой. Все, что вам нужно сделать, это изменить тип возвращаемых значений.
Например, этот метод:
@Query("SELECT * FROM person") List getAllPeople();
@Query("SELECT * FROM person") LiveData> /* or Observable> */ getAllPeople();
Самое большое ограничение в Room: взаимосвязи
Самым большим ограничением в Room является то, что он не будет обрабатывать отношения с другими типами сущностей для вас автоматически, как и другие ORM. Это означает, что если вы хотите отслеживать домашних животных:
@Entity public class Person < @PrimaryKey String name; int age; String favoriteColor; Listpets; > @Entity public class Pet
То 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 getPetsForOwner(String ownerId);
Стоит ли использовать Room?
Если вы уже настроили сохранение данных в своем приложении и довольны им, то ничего не изменяйте. Каждая ORM и встроенная реализация SQLite будут продолжать работать так же, как и раньше. Room — это всего лишь еще один вариант сохранения данных.
Если вы используете SQLite или собираетесь использовать его, вы должны попробовать Room. Он обладает всеми возможностями, необходимыми для выполнения расширенных запросов, одновременно устраняя необходимость писать SQL-запросы для поддержки базы данных самостоятельно.
- Java
- Разработка мобильных приложений
- Разработка под Android