Room је ORM библиотека из састава Android Jetpack која пружа слој апстракције изнад SQLite за рад са локалним базама података на Android-у. Према званичној документацији Android Developers, 2025, Room аутоматски генерише имплементације DAO на основу анотација током компајлирања, што елиминише око 70% шаблонског кода у поређењу са директним коришћењем SQLiteOpenHelper. Библиотека врши проверу SQL упита у фази компајлирања, што омогућава откривање синтаксних грешака пре покретања апликације на уређају.
Главно
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, где се такве грешке откривају тек током извршавања, често у продукцији.
SQLite подржава само пет типова података: TEXT, INTEGER, REAL, BLOB и NULL. Међутим, у Java и Kotlin-у се користе сложени типови: Date, List, Enum и прилагођени објекти. За њихово чување Room пружа механизам TypeConverters — статичких метода које претварају сложени тип у примитивни, разумљив SQLite-у. На пример, објекат Date се конвертује у Long (timestamp), а List<String> — у JSON низ преко Gson или 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()
За декларисање конвертора довољно је додати анотацију @TypeConverter статичкој методи и навести класу конвертора у анотацији @TypeConverters на нивоу базе података. Room аутоматски примењује конвертор при читању и писању одговарајућег типа у сваком SQL упиту без ручног позивања метода конверзије.
Room се састоји од три главне компоненте: Entity, DAO и Database. Свака обавља строго одређену улогу и анотирана је одговарајућом анотацијом. Заједно оне чине потпуни слој приступа подацима који изолује пословну логику апликације од детаља имплементације SQLite-а.
Entity је класа података која описује структуру једне табеле у бази. Свако поље класе одговара колони табеле, а сваки ред у бази — једном примерку класе. Анотација @Entity указује Room-у да је класа табела. Поље са анотацијом @PrimaryKey одређује примарни кључ, који може бити аутоинкрементни или сложени. За везу између табела користи се @ForeignKey, која обезбеђује интегритет података на нивоу базе.
@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) је интерфејс или апстрактна класа која декларише операције за рад са подацима: уметање, читање, ажурирање и брисање. Свака операција је анотирана са @Insert, @Query, @Update или @Delete. Room аутоматски генерише имплементацију овог интерфејса у фази компајлирања. Посебну вредност има анотација @Query, која прима SQL упит у облику низа и проверава његову исправност у фази изградње.
@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 је апстрактна класа која наслеђује RoomDatabase, служећи као тачка улаза у базу података. Садржи листу свих Entity и пружа апстрактне методе за добијање DAO. Класа је анотирана са @Database, где се наводе верзија шеме и листа ентитета. Креирање примерка базе врши се преко Room.databaseBuilder са навођењем контекста апликације, назива датотеке и класе Database.
Room не замењује SQLite, већ ради изнад њега као слој апстракције. Унутрашња архитектура укључује процесор анотација, генератор кода и пул веза. У фази компајлирања, процесор анотација анализира класе Entity, DAO и Database, након чега генерише класе имплементације са суфиксом _Impl. Све генерисане класе се смештају у пакет изградње и нису директно видљиве програмеру.
Генерисање кода у фази компајлирања — централни механизам Room-а. За сваки DAO интерфејс генерише се класа са потпуном имплементацијом свих анотираних метода. SQL упити из анотације @Query проверавају се на исправност: процесор усклађује имена колона са пољима Entity и проверава синтаксу SQL. При откривању грешке, компајлирање се прекида са разумљивом поруком. Ово је немогуће при коришћењу сировог SQLiteOpenHelper-а, где се грешке појављују тек у runtime-у.
Процес генерисања укључује три фазе. Прва — валидација шеме: процесор проверава да ли су све класе наведене у @Database исправни Entity. Друга — генерисање тела DAO: за сваку методу креира се имплементација коришћењем унутрашњег објекта RoomSQLiteQuery, који извршава припремљене упите. Трећа — генерисање класе Database_Impl, која имплементира креирање и отварање базе, као и иницијализацију свих 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 не креира посебан пул нити за операције са базом. Подразумевано, упити се извршавају у нити која их позива са једним ограничењем: читање и писање блокирају нит. За асинхрони рад, Room се интегрише са корутинама Kotlin-а преко suspend функција, са LiveData-ом преко повратних вредности и са Flow преко реактивних омотача. Ово пружа програмеру флексибилност избора архитектонског решења за конкретан задатак.
Размотримо практичан пример креирања апликације за чување бележака употребом Room-а. Апликација садржи једну табелу Note са пољима id, title, content и timestamp. Корисник ће моћи да додаје, прегледа и брише белешке. За демонстрацију се користе корутине за асинхроне операције.
За повезивање Room-а са Android пројектом потребно је додати зависности у датотеку build.gradle модула апликације. Room захтева три компоненте: runtime библиотеку, процесор анотација kapt и опционалну подршку за корутине. Верзија библиотеке се наводи у променљивој room_version ради лакшег ажурирања. Од Room 2.4.0, KSP је подржан као алтернатива kapt-у са већом брзином компајлирања.
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.
@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-у су механизам за промену шеме базе података при ажурирању апликације без губитка постојећих података. Када корисник инсталира нову верзију са измењеним Entity, Room открива неусклађеност верзија и извршава наведене миграционе кораке. Без миграције, база података ће бити обрисана и поново креирана, што ће довести до губитка свих сачуваних корисничких података.
Миграција се описује класом Migration, која прима почетну и крајњу верзију базе. Унутар методе migrate извршава се SQL упит ALTER TABLE или CREATE TABLE за промену шеме. Room не уме аутоматски да одреди промене шеме — програмер мора ручно да напише миграцију за сваку промену Entity-ја. Од верзије Room 2.4.0, доступна је експериментална функција autoMigrations за аутоматско генерисање миграција.
Функција autoMigrations омогућава Room-у да аутоматски генерише миграције на основу разлика између верзија Entity-ја. За њено коришћење довољно је додати анотацију @AutoMigration у @Database и навести извоз шеме у JSON. Room упоређује шеме суседних верзија и генерише потребне ALTER упите. Међутим, autoMigrations подржава само уназад компатибилне промене: додавање колона, креирање индекса и промену типова са компатибилним конверзијама.
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 пружа ORM апстракцију са анотацијама и провером SQL упита у фази компајлирања, док SQLiteOpenHelper захтева ручно писање свих упита и управљање везом. Room аутоматски генерише код за CRUD операције и интегрише се са архитектонским компонентама Android-а, укључујући LiveData и Flow.
Room подржава све примитивне типове Java: Int, Long, Boolean, Float, Double, као и String, ByteArray и Date. За сложене типове, попут List или Enum, користе се TypeConverters — статичке методе конверзије које претварају нестандардне типове у формате подржане од стране SQLite-а.
Да, Room подржава синхроне позиве без корутина, али они блокирају нит у којој се извршавају. За асинхрони рад могу се користити LiveData или RxJava уместо корутина. Google препоручује коришћење корутина као главног начина асинхроног приступа подацима у новим пројектима.
Ако Room открије неусклађеност верзије базе података и не пронађе одговарајућу миграцију, подразумевано настаје IllegalStateException са описом грешке. Програмер може да премости ово понашање методом fallbackToDestructiveMigration, која ће обрисати постојећу базу и створити нову уз губитак свих података.
Room подржава везе преко угнежђених објеката са анотацијом @Embedded и преко класа односа са анотацијом @Relation. За сложене упите са спајањем табела користе се прилагођене POJO класе чија се поља попуњавају из резултата @Query са SQL оператором JOIN.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође