Room je ORM knihovna ze sady Android Jetpack, která poskytuje abstrakční vrstvu nad SQLite pro práci s lokálními databázemi na Androidu. Podle oficiální dokumentace Android Developers, 2025, Room automaticky generuje implementace DAO na základě anotací během kompilace, což eliminuje přibližně 70 % šablonového kódu ve srovnání s přímým použitím SQLiteOpenHelper. Knihovna provádí ověření SQL dotazů ve fázi kompilace, což umožňuje odhalit syntaktické chyby před spuštěním aplikace na zařízení.
Hlavní body
Room je ORM knihovna ze sady Android Jetpack, vytvořená společností Google pro zjednodušení práce s lokálními SQLite databázemi na platformě Android. Poskytuje anotace pro popis schématu dat a automaticky generuje implementaci rozhraní DAO ve fázi kompilace. Na rozdíl od přímého použití SQLiteOpenHelper, Room zbavuje vývojáře psaní značného množství šablonového kódu pro vytváření, otevírání a správu připojení k databázi.
Knihovna byla představena na Google I/O 2017 jako součást architektonických komponent Android. Od té doby se Room stal de facto standardem pro lokální ukládání dat, čímž v popularitě předčil řešení jako GreenDAO a Realm pro Android. Podle údajů Google je knihovna používána ve více než 60 % aplikací publikovaných v Google Play, které pracují s lokálními daty na zařízení.
Klíčová vlastnost — ověření SQL dotazů ve fázi kompilace pomocí procesoru anotací. Pokud vývojář udělá chybu v SQL příkazu, například uvede neexistující název sloupce, kompilace skončí chybou před instalací aplikace. To se zásadně liší od přístupu SQLiteOpenHelper, kde jsou takové chyby odhaleny až během běhu, často v produkci.
SQLite podporuje pouze pět datových typů: TEXT, INTEGER, REAL, BLOB a NULL. V Javě a Kotlinu se však používají komplexní typy: Date, List, Enum a vlastní objekty. Pro jejich ukládání poskytuje Room mechanismus TypeConverters — statických metod, které převádějí komplexní typ na primitivní typ srozumitelný pro SQLite. Například objekt Date se převádí na Long (timestamp) a List<String> na JSON řetězec pomocí Gson nebo Moshi.
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
val db = Room
.databaseBuilder(context, AppDatabase::class.java, "app-db")
.build()
Pro deklaraci převodníku stačí přidat anotaci @TypeConverter ke statické metodě a uvést třídu převodníku v anotaci @TypeConverters na úrovni databáze. Room automaticky aplikuje převodník při čtení a zápisu odpovídajícího typu v každém SQL dotazu bez ručního volání převodních metod.
Room se skládá ze tří hlavních komponent: Entity, DAO a Database. Každá plní přesně definovanou roli a je anotována příslušnou anotací. Společně tvoří plnohodnotnou vrstvu přístupu k datům, která izoluje obchodní logiku aplikace od detailů implementace SQLite.
Entity je datová třída popisující strukturu jedné tabulky v databázi. Každé pole třídy odpovídá sloupci tabulky a každý řádek v databázi jedné instanci třídy. Anotace @Entity informuje Room, že třída je tabulka. Pole s anotací @PrimaryKey definuje primární klíč, který může být auto-inkrementální nebo složený. Pro vztahy mezi tabulkami se používá @ForeignKey, zajišťující integritu dat na úrovni databáze.
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "full_name")
val name: String,
val age: Int,
val email: String
)
DAO (Data Access Object) je rozhraní nebo abstraktní třída deklarující operace pro práci s daty: vkládání, čtení, aktualizaci a mazání. Každá operace je anotována @Insert, @Query, @Update nebo @Delete. Room automaticky generuje implementaci tohoto rozhraní ve fázi kompilace. Zvláštní hodnotu má anotace @Query, která přijímá SQL dotaz jako řetězec a kontroluje jeho správnost ve fázi sestavení.
@Dao
interface UserDao {
@Insert
suspend fun insert(user: User): Long
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: Int): User?
@Query("SELECT * FROM users")
fun getAllUsers(): Flow<List<User>>
@Delete
suspend fun delete(user: User)
}
Database je abstraktní třída dědící z RoomDatabase, sloužící jako vstupní bod do databáze. Obsahuje seznam všech Entity a poskytuje abstraktní metody pro získání DAO. Třída je anotována @Database, kde jsou uvedeny verze schématu a seznam entit. Vytvoření instance databáze se provádí pomocí Room.databaseBuilder s uvedením kontextu aplikace, názvu souboru a třídy Database.
Room nenahrazuje SQLite, ale pracuje nad ním jako abstrakční vrstva. Vnitřní architektura zahrnuje procesor anotací, generátor kódu a fond připojení. Ve fázi kompilace procesor anotací analyzuje třídy Entity, DAO a Database, poté generuje implementační třídy s příponou _Impl. Všechny vygenerované třídy jsou umístěny do balíčku sestavení a nejsou přímo viditelné pro vývojáře.
Generování kódu ve fázi kompilace — ústřední mechanismus Room. Pro každé rozhraní DAO je generována třída s plnou implementací všech anotovaných metod. SQL dotazy z anotace @Query jsou kontrolovány na správnost: procesor přiřazuje názvy sloupců k polím Entity a kontroluje syntaxi SQL. Při detekci chyby je kompilace přerušena srozumitelnou zprávou. To není možné při použití holého SQLiteOpenHelper, kde se chyby objevují až za běhu.
Proces generování zahrnuje tři fáze. První — validace schématu: procesor kontroluje, zda všechny třídy uvedené v @Database jsou platné Entity. Druhá — generování těla DAO: pro každou metodu je vytvořena implementace pomocí interního objektu RoomSQLiteQuery, který provádí připravené dotazy. Třetí — generování třídy Database_Impl, implementující vytvoření a otevření databáze a inicializaci všech objektů DAO.
class UserDao_Impl(private val __db: RoomDatabase) : UserDao {
private val __insertionAdapter = __db
.createInsertionAdapter(User::class, 0)
override suspend fun insert(user: User): Long {
__db.assertNotSuspendingTransaction()
return __db.runInTransaction {
__insertionAdapter.insertAndReturnId(user)
}
}
}
Room nevytváří samostatný fond vláken pro operace s databází. Ve výchozím nastavení jsou dotazy prováděny ve volajícím vlákně s jedním omezením: čtení a zápis blokují vlákno. Pro asynchronní práci se Room integruje s korutinami Kotlin pomocí funkcí suspend, s LiveData pomocí návratových hodnot a s Flow pomocí reaktivních obalů. To dává vývojáři flexibilitu při výběru architektonického řešení pro konkrétní úkol.
Podívejme se na praktický příklad vytvoření aplikace pro ukládání poznámek pomocí Room. Aplikace obsahuje jednu tabulku Note s poli id, title, content a timestamp. Uživatel bude moci přidávat, prohlížet a mazat poznámky. Pro demonstraci jsou použity korutiny pro asynchronní operace.
Pro připojení Room k Android projektu je třeba přidat závislosti do souboru build.gradle modulu aplikace. Room vyžaduje tři komponenty: runtime knihovnu, procesor anotací kapt a volitelnou podporu pro korutiny. Verze knihovny je uvedena v proměnné room_version pro snadnou aktualizaci. Od Room 2.4.0 je KSP podporován jako alternativa kapt s vyšší rychlostí sestavení.
dependencies {
def room_version = "2.6.1"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
// Volitelně: testování
testImplementation "androidx.room:room-testing:$room_version"
}
Po konfiguraci závislostí jsou vytvořeny tři soubory: Entity Note, rozhraní NoteDao a třída AppDatabase. Entity Note obsahuje pole s anotacemi @PrimaryKey a @ColumnInfo. DAO poskytuje metody pro vkládání, získání seznamu a mazání. Database propojuje Entity a DAO prostřednictvím anotace @Database.
@Entity(tableName = "notes")
data class Note(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val title: String,
val content: String,
@ColumnInfo(name = "created_at")
val timestamp: Long = System.currentTimeMillis()
)
@Dao
interface NoteDao {
@Insert
suspend fun insert(note: Note)
@Query("SELECT * FROM notes ORDER BY created_at DESC")
fun getAllNotes(): Flow<List<Note>>
@Delete
suspend fun delete(note: Note)
}
Soubor AppDatabase je deklarován jako abstraktní třída dědící z RoomDatabase. V anotaci @Database jsou uvedeny všechny Entity aktuální verze a číslo verze schématu. Pro získání instance se používá vzor singleton prostřednictvím metody build Room.databaseBuilder s kontextem aplikace. Ukládání instance databáze do mezipaměti zabraňuje vícenásobnému vytváření, které by mohlo vést k únikům paměti.
Migrace v Room jsou mechanismem pro změnu schématu databáze při aktualizaci aplikace bez ztráty existujících dat. Když uživatel nainstaluje novou verzi se změněnými Entity, Room detekuje nesoulad verzí a provede zadané migrační kroky. Bez migrace bude databáze smazána a znovu vytvořena, což povede ke ztrátě všech uložených uživatelských dat.
Migrace je popsána třídou Migration, která přijímá počáteční a konečnou verzi databáze. Uvnitř metody migrate se provádí SQL dotaz ALTER TABLE nebo CREATE TABLE pro změnu schématu. Room neumí automaticky zjišťovat změny schématu — vývojář musí napsat migraci ručně pro každou změnu Entity. Od verze Room 2.4.0 je k dispozici experimentální funkce autoMigrations pro automatické generování migrací.
Funkce autoMigrations umožňuje Room automaticky generovat migrace na základě rozdílů mezi verzemi Entity. Pro její použití stačí přidat anotaci @AutoMigration do @Database a určit export schématu do JSON. Room porovnává schémata sousedních verzí a generuje potřebné ALTER dotazy. Nicméně autoMigrations podporuje pouze zpětně kompatibilní změny: přidávání sloupců, vytváření indexů a změny typů s kompatibilními převody.
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"ALTER TABLE users ADD COLUMN phone TEXT"
)
}
}
val db = Room
.databaseBuilder(context, AppDatabase::class.java, "app-db")
.addMigrations(MIGRATION_1_2)
.build()
Při přidávání složitých změn, jako je přejmenování sloupců nebo slučování tabulek, je vyžadována ruční migrace s použitím dočasných tabulek. Typický scénář: vytvořte dočasnou tabulku se starým schématem, zkopírujte data ze staré tabulky do nové s převody, smažte starou tabulku a přejmenujte dočasnou. Room zaručuje, že všechny migrace jsou prováděny v jedné transakci a při výskytu chyby jsou změny zcela vráceny zpět.
Často kladené otázky
Room poskytuje ORM abstrakci s anotacemi a ověřením SQL ve fázi kompilace, zatímco SQLiteOpenHelper vyžaduje ruční psaní všech dotazů a správu připojení. Room automaticky generuje kód pro CRUD operace a integruje se s architektonickými komponentami Android, včetně LiveData a Flow.
Room podporuje všechny primitivní typy Java: Int, Long, Boolean, Float, Double, dále String, ByteArray a Date. Pro komplexní typy, jako je List nebo Enum, se používají TypeConverters — statické převodní metody, které převádějí nestandardní typy do formátů podporovaných SQLite.
Ano, Room podporuje synchronní volání bez korutin, ale blokují vlákno, ve kterém jsou prováděny. Pro asynchronní práci lze použít LiveData nebo RxJava místo korutin. Google doporučuje používat korutiny jako hlavní způsob asynchronního přístupu k datům v nových projektech.
Pokud Room detekuje nesoulad verze databáze a nenajde vhodnou migraci, ve výchozím nastavení dojde k IllegalStateException s popisem chyby. Vývojář může toto chování přepsat metodou fallbackToDestructiveMigration, která odstraní stávající databázi a vytvoří novou se ztrátou všech dat.
Room podporuje vztahy prostřednictvím vnořených objektů s anotací @Embedded a prostřednictvím relačních tříd s anotací @Relation. Pro složité dotazy se spojováním tabulek se používají vlastní POJO třídy, jejichž pole jsou plněna z výsledků @Query s operátorem JOIN v SQL.
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é