Room یک کتابخانه ORM از مجموعه Android Jetpack است که لایه انتزاعی بر روی SQLite برای کار با پایگاههای داده محلی در Android فراهم میکند. طبق مستندات رسمی Android Developers, 2025، Room به طور خودکار پیادهسازی DAO را بر اساس annotationها در زمان کامپایل تولید میکند که حدود 70٪ از کدهای تکراری را در مقایسه با استفاده مستقیم از SQLiteOpenHelper حذف میکند. این کتابخانه بررسی پرسوجوهای SQL را در مرحله کامپایل انجام میدهد که امکان شناسایی خطاهای نحوی را قبل از اجرای برنامه روی دستگاه فراهم میکند.
نکات اصلی
Room یک کتابخانه ORM از مجموعه Android Jetpack است که توسط Google برای سادهسازی کار با پایگاههای داده محلی SQLite در پلتفرم Android ایجاد شده است. این کتابخانه annotationهایی برای توصیف طرح داده فراهم میکند و به طور خودکار پیادهسازی واسطهای DAO را در مرحله کامپایل تولید میکند. برخلاف استفاده مستقیم از SQLiteOpenHelper، Room توسعهدهنده را از نوشتن حجم قابل توجهی کدهای تکراری برای ایجاد، باز کردن و مدیریت اتصال به پایگاه داده بینیاز میکند.
این کتابخانه در Google I/O 2017 به عنوان بخشی از مؤلفههای معماری Android معرفی شد. از آن زمان Room به استاندارد واقعی ذخیرهسازی محلی دادهها تبدیل شده و از نظر محبوبیت از راهحلهایی مانند GreenDAO و Realm برای Android پیشی گرفته است. طبق دادههای Google، این کتابخانه در بیش از 60٪ از برنامههای منتشر شده در Google Play که با دادههای محلی روی دستگاه کار میکنند، استفاده میشود.
ویژگی کلیدی — بررسی پرسوجوهای SQL در مرحله کامپایل با استفاده از پردازشگر annotation. اگر توسعهدهنده در دستور 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()
برای اعلام مبدل، کافی است annotation @TypeConverter را به یک متد استاتیک اضافه کنید و کلاس مبدل را در annotation @TypeConverters در سطح پایگاه داده مشخص کنید. Room به طور خودکار مبدل را هنگام خواندن و نوشتن نوع مربوطه در هر پرسوجوی SQL بدون فراخوانی دستی روشهای تبدیل اعمال میکند.
Room از سه مؤلفه اصلی تشکیل شده است: Entity، DAO و Database. هر کدام نقش کاملاً مشخصی را ایفا میکند و با annotation مربوطه مشخص میشود. با هم، آنها یک لایه دسترسی به داده کامل را تشکیل میدهند که منطق تجاری برنامه را از جزئیات پیادهسازی SQLite جدا میکند.
Entity یک کلاس داده است که ساختار یک جدول را در پایگاه داده توصیف میکند. هر فیلد کلاس با یک ستون جدول مطابقت دارد و هر ردیف در پایگاه داده — با یک نمونه از کلاس. annotation @Entity به Room اطلاع میدهد که کلاس یک جدول است. فیلد با annotation @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 annotation میشود. Room به طور خودکار پیادهسازی این واسط را در مرحله کامپایل تولید میکند. annotation @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 annotation میشود، جایی که نسخه طرح و فهرست موجودیتها مشخص میشوند. ایجاد نمونه پایگاه داده از طریق Room.databaseBuilder با مشخص کردن زمینه برنامه، نام فایل و کلاس Database انجام میشود.
Room SQLite را جایگزین نمیکند، بلکه روی آن به عنوان یک لایه انتزاعی کار میکند. معماری داخلی شامل پردازشگر annotation، تولیدکننده کد و استخر اتصالات است. در مرحله کامپایل، پردازشگر annotation کلاسهای Entity، DAO و Database را تجزیه و تحلیل میکند، سپس کلاسهای پیادهسازی با پسوند _Impl را تولید میکند. همه کلاسهای تولید شده در بسته ساخت قرار میگیرند و مستقیماً برای توسعهدهنده قابل مشاهده نیستند.
تولید کد در مرحله کامپایل — مکانیسم مرکزی Room. برای هر واسط DAO، کلاسی با پیادهسازی کامل همه روشهای annotation شده تولید میشود. پرسوجوهای SQL از annotation @Query از نظر صحت بررسی میشوند: پردازشگر نام ستونها را با فیلدهای Entity مطابقت میدهد و نحو SQL را بررسی میکند. در صورت تشخیص خطا، ساخت با پیام قابل فهمی متوقف میشود. این امر با استفاده از SQLiteOpenHelper خام غیرممکن است، جایی که خطاها فقط در زمان اجرا ظاهر میشوند.
فرآیند تولید شامل سه مرحله است. اول — اعتبارسنجی طرح: پردازشگر بررسی میکند که آیا همه کلاسهای ذکر شده در @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، پردازشگر annotation 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 شامل فیلدهایی با annotationهای @PrimaryKey و @ColumnInfo است. DAO روشهایی برای درج، دریافت فهرست و حذف فراهم میکند. Database Entity و DAO را از طریق annotation @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 ارث بری میکند، اعلام میشود. در annotation @Database، همه Entityهای نسخه جاری و شماره نسخه طرح مشخص میشوند. برای دریافت نمونه از الگوی singleton از طریق متد build Room.databaseBuilder با زمینه برنامه استفاده میشود. ذخیرهسازی نمونه پایگاه داده از ایجاد چندگانه که میتواند منجر به نشت حافظه شود، جلوگیری میکند.
مهاجرتها در Room مکانیسمی برای تغییر طرح پایگاه داده هنگام بهروزرسانی برنامه بدون از دست دادن دادههای موجود است. وقتی کاربر نسخه جدیدی با Entityهای تغییر یافته نصب میکند، Room عدم تطابق نسخه را تشخیص میدهد و مراحل مهاجرت مشخص شده را انجام میدهد. بدون مهاجرت، پایگاه داده حذف و دوباره ایجاد میشود که منجر به از دست دادن تمام دادههای ذخیرهشده کاربر میشود.
مهاجرت با کلاس Migration توصیف میشود که نسخه اولیه و نهایی پایگاه داده را دریافت میکند. در داخل متد migrate، پرسوجوی SQL ALTER TABLE یا CREATE TABLE برای تغییر طرح اجرا میشود. Room نمیتواند به طور خودکار تغییرات طرح را تشخیص دهد — توسعهدهنده باید برای هر تغییر Entity به صورت دستی مهاجرت بنویسد. از نسخه Room 2.4.0، تابع آزمایشی autoMigrations برای تولید خودکار مهاجرتها در دسترس است.
تابع autoMigrations به Room اجازه میدهد تا به طور خودکار مهاجرتها را بر اساس تفاوتهای بین نسخههای Entity تولید کند. برای استفاده از آن، کافی است annotation @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 با annotationها و بررسی 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 روابط را از طریق اشیاء تو در تو با annotation @Embedded و از طریق کلاسهای رابطه با annotation @Relation پشتیبانی میکند. برای پرسوجوهای پیچیده با اتصال جداول، از کلاسهای POJO سفارشی استفاده میشود که فیلدهای آنها از نتایج @Query با عملگر JOIN در SQL پر میشوند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید