Przechowywanie informacji w bazie danych jest jednym z najpopularniejszych sposobów zarządzania zbiorem danych o ustalonej strukturze. Doskonale spełnia swoją rolę w sytuacji gromadzenia przede wszystkim wielu obiektów zdefiniowanych typów (np. kontakty). Podobnie jak w przypadku plików w storage czy wartości prymitywnych w SharedPreferences dane pozostają dostępne do odczytu i zapisu niezależnie od cyklu życia aplikacji. Są zachowane w pamięci wewnętrznej urządzenia dopóki nie zostaną usunięte programowo lub ręcznie przez użytkownika poprzez wyczyszczenie danych aplikacji. Android wykorzystuje bazy danych w schemacie SQL w implementacji SQLite. Pomimo możliwości bezpośredniego operowania na bazie danych za pomocą zapytań SQLite przez klienta SQLiteOpenHelper wysoce zalecane jest użycie biblioteki Room dostarczającej dodatkowej warstwy abstrakcji.
Implementacja
Jedną z głównych zasad tworzenia baz danych SQL jest formalna deklaracja struktury. Definiuje ona sposób w jaki baza danych jest organizowana oraz jakie typy obiektów mogą być przechowywane i modyfikowane. Dane znajdują się w tabelach zbudowanych z kolumn i wierszy. Reprezentacją jednego wpisu (porcji informacji) jest encja. Aby zaimplementować bazę danych należy rozszerzyć klasę SQLiteOpenHelper oraz nadpisać metody onCreate i onUpgrade definiując jej strukturę. Modyfikacja zawartości bazy odbywa się przy pomocy zapytań SQL w metodach put, query, delete, update.
Ograniczenia
SQLiteOpenHelper umożliwia zarządzanie zarówno strukturą jak i zawartością bazy danych. Jest narzędziem niższego poziomu, które wymaga od programisty implementacji kodu związanego ze strukturą i zapytaniami do bazy co jest podatne na błędy z uwagi na brak weryfikacji poprawności poleceń SQL i zgodności ze schematem bazy. Wymaga generowania nadmiarowego kodu konwersji zapytań SQL do obiektów i na odwrót (praktycznie jedna metoda dla każdej atomowej operacji dla danego typu). Rozwiązaniem tych problemów może być wykorzystanie biblioteki Room.
Room
Room dostarcza warstwy abstrakcji dla SQLite dzięki czemu dostęp i zarządzanie bazą danych staje się łatwiejsze, a pisanie kodu szybsze. Weryfikuje poprawność zapytań SQL już w trakcie kompilacji co znacznie zmniejsza możliwość popełnienia błędu. Ponadto wspiera mechanizm transakcji i dostarcza wiele adnotacji redukując tym samym potrzebę pisania wielu zapytań. Aby umożliwić współpracę z Room należy dostarczyć trzy komponenty oznaczone jako @Database, @Entity, @Dao.
Entity
Klasa oznaczona jako @Entity reprezentuje tabele, gdzie każde jej pole jest kolumną. Pełni ona rolę modelu w bazie danych i nie zawiera żadnej logiki. @PrimaryKey ustawia klucz podstawowy na wskazanym polu, a @ForeignKey wskazuje klucz obcy i łączy. @ColumnInfo definiuje nazwę kolumny, @Embedded umożliwia dostęp do wewnętrznych pól klasy jako kolumn tabeli, natomiast @Ignore pozwala zignorowanie konstruktorów czy pól przy tworzeniu obiektu przez RoomDatabase (jeśli jest kilka konstruktorów).
Dao
Klasa oznaczona jako @Dao odpowiedzialna jest za dostarczenie deklaracji metod dostępowych do baz danych. Są one tworzone przy użyciu różnych adnotacji takich jak m.in. @Insert, @Update, @Delete które automatycznie generują kod zapytań czy też przez adnotację @Query wymagającej definicji zapytania. W przypadku ciągu operacji, które muszą zostać wykonane w ramach jednej transakcji należy użyć adnotacji @Transaction.
Database
Klasa oznaczona jako @Database łączy wybrane tabele klas @Entity i metody dostępowe klas @Dao w jedną całość. Taka klasa musi być abstrakcyjna i rozszerzać RoomDatabase oraz deklarować metody abstrakcyjne zwracające obiekty @Dao.
Użycie
Tworzenie instancji bazy danych RoomDatabase odbywa się przy użyciu budowniczego. Ze względu na kosztowność bazy warto rozważyć zastosowanie wzorca Singleton i tym samym ograniczyć instancję do jednej dla całej aplikacji.
Migracja
W trakcie rozwijania aplikacji nierzadko występuje potrzeba zmiany struktury bazy danych. W takiej sytuacji może okazać się, że jakaś część danych z poprzedniego formatu bazy jest nadal potrzebna. W związku z tym należy zapewnić poprawną migrację danych między wersjami bazy poprzez skonstrukowanie odpowiedniego zapytania SQL w obiekcie Migration oraz dodanie go przez addMigrations w trakcie tworzenia bazy. Z uwagi na to, że proces migracji narażony jest na występowanie poważnych błędów przed wypuszczeniem wersji produkcyjnej aplikacji nie można zapomnieć o lokalnej kopii zapasowej danych i właściwych testach migracji wspieranych przez MigrationTestHelper.