Room je knihovna pro práci s SQLite v Androidu, která je součástí Jetpack. Poskytuje vrstvu abstrakce nad čistým SQLite, automatizuje vytváření tabulek, provádění dotazů a převod dat na objekty Kotlin a Java. Podle Android Developers Room kompiluje SQL dotazy během fáze sestavení a kontroluje správnost syntaxe a vztahů mezi Entity a tabulkami.
Hlavní body
Room je knihovna persistence z Android Jetpack, která poskytuje objektově-relační mapování pro SQLite. Room řeší tři hlavní problémy čistého SQLite: psaní velkého množství boilerplate kódu pro vytváření tabulek, chybějící kontrolu SQL dotazů v době kompilace a ruční převod Cursor na objekty.
Knihovna používá kompilátor anotací (kapt nebo KSP), který generuje implementaci abstraktních tříd RoomDatabase a DAO během fáze sestavení. To zaručuje, že syntaktické chyby v SQL a neshody typů jsou odhaleny před spuštěním aplikace, nikoli za běhu po publikování v Google Play.
Podle Google I/O 2023 je Room používán v 68% Android aplikací, které pracují s lokálními daty. Je to standard ukládání dat na zařízení, doporučený Googlem pro všechny nové projekty — namísto zastaralých SQLiteOpenHelper a ContentProvider.
Implementujte Room v projektech, kde je vyžadováno lokální ukládání dat z serveru do mezipaměti, offline režim nebo ukládání strukturovaných uživatelských dat s možností komplexních SQL dotazů.
Room je součástí Android Jetpack a je oficiálně doporučen Googlem pro všechny nové projekty pracující s lokálními daty. Na rozdíl od Realm nebo ObjectBox Room používá nativní SQLite, což zaručuje kompatibilitu s jakýmikoli nástroji třetích stran pro práci s databází — od DB Browser po DataGrip. Vývojář může otevřít .db soubor aplikace a provádět SQL dotazy přímo, což usnadňuje ladění a analýzu dat během vývoje.
Entity je datová třída anotovaná @Entity, kterou Room přemění na tabulku databáze. Každé pole třídy se stane sloupcem tabulky a každá instance řádkem. Room používá reflexi pro přístup k polím, proto je vyžadována anotace @PrimaryKey pro povinný identifikátor.
Anotace @Entity informuje Room, že třída je tabulka. Parametr tableName nastavuje název tabulky, pokud se liší od názvu třídy. @PrimaryKey definuje primární klíč s možností automatického generování pomocí autoGenerate = true.
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "full_name")
val name: String,
@Ignore
val tempData: String?
)
@ColumnInfo nastavuje název sloupce v tabulce, pokud se liší od názvu pole Kotlin. @Ignore vylučuje pole z tabulky — nebude uloženo do databáze. @ForeignKey popisuje cizí klíče pro vztahy mezi tabulkami s kaskádovými operacemi při mazání nebo aktualizaci.
Room podporuje vnořené objekty prostřednictvím anotace @Embedded. Pole vnořené třídy se rozvinou do sloupců nadřazené tabulky s prefixem pro zabránění konfliktům názvů. Například třída Address s poli city a street, vložená do User, vytvoří sloupce address_city a address_street v tabulce users, čímž odpadá potřeba vytvářet samostatné tabulky pro jednoduché hodnotové objekty.
Room podporuje pouze primitivní typy a jejich obaly. Pro ukládání seznamů, Date nebo vlastních typů se používá @TypeConverter — statické metody převodu mezi vlastním typem a primitivem SQLite, například mezi List a JSON řetězcem.
DAO (Data Access Object) je rozhraní nebo abstraktní třída anotovaná @Dao, obsahující metody pro přístup k datům. Každá metoda je anotována SQL operací: @Insert, @Update, @Delete nebo @Query s explicitním SQL dotazem.
Anotace @Query přijímá SQL řetězec, který je Roomem kontrolován v době kompilace na správnost syntaxe a shodu názvů sloupců s poli Entity. Room podporuje parametrizované dotazy pomocí syntaxe :paramName.
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: Int): User?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User)
@Query("SELECT * FROM users ORDER BY name ASC")
fun getAllUsers(): Flow<List<User>>
}
@Insert podporuje strategie OnConflictStrategy pro zpracování konfliktů při vkládání duplicitních záznamů. Flow jako návratový typ zajišťuje reaktivní aktualizaci UI při každé změně dat v tabulce — odběr se automaticky restartuje při každém INSERT, UPDATE nebo DELETE.
Anotace @Transaction zaručuje atomické provedení více operací v jednom transakčním bloku. Room uzamkne databázi během provádění a zabraňuje race condition při současném přístupu z více vláken.
RoomDatabase je abstraktní třída, která spojuje Entity a DAO do jediného přístupového bodu k databázi. Vytváří se pomocí Room.databaseBuilder s uvedením verze schématu a seznamu tříd Entity. Instanci databáze se doporučuje vytvářet jako singleton pomocí lazy delegátu, aby se předešlo vícenásobným připojením.
Migrace v Room je třída Migration popisující SQL skript pro přechod ze staré verze schématu na novou. Pokud není migrace poskytnuta při změně schématu, Room vyhodí IllegalStateException. To chrání před náhodnou ztrátou uživatelských dat při aktualizaci aplikace.
val migration_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE users ADD COLUMN age INTEGER NOT NULL DEFAULT 0")
}
}
val db = Room.databaseBuilder(
getApplication(),
AppDatabase::class.java,
"app_database"
).addMigrations(migration_1_2)
.build()
Pro vývoj lze použít fallbackToDestructiveMigration, který při neshodě verzí smaže starou databázi a vytvoří novou. Tento režim je určen pouze pro ladění — v produkčních verzích se migrace povinně píší.
Pro testování databáze Room poskytuje speciální třídu Room.inMemoryTestBuilder, která vytváří databázi v RAM bez ukládání na disk. Po dokončení každého testu je databáze automaticky zničena, což zaručuje úplnou izolaci testovacích scénářů. V kombinaci s knihovnou android-arch-core-testing může vývojář spravovat životní cyklus databáze a kontrolovat správnost migrací bez nutnosti ručně čistit stav.
Výkon Room přímo závisí na struktuře dotazů a indexů. Pro analýzu pomalých dotazů Room poskytuje příznak enableQueryCallback, který loguje všechny SQL dotazy s dobou provedení. Vývojář může tento log použít k nalezení dotazů trvajících déle než 100 milisekund a optimalizovat je přidáním složených indexů pomocí anotace @Index v @Entity nebo přepsáním poddotazů na přímá JOIN spojení s využitím @Relation.
Room také podporuje šifrování databáze prostřednictvím SQLCipher. Připojení knihovny net.zetetic:android-database-sqlcipher a použití SupportFactory místo standardního zajišťuje transparentní šifrování všech dat na disku beze změny DAO dotazů a struktury Entity. To je nezbytné pro aplikace pracující s osobními údaji uživatelů a splňuje požadavky GDPR a ruského zákona 152-FZ o ochraně osobních údajů. Šifrovací heslo může být uloženo v Android Keystore pro ochranu před extrakcí pomocí nástrojů na rootovaných zařízeních.
Room nativně podporuje Kotlin Coroutines od verze 2.1. DAO metody mohou být suspend funkce provádějící dotazy na pozadí bez blokování hlavního vlákna. Room automaticky spravuje dispečery a používá Dispatchers.IO pro čtecí a zapisovací dotazy.
Pro reaktivní dotazy Room vrací Flow — studený datový tok, který emituje novou hodnotu při každé změně dotčené tabulky. ViewModel se přihlásí k odběru Flow pomocí stateIn nebo collect, což zajišťuje automatickou aktualizaci UI bez ručního oznamování adaptéru.
Room také podporuje Paging 3 prostřednictvím speciální implementace PagingSource, která načítá data po stránkách z SQLite. To je efektivní pro velké seznamy s tisíci záznamy: Paging 3 načítá pouze řádky viditelné na obrazovce a automaticky je aktualizuje při změnách v databázi.
Používejte Paging 3 s Room při zobrazování zpravodajského kanálu, protokolu operací nebo seznamu produktů s možností offline přístupu a nekonečným posouváním.
Často kladené otázky
Room automatizuje vytváření tabulek, převod Cursor na objekty a kontrolu SQL v době kompilace. SQLiteOpenHelper vyžaduje ruční psaní schématu, zpracování Cursor a nemá kontrolu dotazů před spuštěním aplikace, což zvyšuje riziko chyb.
Ano, při změně Entity (přidání/odebrání pole, změna typu) je vyžadována migrace. Bez ní Room při spuštění vyhodí IllegalStateException. Pro vývoj lze zapnout fallbackToDestructiveMigration, ale v release jsou povinné správné migrační skripty.
Room podporuje @ForeignKey pro kaskádové operace a @Relation pro vnořené objekty. Pro komplexní JOIN dotazy se používá anotace @Transaction s @Query vracející POJO s vnořenými entitami pomocí @Embedded a @Relation.
Ano, Room je plně kompatibilní s Java. Místo suspend funkcí se používají LiveData nebo RxJava Observable, místo Flow — LiveData. Room s Java podporuje všechny stejné anotace, ale vyžaduje více boilerplate kódu pro asynchronní operace.
Room podporuje šifrování prostřednictvím SQLCipher od Zetetic. Místo Room.databaseBuilder použijte SupportFactory z knihovny net.zetetic:android-database-sqlcipher a předejte šifrovací heslo. Všechna data na disku budou transparentně zašifrována pro DAO dotazy.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také