Room: какво е, ORM библиотека и работа с SQLite

Автор: IT Sectr Публикувано: 2026-03-12 Време за четене: 10 мин

Room е ORM библиотека от състава на Android Jetpack, която предоставля слой на абстракция над SQLite за работа с локални бази данни на Android. Според официалната документация Android Developers, 2025, Room автоматично генерира реализации на DAO на базата на анотации по време на компилиране, което елиминира около 70% от повтарящия се код в сравнение с директното използване на SQLiteOpenHelper. Библиотеката извършва проверка на SQL заявки на етапа на компилиране, което позволява откриване на синтактични грешки преди стартиране на приложението на устройството.

Основни неща

  • Room — ORM библиотека на Android Jetpack, предоставяща слой на абстракция над SQLite за локално съхранение на данни в Android приложения.
  • Три основни компонента: Entity (описание на таблица), DAO (операции с данни) и Database (точка на вход в базата данни).
  • Проверка на SQL заявки на етапа на компилиране — ключово предимство, позволяващо откриване на грешки преди инсталиране на приложението.
  • Вградена поддръжка за Flow, LiveData и RxJava за реактивно наблюдение на промените в базата данни.
  • Механизмът за миграции позволява актуализиране на схемата на базата данни без загуба на вече запазени потребителски данни.

Какво е Room ORM библиотека?

Room е ORM библиотека от състава на Android Jetpack, създадена от Google за опростяване на работата с локални SQLite бази данни на платформата Android. Тя предоставля анотации за описание на схемата на данни и автоматично генерира реализация на DAO интерфейси на етапа на компилиране. За разлика от директното използване на SQLiteOpenHelper, Room освобождава разработчика от писане на значително количество повтарящ се код за създаване, отваряне и управление на връзката с базата данни.

Библиотеката беше представена на Google I/O 2017 като част от архитектурните компоненти на Android. Оттогава Room се превърна в де факто стандарт за локално съхранение на данни, изпреварвайки по популярност решения като GreenDAO и Realm за Android. Според данни на Google, библиотеката се използва в над 60% от приложенията, публикувани в Google Play, които работят с локални данни на устройството.

Ключова характеристика — проверка на SQL заявки на етапа на компилиране с помощта на процесор на анотации. Ако разработчикът допусне грешка в SQL команда, например посочи несъществуващо име на колона, компилирането ще завърши с грешка преди инсталиране на приложението. Това коренно се различава от подхода на SQLiteOpenHelper, при който такива грешки се откриват едва по време на изпълнение, често в продукционна среда.

TypeConverters за нестандартни типове

SQLite поддържа само пет типа данни: TEXT, INTEGER, REAL, BLOB и NULL. В Java и Kotlin обаче се използват сложни типове: Date, List, Enum и персонализирани обекти. За тяхното съхранение Room предоставля механизма TypeConverters — статични методи, които преобразуват сложен тип в примитивен тип, разбираем за SQLite. Например обект Date се конвертира в Long (timestamp), а List<String> — в JSON низ чрез Gson или Moshi.

kotlin
@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()

За да декларирате конвертор, достатъчно е да добавите анотация @TypeConverter към статичен метод и да посочите класа на конвертора в анотация @TypeConverters на ниво база данни. Room автоматично прилага конвертора при четене и запис на съответния тип във всяка SQL заявка без ръчно извикване на методи за преобразуване.

Архитектура на Room: три ключови компонента

Room се състои от три основни компонента: Entity, DAO и Database. Всеки изпълнява строго определена роля и се анотира със съответната анотация. Заедно те формират пълноценен слой за достъп до данни, който изолира бизнес логиката на приложението от детайлите на имплементация на SQLite.

Entity — таблица на база данни

Entity е клас от данни, който описва структурата на една таблица в базата. Всяко поле на класа съответства на колона от таблицата, а всеки ред в базата — на един екземпляр на класа. Анотацията @Entity указва на Room, че класът е таблица. Полето с анотация @PrimaryKey определя първичния ключ, който може да бъде автоинкрементен или съставен. За връзка между таблици се използва @ForeignKey, осигуряваща целостта на данните на ниво база.

kotlin
@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 — операции с данни

DAO (Data Access Object) е интерфейс или абстрактен клас, който декларира операции за работа с данни: вмъкване, четене, актуализиране и изтриване. Всяка операция се анотира с @Insert, @Query, @Update или @Delete. Room автоматично генерира имплементация на този интерфейс на етапа на компилиране. Особена стойност има анотацията @Query, която приема SQL заявка като низ и проверява нейната коректност на етапа на изграждане.

kotlin
@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 — точка на вход

Database е абстрактен клас, наследяващ RoomDatabase, служещ като точка на вход в базата данни. Той съдържа списък на всички Entity и предоставля абстрактни методи за получаване на DAO. Класът се анотира с @Database, където се посочват версията на схемата и списъкът на обекти. Създаването на екземпляр на базата се извършва чрез Room.databaseBuilder с посочване на контекста на приложението, името на файла и класа Database.

Как Room работи с SQLite под капака

Room не замества SQLite, а работи над него като слой на абстракция. Вътрешната архитектура включва процесор на анотации, генератор на код и пул от връзки. На етапа на компилиране процесорът на анотации анализира класовете Entity, DAO и Database, след което генерира класове за имплементация с наставка _Impl. Всички генерирани класове се поставят в пакета за изграждане и не са пряко видими за разработчика.

Генерирането на код на етапа на компилиране — централният механизъм на Room. За всеки DAO интерфейс се генерира клас с пълна имплементация на всички анотирани методи. SQL заявките от анотация @Query се проверяват за коректност: процесорът съпоставя имената на колони с полетата на Entity и проверява синтаксиса на SQL. При откриване на грешка компилирането се прекъсва с разбираемо съобщение. Това е невъзможно при използване на суров SQLiteOpenHelper, където грешките се проявяват едва по време на изпълнение.

Генериране на код на етапа на компилиране

Процесът на генериране включва три етапа. Първи — валидиране на схемата: процесорът проверява дали всички класове, изброени в @Database, са валидни Entity. Втори — генериране на тялото на DAO: за всеки метод се създава имплементация с помощта на вътрешния обект RoomSQLiteQuery, който изпълнява подготвени заявки. Трети — генериране на класа Database_Impl, който имплементира създаването и отварянето на базата, както и инициализацията на всички DAO обекти.

kotlin
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 не създава отделен пул от нишки за операции с базата. По подразбиране заявките се изпълняват в извикващата нишка с едно ограничение: четенето и писането блокират нишката. За асинхронна работа Room се интегрира с корутини на Kotlin чрез suspend функции, с LiveData чрез върнати стойности и с Flow чрез реактивни обвивки. Това дава на разработчика гъвкавост при избора на архитектурно решение за конкретна задача.

Пример за използване на Room в Android приложение

Нека разгледаме практически пример за създаване на приложение за съхранение на бележки с използване на Room. Приложението съдържа една таблица Note с полета id, title, content и timestamp. Потребителят ще може да добавя, преглежда и изтрива бележки. За демонстрация се използват корутини за асинхронни операции.

Конфигуриране на Gradle зависимости

За да свържете Room с Android проект, трябва да добавите зависимости във файла build.gradle на модула на приложението. Room изисква три компонента: runtime библиотека, процесор на анотации kapt и опционална поддръжка за корутини. Версията на библиотеката се посочва в променливата room_version за удобство при актуализиране. От Room 2.4.0 нататък се поддържа KSP като алтернатива на kapt с по-висока скорост на изграждане.

groovy
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"
    // Опционално: тестване
    testImplementation "androidx.room:room-testing:$room_version"
}

След конфигуриране на зависимостите се създават три файла: Entity Note, интерфейс NoteDao и клас AppDatabase. Entity Note съдържа полета с анотации @PrimaryKey и @ColumnInfo. DAO предоставля методи за вмъкване, получаване на списък и изтриване. Database свързва Entity и DAO чрез анотация @Database.

kotlin
@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)
}

Файлът AppDatabase се декларира като абстрактен клас, наследяващ RoomDatabase. В анотация @Database се посочват всички Entity от текущата версия и номерът на версията на схемата. За получаване на екземпляр се използва шаблонът сингълтън чрез метода build на Room.databaseBuilder с контекст на приложението. Кеширането на екземпляра на базата предотвратява многократно създаване, което може да доведе до изтичане на памет.

Миграции на база данни в Room

Миграциите в Room са механизъм за промяна на схемата на базата данни при актуализиране на приложението без загуба на съществуващи данни. Когато потребителят инсталира нова версия с променени Entity, Room открива несъответствие на версиите и изпълнява посочените миграционни стъпки. Без миграция базата данни ще бъде изтрита и създадена наново, което ще доведе до загуба на всички запазени потребителски данни.

Миграцията се описва от класа Migration, който приема начална и крайна версия на базата. Вътре в метода migrate се изпълнява SQL заявка ALTER TABLE или CREATE TABLE за промяна на схемата. Room не може автоматично да открива промени в схемата — разработчикът трябва ръчно да напише миграция за всяка промяна на Entity. От версия Room 2.4.0 е достъпна експерименталната функция autoMigrations за автоматично генериране на миграции.

Автоматични миграции с autoMigrations

Функцията autoMigrations позволява на Room автоматично да генерира миграции на базата на разлики между версиите на Entity. За да я използвате, достатъчно е да добавите анотация @AutoMigration в @Database и да посочите експорт на схемата в JSON. Room сравнява схемите на съседни версии и генерира необходимите ALTER заявки. Въпреки това autoMigrations поддържа само обратно съвместими промени: добавяне на колони, създаване на индекси и промяна на типове със съвместими преобразувания.

kotlin
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()

При добавяне на сложни промени, като преименуване на колони или обединяване на таблици, се изисква ръчна миграция с използване на временни таблици. Типичен сценарий: създайте временна таблица със старата схема, копирайте данните от старата таблица в новата с преобразувания, изтрийте старата таблица и преименувайте временната. Room гарантира, че всички миграции се изпълняват в една транзакция и при възникване на грешка промените се отменят напълно.

Често задавани въпроси

С какво Room се различава от SQLiteOpenHelper?

Room предоставля ORM абстракция с анотации и проверка на SQL на етапа на компилиране, докато SQLiteOpenHelper изисква ръчно писане на всички заявки и управление на връзката. Room автоматично генерира код за CRUD операции и се интегрира с архитектурните компоненти на Android, включително LiveData и Flow.

Какви типове данни поддържа Room?

Room поддържа всички примитивни типове на Java: Int, Long, Boolean, Float, Double, както и String, ByteArray и Date. За сложни типове, като List или Enum, се използват TypeConverters — статични методи за преобразуване, които конвертират нестандартни типове във формати, поддържани от SQLite.

Може ли да се използва Room без корутини?

Да, Room поддържа синхронни извиквания без корутини, но те блокират нишката, в която се изпълняват. За асинхронна работа могат да се използват LiveData или RxJava вместо корутини. Google препоръчва използването на корутини като основен начин за асинхронен достъп до данни в нови проекти.

Какво се случва при липса на миграция?

Ако Room открие несъответствие на версията на базата данни и не намери подходяща миграция, по подразбиране възниква IllegalStateException с описание на грешката. Разработчикът може да замени това поведение с метода fallbackToDestructiveMigration, който ще изтрие съществуващата база и ще създаде нова със загуба на всички данни.

Как Room обработва връзките между таблици?

Room поддържа връзки чрез вложени обекти с анотация @Embedded и чрез класове за връзка с анотация @Relation. За сложни заявки с обединяване на таблици се използват персонализирани POJO класове, чиито полета се попълват от резултатите на @Query с оператор JOIN в SQL.

Резюме

  • Room — ORM библиотека на Android Jetpack, създаваща слой на абстракция над SQLite за удобно съхранение на данни на устройството.
  • Архитектурата се основава на три компонента: Entity (схема на таблица), DAO (операции) и Database (точка на вход).
  • Проверка на SQL заявки на етапа на компилиране — основното предимство, елиминиращо грешки по време на изпълнение в заявките.
  • Вградената поддръжка за Flow, LiveData и RxJava позволява изграждане на реактивни архитектури с автоматично актуализиране на потребителския интерфейс при промяна на данни.
  • Миграциите в Room осигуряват безпроблемно актуализиране на схемата на базата данни без загуба на запазена потребителска информация.
  • Библиотеката се интегрира с корутини на Kotlin чрез suspend функции, опростявайки асинхронната работа с данни.
  • За нови проекти Room е официално препоръчваното от Google решение за локално съхранение на данни на Android.

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също