Room is een ORM-bibliotheek uit de Android Jetpack-suite die een abstractielaag boven SQLite biedt voor het werken met lokale databases op Android. Volgens de officiële documentatie Android Developers, 2025, genereert Room automatisch DAO-implementaties op basis van annotaties tijdens het compileren, waardoor ongeveer 70% van de boilerplate-code wordt geëlimineerd in vergelijking met direct gebruik van SQLiteOpenHelper. De bibliotheek voert validatie van SQL-query's uit in de compilatiefase, waardoor syntaxisfouten kunnen worden opgespoord voordat de app op het apparaat wordt gestart.
Belangrijkste punten
Room is een ORM-bibliotheek uit de Android Jetpack-suite, gemaakt door Google om het werken met lokale SQLite-databases op het Android-platform te vereenvoudigen. Het biedt annotaties voor het beschrijven van het gegevensschema en genereert automatisch de implementatie van DAO-interfaces tijdens het compileren. In tegenstelling tot direct gebruik van SQLiteOpenHelper, bevrijdt Room de ontwikkelaar van het schrijven van aanzienlijke hoeveelheden boilerplate-code voor het maken, openen en beheren van de databaseverbinding.
De bibliotheek werd geïntroduceerd op Google I/O 2017 als onderdeel van de Android-architectuurcomponenten. Sindsdien is Room de de facto standaard geworden voor lokale gegevensopslag, waarmee het qua populariteit oplossingen zoals GreenDAO en Realm voor Android heeft overtroffen. Volgens Google wordt de bibliotheek gebruikt in meer dan 60% van de apps die in Google Play zijn gepubliceerd en die met lokale gegevens op het apparaat werken.
Belangrijkste kenmerk — validatie van SQL-query's tijdens het compileren met behulp van een annotatieprocessor. Als de ontwikkelaar een fout maakt in een SQL-opdracht, bijvoorbeeld een niet-bestaande kolomnaam opgeeft, eindigt de compilatie met een fout voordat de app wordt geïnstalleerd. Dit verschilt fundamenteel van de SQLiteOpenHelper-aanpak, waar dergelijke fouten pas tijdens de uitvoering worden ontdekt, vaak in de productie.
SQLite ondersteunt slechts vijf gegevenstypen: TEXT, INTEGER, REAL, BLOB en NULL. In Java en Kotlin worden echter complexe typen gebruikt: Date, List, Enum en aangepaste objecten. Voor het opslaan ervan biedt Room het TypeConverters-mechanisme — statische methoden die een complex type omzetten naar een primitief type dat begrijpelijk is voor SQLite. Een Date-object wordt bijvoorbeeld geconverteerd naar Long (timestamp) en List<String> naar een JSON-string via Gson of 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()
Om een converter te declareren, volstaat het om de @TypeConverter-annotatie aan een statische methode toe te voegen en de converterklasse op te geven in de @TypeConverters-annotatie op databaseniveau. Room past de converter automatisch toe bij het lezen en schrijven van het overeenkomstige type in elke SQL-query zonder handmatig aanroepen van conversiemethoden.
Room bestaat uit drie hoofdcomponenten: Entity, DAO en Database. Elk vervult een strikt gedefinieerde rol en wordt geannoteerd met de bijbehorende annotatie. Samen vormen ze een volledige gegevenstoegangslaag die de bedrijfslogica van de app isoleert van de implementatiedetails van SQLite.
Entity is een gegevensklasse die de structuur van één tabel in de database beschrijft. Elk veld van de klasse komt overeen met een kolom van de tabel en elke rij in de database met één instantie van de klasse. De @Entity-annotatie vertelt Room dat de klasse een tabel is. Het veld met de @PrimaryKey-annotatie definieert de primaire sleutel, die automatisch oplopend of samengesteld kan zijn. Voor relaties tussen tabellen wordt @ForeignKey gebruikt, die de gegevensintegriteit op databaseniveau waarborgt.
@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) is een interface of abstracte klasse die bewerkingen voor het werken met gegevens declareert: invoegen, lezen, bijwerken en verwijderen. Elke bewerking wordt geannoteerd met @Insert, @Query, @Update of @Delete. Room genereert automatisch de implementatie van deze interface tijdens het compileren. Bijzondere waarde heeft de @Query-annotatie, die een SQL-query als string ontvangt en de juistheid ervan controleert tijdens de buildfase.
@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 is een abstracte klasse die overerft van RoomDatabase en dient als toegangspunt tot de database. Het bevat een lijst van alle Entity's en biedt abstracte methoden voor het verkrijgen van DAO's. De klasse wordt geannoteerd met @Database, waarin de schemaversie en de lijst van entiteiten worden gespecificeerd. Het maken van een database-instantie gebeurt via Room.databaseBuilder met opgave van de app-context, bestandsnaam en Database-klasse.
Room vervangt SQLite niet, maar werkt er bovenop als een abstractielaag. De interne architectuur omvat een annotatieprocessor, codegenerator en een verbindingspool. Tijdens het compileren analyseert de annotatieprocessor de Entity-, DAO- en Database-klassen en genereert vervolgens implementatieklassen met het achtervoegsel _Impl. Alle gegenereerde klassen worden in het buildpakket geplaatst en zijn niet direct zichtbaar voor de ontwikkelaar.
Codegeneratie tijdens het compileren — het centrale mechanisme van Room. Voor elke DAO-interface wordt een klasse gegenereerd met een volledige implementatie van alle geannoteerde methoden. SQL-query's uit de @Query-annotatie worden gecontroleerd op juistheid: de processor matcht kolomnamen met Entity-velden en controleert de SQL-syntax. Bij detectie van een fout wordt de compilatie onderbroken met een duidelijke melding. Dit is onmogelijk bij het gebruik van ruwe SQLiteOpenHelper, waar fouten pas tijdens runtime optreden.
Het generatieproces omvat drie fasen. Eerste — schemavalidatie: de processor controleert of alle klassen die in @Database worden vermeld, geldige Entity's zijn. Tweede — het genereren van de DAO-body: voor elke methode wordt een implementatie gemaakt met behulp van een intern RoomSQLiteQuery-object dat prepared query's uitvoert. Derde — het genereren van de Database_Impl-klasse, die het maken en openen van de database en de initialisatie van alle DAO-objecten implementeert.
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 maakt geen aparte threadpool voor databasebewerkingen. Standaard worden query's uitgevoerd in de aanroepende thread met één beperking: lezen en schrijven blokkeren de thread. Voor asynchroon werk integreert Room met Kotlin-coroutines via suspend-functies, met LiveData via retourwaarden en met Flow via reactieve wrappers. Dit geeft de ontwikkelaar flexibiliteit bij het kiezen van de architectuuroplossing voor een specifieke taak.
Laten we een praktisch voorbeeld bekijken van het maken van een app voor het opslaan van notities met Room. De app bevat één tabel Note met velden id, title, content en timestamp. De gebruiker kan notities toevoegen, bekijken en verwijderen. Voor de demonstratie worden coroutines gebruikt voor asynchrone bewerkingen.
Om Room aan te sluiten op een Android-project, moeten afhankelijkheden worden toegevoegd aan het build.gradle-bestand van de app-module. Room vereist drie componenten: een runtime-bibliotheek, een kapt-annotatieprocessor en optionele ondersteuning voor coroutines. De bibliotheekversie wordt opgegeven in de variabele room_version voor gemakkelijke updates. Sinds Room 2.4.0 wordt KSP ondersteund als alternatief voor kapt met een hogere buildsnelheid.
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"
// Optioneel: testen
testImplementation "androidx.room:room-testing:$room_version"
}
Na het configureren van de afhankelijkheden worden drie bestanden gemaakt: de Note Entity, de NoteDao-interface en de AppDatabase-klasse. De Note Entity bevat velden met @PrimaryKey- en @ColumnInfo-annotaties. DAO biedt methoden voor invoegen, lijst ophalen en verwijderen. Database verbindt Entity en DAO via de @Database-annotatie.
@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)
}
Het bestand AppDatabase wordt gedeclareerd als een abstracte klasse die overerft van RoomDatabase. In de @Database-annotatie worden alle Entity's van de huidige versie en het schemaversienummer vermeld. Voor het verkrijgen van een instantie wordt het singleton-patroon gebruikt via de build-methode van Room.databaseBuilder met de app-context. Het cachen van de database-instantie voorkomt meervoudige aanmaak die tot geheugenlekken kan leiden.
Migraties in Room zijn een mechanisme voor het wijzigen van het databaseschema bij het updaten van de app zonder verlies van bestaande gegevens. Wanneer de gebruiker een nieuwe versie met gewijzigde Entity's installeert, detecteert Room de versieconflict en voert de opgegeven migratiestappen uit. Zonder migratie wordt de database verwijderd en opnieuw aangemaakt, wat leidt tot verlies van alle opgeslagen gebruikersgegevens.
Een migratie wordt beschreven door de klasse Migration, die de begin- en eindversie van de database ontvangt. Binnen de migrate-methode wordt een SQL-query ALTER TABLE of CREATE TABLE uitgevoerd om het schema te wijzigen. Room kan schemawijzigingen niet automatisch detecteren — de ontwikkelaar moet handmatig een migratie schrijven voor elke wijziging van een Entity. Sinds Room-versie 2.4.0 is de experimentele functie autoMigrations beschikbaar voor het automatisch genereren van migraties.
De functie autoMigrations stelt Room in staat om automatisch migraties te genereren op basis van verschillen tussen Entity-versies. Om het te gebruiken, volstaat het om de @AutoMigration-annotatie aan @Database toe te voegen en de schema-export naar JSON op te geven. Room vergelijkt de schema's van aangrenzende versies en genereert de benodigde ALTER-query's. autoMigrations ondersteunt echter alleen achterwaarts compatibele wijzigingen: kolommen toevoegen, indexen maken en typewijzigingen met compatibele conversies.
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()
Bij het toevoegen van complexe wijzigingen, zoals het hernoemen van kolommen of het samenvoegen van tabellen, is handmatige migratie met behulp van tijdelijke tabellen vereist. Typisch scenario: maak een tijdelijke tabel met het oude schema, kopieer gegevens van de oude tabel naar de nieuwe met conversies, verwijder de oude tabel en hernoem de tijdelijke. Room garandeert dat alle migraties in één transactie worden uitgevoerd en dat wijzigingen bij een fout volledig worden teruggedraaid.
Veelgestelde vragen
Room biedt een ORM-abstractie met annotaties en SQL-validatie tijdens het compileren, terwijl SQLiteOpenHelper het handmatig schrijven van alle query's en verbindingsbeheer vereist. Room genereert automatisch code voor CRUD-bewerkingen en integreert met Android-architectuurcomponenten, waaronder LiveData en Flow.
Room ondersteunt alle primitieve Java-typen: Int, Long, Boolean, Float, Double, evenals String, ByteArray en Date. Voor complexe typen zoals List of Enum worden TypeConverters gebruikt — statische conversiemethoden die niet-standaard typen omzetten naar door SQLite ondersteunde formaten.
Ja, Room ondersteunt synchrone aanroepen zonder coroutines, maar ze blokkeren de thread waarin ze worden uitgevoerd. Voor asynchroon werk kunnen LiveData of RxJava worden gebruikt in plaats van coroutines. Google raadt aan om coroutines te gebruiken als de primaire methode voor asynchrone gegevenstoegang in nieuwe projecten.
Als Room een versieconflict van de database detecteert en geen geschikte migratie vindt, treedt standaard een IllegalStateException op met een foutbeschrijving. De ontwikkelaar kan dit gedrag overschrijven met de methode fallbackToDestructiveMigration, die de bestaande database verwijdert en een nieuwe aanmaakt met verlies van alle gegevens.
Room ondersteunt relaties via geneste objecten met de @Embedded-annotatie en via relatieklassen met de @Relation-annotatie. Voor complexe query's met tabelkoppelingen worden aangepaste POJO-klassen gebruikt waarvan de velden worden gevuld uit @Query-resultaten met de JOIN-operator in SQL.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook