Android 2026'da Room Database: Migration, İlişkiler ve Coroutines
Android 2026'da Room Database hakkında kapsamlı rehber - şema migrasyonları, entity ilişkileri, Kotlin Coroutines entegrasyonu ve en iyi uygulama pratikleri.

Room Database, Android uygulamalarında yerel veri depolama için temel araç olmaya devam etmektedir. 2026 yılında bu kütüphane, geliştirilmiş migrasyon desteği, gelişmiş entity ilişkileri ve Kotlin Coroutines ile tam entegrasyon gibi daha fazla özellik sunmaktadır. Bu makale, modern Android projelerinde Room kullanımına kapsamlı bir yaklaşım sunmaktadır.
Room Database, Android Jetpack'in bir parçasıdır ve SQLite üzerinde bir soyutlama katmanı sağlar. Boilerplate kod yazma ihtiyacını ortadan kaldırır ve derleme zamanında SQL sorgu doğrulaması sunar.
Android 2026 Projesinde Room Yapılandırması
Room ile çalışmaya başlamak için projeye gerekli bağımlılıkların eklenmesi gerekmektedir. 2026 yılında, daha iyi derleme performansı için KSP (Kotlin Symbol Processing) ile birlikte kütüphanenin en son sürümlerinin kullanılması önerilmektedir.
plugins {
id("com.google.devtools.ksp") version "2.1.0-1.0.29"
}
dependencies {
val roomVersion = "2.7.0"
implementation("androidx.room:room-runtime:$roomVersion")
implementation("androidx.room:room-ktx:$roomVersion")
ksp("androidx.room:room-compiler:$roomVersion")
}Bağımlılıklar yapılandırıldıktan sonra, veritabanındaki tabloları temsil eden entity'lerin tanımlanmasına geçilebilir.
Entity Tanımlama ve Temel İşlemler
Room'da entity'ler, @Entity anotasyonu ile işaretlenmiş veri sınıflarıdır. Her entity, SQLite veritabanındaki bir tabloya karşılık gelir.
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
@ColumnInfo(name = "first_name")
val firstName: String,
@ColumnInfo(name = "last_name")
val lastName: String,
@ColumnInfo(name = "email")
val email: String,
@ColumnInfo(name = "created_at")
val createdAt: Long = System.currentTimeMillis()
)DAO (Data Access Object), verilere erişim yöntemlerini tanımlar. Room, bu arayüzlerin implementasyonunu otomatik olarak oluşturur.
@Dao
interface UserDao {
@Query("SELECT * FROM users ORDER BY created_at DESC")
suspend fun getAllUsers(): List<User>
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: Long): User?
@Query("SELECT * FROM users WHERE email = :email LIMIT 1")
suspend fun getUserByEmail(email: String): User?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User): Long
@Update
suspend fun updateUser(user: User)
@Delete
suspend fun deleteUser(user: User)
@Query("DELETE FROM users")
suspend fun deleteAllUsers()
}Veritabanı Şema Migrasyonları
Migrasyonlar, veritabanı şemasının evrimini yönetmenin kritik bir unsurudur. Room, veritabanı sürümündeki her değişiklikte açık migrasyon tanımı gerektirir.
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE users ADD COLUMN phone_number TEXT")
}
}
val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("""
CREATE TABLE IF NOT EXISTS user_preferences (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
user_id INTEGER NOT NULL,
theme TEXT NOT NULL DEFAULT 'system',
notifications_enabled INTEGER NOT NULL DEFAULT 1,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
""".trimIndent())
db.execSQL("CREATE INDEX index_user_preferences_user_id ON user_preferences(user_id)")
}
}Sütun türü değişikliği veya veri yeniden düzenleme gibi karmaşık migrasyonlarda, geçici tablo stratejisi uygulanmalıdır.
val MIGRATION_3_4 = object : Migration(3, 4) {
override fun migrate(db: SupportSQLiteDatabase) {
// Düzeltilmiş şema ile yeni tablo oluşturma
db.execSQL("""
CREATE TABLE users_new (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email TEXT NOT NULL,
phone_number TEXT,
created_at INTEGER NOT NULL,
is_verified INTEGER NOT NULL DEFAULT 0
)
""".trimIndent())
// Eski tablodan verilerin kopyalanması
db.execSQL("""
INSERT INTO users_new (id, first_name, last_name, email, phone_number, created_at)
SELECT id, first_name, last_name, email, phone_number, created_at FROM users
""".trimIndent())
// Eski tablonun silinmesi
db.execSQL("DROP TABLE users")
// Yeni tablonun yeniden adlandırılması
db.execSQL("ALTER TABLE users_new RENAME TO users")
// İndekslerin yeniden oluşturulması
db.execSQL("CREATE UNIQUE INDEX index_users_email ON users(email)")
}
}Room 2.7'de Otomatik Migrasyonlar
Room 2.7, basit şema değişiklikleri için basitleştirilmiş otomatik migrasyonlar sunmaktadır. Sistem, yeni sütunların veya tabloların eklenmesini otomatik olarak algılar.
@Database(
version = 5,
entities = [User::class, UserPreferences::class, Article::class],
autoMigrations = [
AutoMigration(from = 4, to = 5)
],
exportSchema = true
)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun articleDao(): ArticleDao
}Daha karmaşık otomatik migrasyonlar için migrasyon spesifikasyonu tanımlanabilir.
@Database(
version = 6,
entities = [User::class, UserPreferences::class, Article::class],
autoMigrations = [
AutoMigration(from = 5, to = 6, spec = Migration5To6::class)
]
)
abstract class AppDatabase : RoomDatabase() {
@RenameColumn(tableName = "users", fromColumnName = "phone_number", toColumnName = "mobile_phone")
@DeleteColumn(tableName = "users", columnName = "legacy_field")
class Migration5To6 : AutoMigrationSpec
}Entity'ler Arasındaki İlişkiler
Room, entity'ler arasında çeşitli ilişki türlerini destekler: bire-bir, bire-çok ve çoka-çok.
Bire-Çok İlişkisi
@Entity(tableName = "articles")
data class Article(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val authorId: Long,
val title: String,
val content: String,
val publishedAt: Long = System.currentTimeMillis()
)
data class UserWithArticles(
@Embedded
val user: User,
@Relation(
parentColumn = "id",
entityColumn = "authorId"
)
val articles: List<Article>
)
@Dao
interface UserDao {
@Transaction
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserWithArticles(userId: Long): UserWithArticles?
@Transaction
@Query("SELECT * FROM users")
fun getAllUsersWithArticles(): Flow<List<UserWithArticles>>
}Çoka-Çok İlişkisi
@Entity(tableName = "tags")
data class Tag(
@PrimaryKey(autoGenerate = true)
val tagId: Long = 0,
val name: String
)
@Entity(
tableName = "article_tag_cross_ref",
primaryKeys = ["articleId", "tagId"],
foreignKeys = [
ForeignKey(
entity = Article::class,
parentColumns = ["id"],
childColumns = ["articleId"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = Tag::class,
parentColumns = ["tagId"],
childColumns = ["tagId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class ArticleTagCrossRef(
val articleId: Long,
val tagId: Long
)
data class ArticleWithTags(
@Embedded
val article: Article,
@Relation(
parentColumn = "id",
entityColumn = "tagId",
associateBy = Junction(ArticleTagCrossRef::class)
)
val tags: List<Tag>
)Kotlin Coroutines ve Flow ile Entegrasyon
Room, Kotlin Coroutines ile yerel entegrasyon sunarak veritabanındaki değişikliklerin reaktif olarak gözlemlenmesini sağlar.
@Dao
interface ArticleDao {
// Tek seferlik veri alma
@Query("SELECT * FROM articles ORDER BY publishedAt DESC")
suspend fun getAllArticles(): List<Article>
// Reaktif Flow - değişikliklerde otomatik emit
@Query("SELECT * FROM articles ORDER BY publishedAt DESC")
fun observeAllArticles(): Flow<List<Article>>
// Parametreli Flow
@Query("SELECT * FROM articles WHERE authorId = :authorId ORDER BY publishedAt DESC")
fun observeArticlesByAuthor(authorId: Long): Flow<List<Article>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertArticle(article: Article): Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertArticles(articles: List<Article>)
@Query("DELETE FROM articles WHERE id = :articleId")
suspend fun deleteArticleById(articleId: Long)
}ViewModel'da Flow'dan gelen veriler, UI durumunu daha kolay yönetmek için StateFlow'a dönüştürülebilir.
class ArticleViewModel(
private val articleDao: ArticleDao
) : ViewModel() {
val articles: StateFlow<List<Article>> = articleDao
.observeAllArticles()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
fun addArticle(title: String, content: String, authorId: Long) {
viewModelScope.launch {
_isLoading.value = true
try {
val article = Article(
title = title,
content = content,
authorId = authorId
)
articleDao.insertArticle(article)
} finally {
_isLoading.value = false
}
}
}
}Transaction ve Toplu İşlemler
Room, atomiklik gerektiren işlemler için transaction desteği sunar. @Transaction anotasyonu, tüm işlemlerin tek bir transaction içinde yürütülmesini sağlar.
@Dao
interface ArticleDao {
@Transaction
suspend fun replaceAllArticles(articles: List<Article>) {
deleteAllArticles()
insertArticles(articles)
}
@Query("DELETE FROM articles")
suspend fun deleteAllArticles()
@Insert
suspend fun insertArticles(articles: List<Article>)
}Daha karmaşık senaryolar için veritabanı nesnesinden withTransaction kullanılabilir.
class ArticleRepository(
private val database: AppDatabase,
private val articleDao: ArticleDao,
private val tagDao: TagDao
) {
suspend fun createArticleWithTags(
article: Article,
tagNames: List<String>
): Long = database.withTransaction {
val articleId = articleDao.insertArticle(article)
tagNames.forEach { tagName ->
val existingTag = tagDao.getTagByName(tagName)
val tagId = existingTag?.tagId ?: tagDao.insertTag(Tag(name = tagName))
tagDao.insertArticleTagCrossRef(
ArticleTagCrossRef(articleId = articleId, tagId = tagId)
)
}
articleId
}
}Room Database Test Etme
Room, bellek içi veritabanı kullanılarak test desteği sunmaktadır.
@RunWith(AndroidJUnit4::class)
class UserDaoTest {
private lateinit var database: AppDatabase
private lateinit var userDao: UserDao
@Before
fun setup() {
val context = ApplicationProvider.getApplicationContext<Context>()
database = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
.allowMainThreadQueries()
.build()
userDao = database.userDao()
}
@After
fun teardown() {
database.close()
}
@Test
fun insertAndRetrieveUser() = runTest {
val user = User(
firstName = "Ahmet",
lastName = "Yilmaz",
email = "ahmet@example.com"
)
val userId = userDao.insertUser(user)
val retrievedUser = userDao.getUserById(userId)
assertThat(retrievedUser).isNotNull()
assertThat(retrievedUser?.firstName).isEqualTo("Ahmet")
assertThat(retrievedUser?.email).isEqualTo("ahmet@example.com")
}
@Test
fun testMigration() = runTest {
val helper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java
)
// Sürüm 1'de veritabanı oluşturma
helper.createDatabase(TEST_DB, 1).apply {
execSQL("INSERT INTO users (first_name, last_name, email, created_at) VALUES ('Test', 'User', 'test@example.com', 0)")
close()
}
// Sürüm 2'ye migrasyon
helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2)
}
companion object {
private const val TEST_DB = "test-database"
}
}Android mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
Room Hakkında Sık Sorulan Mülakat Soruları
Android Developer pozisyonları için yapılan iş görüşmelerinde Room Database hakkında sıkça sorular sorulmaktadır:
Room ile SQLite arasındaki fark nedir? Room, SQLite üzerinde bir soyutlama katmanıdır. Derleme zamanında SQL sorgu doğrulaması, otomatik DAO kod oluşturma ve Kotlin Coroutines ile LiveData/Flow yerel entegrasyonu sunar.
Room thread yönetimini nasıl ele alır?
Room varsayılan olarak veritabanı işlemlerinin ana thread dışında yapılmasını gerektirir. Coroutines entegrasyonu sayesinde, suspend olarak işaretlenen DAO metodları otomatik olarak uygun bağlamda çalışır.
Otomatik migrasyon mu yoksa manuel migrasyon mu kullanılmalı? Otomatik migrasyonlar, sütun veya tablo ekleme gibi basit değişiklikler için uygundur. Manuel migrasyonlar, veri türü değişikliği, tablo yeniden düzenleme veya mevcut verilerin dönüştürülmesi için gereklidir.
Sonuç
2026 yılında Room Database, Android uygulamalarında yerel veri depolama için olgun ve kararlı bir çözüm sunmaktadır. Temel konular arasında şema migrasyonlarının doğru yönetimi, entity'ler arasındaki ilişkilerin etkin modellenmesi ve verilere reaktif erişim için Kotlin Coroutines kullanımı yer almaktadır. Bu konuların bilinmesi, yerel veritabanı gerektiren uygulamalar üzerinde çalışan her Android geliştiricisi için zorunludur.
Etiketler
Paylaş
İlgili makaleler

Kotlin Flow, StateFlow ve SharedFlow: 2026'da Android Mülakat Soruları
Android mülakatçılarının 2026'da sorduğu Kotlin Flow, StateFlow ve SharedFlow soruları; net yanıtlar, bir karşılaştırma tablosu ve üretime hazır kod ile birlikte.

Kotlin Coroutines Rehberi: Android için 2026 Kılavuzu
Android geliştirmede Kotlin coroutines konusunda uzmanlaşın: suspend fonksiyonlar, scope yapıları, dispatcher'lar ve ileri düzey kalıplar.

Android için Kotlin 2.3: İsim Tabanlı Yapı Bozma, KMP ve 2026 Mülakat Soruları
İsim tabanlı yapı bozma, Kotlin Multiplatform, context parametreleri, coroutines ve Flow konularını kapsayan Kotlin 2.3 mülakat soruları. 2026'da gerçek dünya kod örnekleriyle Android geliştirici mülakatlarına hazırlanın.