Room este o bibliotecă ORM din cadrul Android Jetpack care oferă un strat de abstractizare peste SQLite pentru lucrul cu baze de date locale pe Android. Conform documentației oficiale Android Developers, 2025, Room generează automat implementări DAO pe baza adnotărilor în timpul compilării, eliminând aproximativ 70% din codul boilerplate față de utilizarea directă a SQLiteOpenHelper. Biblioteca efectuează verificarea interogărilor SQL în faza de compilare, permițând depistarea erorilor sintactice înainte de lansarea aplicației pe dispozitiv.
Principalele puncte
Room este o bibliotecă ORM din cadrul Android Jetpack, creată de Google pentru simplificarea lucrului cu bazele de date locale SQLite pe platforma Android. Oferă adnotări pentru descrierea schemei datelor și generează automat implementarea interfețelor DAO în faza de compilare. Spre deosebire de utilizarea directă a SQLiteOpenHelper, Room eliberează dezvoltatorul de scrierea unei cantități semnificative de cod boilerplate pentru crearea, deschiderea și gestionarea conexiunii cu baza de date.
Biblioteca a fost prezentată la Google I/O 2017 ca parte a componentelor arhitecturale Android. De atunci Room a devenit standardul de facto pentru stocarea locală a datelor, depășind ca popularitate soluții precum GreenDAO și Realm pentru Android. Potrivit Google, biblioteca este utilizată în peste 60% din aplicațiile publicate în Google Play care lucrează cu date locale pe dispozitiv.
Caracteristica cheie — verificarea interogărilor SQL în faza de compilare cu ajutorul procesorului de adnotări. Dacă dezvoltatorul greșește într-o comandă SQL, de exemplu, indică un nume de coloană inexistent, compilarea se va încheia cu eroare înainte de instalarea aplicației. Aceasta diferă radical de abordarea SQLiteOpenHelper, unde astfel de erori sunt descoperite doar în timpul execuției, adesea în producție.
SQLite suportă doar cinci tipuri de date: TEXT, INTEGER, REAL, BLOB și NULL. Însă în Java și Kotlin se folosesc tipuri complexe: Date, List, Enum și obiecte personalizate. Pentru salvarea lor, Room oferă mecanismul TypeConverters — metode statice care convertesc un tip complex într-un tip primitiv, inteligibil pentru SQLite. De exemplu, obiectul Date este convertit în Long (timestamp), iar List<String> — în șir JSON prin Gson sau 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()
Pentru a declara convertizorul, este suficient să adăugați adnotarea @TypeConverter la o metodă statică și să indicați clasa convertizorului în adnotarea @TypeConverters la nivelul bazei de date. Room aplică automat convertizorul la citirea și scrierea tipului corespunzător în fiecare interogare SQL, fără apelarea manuală a metodelor de conversie.
Room este format din trei componente principale: Entity, DAO și Database. Fiecare îndeplinește un rol strict definit și este adnotat cu adnotarea corespunzătoare. Împreună, ele formează un strat complet de acces la date care izolează logica de afaceri a aplicației de detaliile de implementare SQLite.
Entity este o clasă de date care descrie structura unui tabel în bază. Fiecare câmp al clasei corespunde unei coloane a tabelului, iar fiecare rând în bază — unei instanțe a clasei. Adnotarea @Entity indică Room că clasa este un tabel. Câmpul cu adnotarea @PrimaryKey definește cheia primară, care poate fi auto-incrementată sau compusă. Pentru legătura între tabele se folosește @ForeignKey, asigurând integritatea datelor la nivelul bazei.
@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) este o interfață sau clasă abstractă care declară operațiile pentru lucrul cu datele: inserare, citire, actualizare și ștergere. Fiecare operație este adnotată cu @Insert, @Query, @Update sau @Delete. Room generează automat implementarea acestei interfețe în faza de compilare. O valoare deosebită o are adnotarea @Query, care primește o interogare SQL sub formă de șir și verifică corectitudinea acesteia în faza de construire.
@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 este o clasă abstractă care moștenește RoomDatabase, servind ca punct de intrare în baza de date. Conține lista tuturor Entity și oferă metode abstracte pentru obținerea DAO. Clasa este adnotată cu @Database, unde se specifică versiunea schemei și lista entităților. Crearea instanței bazei se realizează prin Room.databaseBuilder cu specificarea contextului aplicației, numelui fișierului și clasei Database.
Room nu înlocuiește SQLite, ci funcționează deasupra lui ca un strat de abstractizare. Arhitectura internă include procesorul de adnotări, generatorul de cod și un pool de conexiuni. În faza de compilare, procesorul de adnotări analizează clasele Entity, DAO și Database, după care generează clase de implementare cu sufixul _Impl. Toate clasele generate sunt plasate în pachetul de compilare și nu sunt vizibile direct dezvoltatorului.
Generarea codului în faza de compilare — mecanismul central al Room. Pentru fiecare interfață DAO se generează o clasă cu implementarea completă a tuturor metodelor adnotate. Interogările SQL din adnotarea @Query sunt verificate pentru corectitudine: procesorul potrivește numele coloanelor cu câmpurile Entity și verifică sintaxa SQL. La detectarea unei erori, compilarea este întreruptă cu un mesaj clar. Acest lucru este imposibil la utilizarea SQLiteOpenHelper direct, unde erorile apar doar în runtime.
Procesul de generare include trei etape. Prima — validarea schemei: procesorul verifică dacă toate clasele enumerate în @Database sunt Entity corecte. A doua — generarea corpului DAO: pentru fiecare metodă se creează o implementare folosind obiectul intern RoomSQLiteQuery, care execută interogări pregătite. A treia — generarea clasei Database_Impl, care implementează crearea și deschiderea bazei, precum și inițializarea tuturor obiectelor 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 nu creează un pool separat de fire de execuție pentru operațiile cu baza. În mod implicit, interogările sunt executate în firul de apelare cu o singură restricție: citirea și scrierea blochează firul. Pentru lucrul asincron, Room se integrează cu corutinele Kotlin prin funcții suspend, cu LiveData prin valori returnate și cu Flow prin învelitori reactive. Aceasta oferă dezvoltatorului flexibilitate în alegerea soluției arhitecturale pentru o sarcină concretă.
Să analizăm un exemplu practic de creare a unei aplicații pentru stocarea notițelor folosind Room. Aplicația conține un tabel Note cu câmpurile id, title, content și timestamp. Utilizatorul va putea adăuga, vizualiza și șterge notițe. Pentru demonstrare se folosesc corutine pentru operații asincrone.
Pentru a conecta Room la un proiect Android, trebuie adăugate dependențe în fișierul build.gradle al modulului aplicației. Room necesită trei componente: biblioteca runtime, procesorul de adnotări kapt și suportul opțional pentru corutine. Versiunea bibliotecii este specificată în variabila room_version pentru comoditatea actualizării. Începând cu Room 2.4.0, KSP este suportat ca alternativă la kapt cu o viteză de compilare mai mare.
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"
// Opțional: testare
testImplementation "androidx.room:room-testing:$room_version"
}
După configurarea dependențelor, se creează trei fișiere: Entity Note, interfața NoteDao și clasa AppDatabase. Entity Note conține câmpuri cu adnotările @PrimaryKey și @ColumnInfo. DAO oferă metode pentru inserare, obținerea listei și ștergere. Database conectează Entity și DAO prin adnotarea @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)
}
Fișierul AppDatabase este declarat ca o clasă abstractă care moștenește RoomDatabase. În adnotarea @Database sunt enumerate toate Entity ale versiunii curente și numărul versiunii schemei. Pentru obținerea instanței se folosește modelul singleton prin metoda build a Room.databaseBuilder cu contextul aplicației. Stocarea în cache a instanței bazei previne creările multiple care ar putea duce la scurgeri de memorie.
Migrările în Room sunt un mecanism pentru modificarea schemei bazei de date la actualizarea aplicației fără pierderea datelor existente. Când utilizatorul instalează o nouă versiune cu Entity modificate, Room detectează nepotrivirea versiunilor și execută pașii de migrare specificați. Fără migrare, baza de date va fi ștearsă și recreată, ceea ce va duce la pierderea tuturor datelor salvate de utilizator.
Migrarea este descrisă de clasa Migration, care primește versiunea inițială și finală a bazei. În interiorul metodei migrate se execută o interogare SQL ALTER TABLE sau CREATE TABLE pentru modificarea schemei. Room nu poate determina automat modificările schemei — dezvoltatorul trebuie să scrie manual migrarea pentru fiecare modificare a Entity. Începând cu versiunea Room 2.4.0, este disponibilă funcția experimentală autoMigrations pentru generarea automată a migrărilor.
Funcția autoMigrations permite Room să genereze automat migrări pe baza diferențelor dintre versiunile Entity. Pentru utilizare, este suficient să adăugați adnotarea @AutoMigration în @Database și să specificați exportul schemei în JSON. Room compară schemele versiunilor vecine și generează interogările ALTER necesare. Totuși, autoMigrations suportă doar modificări compatibile invers: adăugarea de coloane, crearea de indici și modificarea tipurilor cu conversii compatibile.
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()
La adăugarea modificărilor complexe, cum ar fi redenumirea coloanelor sau îmbinarea tabelelor, este necesară migrarea manuală cu ajutorul tabelelor intermediare. Scenariu tipic: creați un tabel temporar cu schema veche, copiați datele din tabelul vechi în cel nou cu conversii, ștergeți tabelul vechi și redenumiți tabelul temporar. Room garantează că toate migrările sunt executate într-o singură tranzacție, iar la apariția unei erori, modificările sunt complet anulate.
Întrebări frecvente
Room oferă o abstractizare ORM cu adnotări și verificare SQL în faza de compilare, în timp ce SQLiteOpenHelper necesită scrierea manuală a tuturor interogărilor și gestionarea conexiunii. Room generează automat codul pentru operații CRUD și se integrează cu componentele arhitecturale Android, inclusiv LiveData și Flow.
Room suportă toate tipurile primitive Java: Int, Long, Boolean, Float, Double, precum și String, ByteArray și Date. Pentru tipurile complexe, cum ar fi List sau Enum, se folosesc TypeConverters — metode statice de conversie care transformă tipurile nestandard în formate suportate de SQLite.
Da, Room suportă apeluri sincrone fără corutine, dar acestea blochează firul de execuție în care rulează. Pentru lucrul asincron se poate folosi LiveData sau RxJava în locul corutinelor. Google recomandă utilizarea corutinelor ca metodă principală de acces asincron la date în proiecte noi.
Dacă Room detectează o nepotrivire a versiunii bazei de date și nu găsește o migrare potrivită, în mod implicit apare IllegalStateException cu descrierea erorii. Dezvoltatorul poate suprascrie acest comportament prin metoda fallbackToDestructiveMigration, care va șterge baza existentă și va crea una nouă cu pierderea tuturor datelor.
Room suportă relații prin obiecte înglobate cu adnotarea @Embedded și prin clase de relații cu adnotarea @Relation. Pentru interogări complexe cu îmbinarea tabelelor se folosesc clase POJO personalizate, ale căror câmpuri sunt populate din rezultatele @Query cu operatorul JOIN în SQL.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și