Floor — چیست، ORM روی SQLite در Flutter

نویسنده: IT Sectr منتشر شده: 2026-03-13 زمان مطالعه: 9 دقیقه

Floor — ORM (Object-Relational Mapping) برای Flutter است که یک لایه تایپ‌شده روی SQLite ارائه می‌دهد. برخلاف پرس‌وجوهای خام SQLite، Floor کلاس‌های DAO را از مدل‌های Dart حاشیه‌نویسی شده تولید می‌کند. بر اساس داده‌های Pub.dev، 2024، Floor در بیش از 3500 پروژه Flutter استفاده می‌شود و در کنار drift و hive جزو سه راه‌حل محبوب ORM برای ذخیره‌سازی محلی داده‌ها است.

نکات کلیدی

  • Floor — ORM روی SQLite با تولید کد DAO و Entity از طریق annotationها
  • ایمنی نوع — پرس‌وجوها در مرحله کامپایل بررسی می‌شوند و خطاهای زمان اجرای SQL را حذف می‌کنند
  • الگوی DAO — Data Access Object پرس‌وجوهای SQL را در متدهای Dart کپسوله می‌کند
  • مهاجرت‌ها — پشتیبانی داخلی از نسخه‌بندی طرح SQLite
  • واکنش‌گرایی — پرس‌وجوهای Flow از طریق Stream برای به‌روزرسانی خودکار UI

Floor چیست؟

Floor — یک کتابخانه ORM برای Flutter و Dart است که بر اساس SQLite ساخته شده است. از annotationها برای توصیف موجودیت‌ها (Entity)، Data Access Object (DAO) و پایگاه داده (Database) استفاده می‌کند. تولید کد از طریق build_runner و floor_generator انجام می‌شود — کامپایلر پیاده‌سازی‌های DAO و کلاس مدیریت پایگاه داده را ایجاد می‌کند. برخلاف sqflite خام، Floor نیاز به تبدیل دستی ResultSet به اشیاء Dart را کاملاً از بین می‌برد و به طور خودکار ستون‌ها را به فیلدهای Entity از طریق بازتاب نوع نگاشت می‌کند.

Floor از الگوی Repository + DAO پیروی می‌کند که برای توسعه‌دهندگان Android از Room آشنا است. هر جدول با یک کلاس Dart با annotation @Entity نمایش داده می‌شود، پرس‌وجوهای SQL در رابط‌های دارای annotation @dao گروه‌بندی می‌شوند و پایگاه داده در یک کلاس انتزاعی با @Database جمع‌آوری می‌شود. این رویکرد مدل داده و منطق پرس‌وجو را به طور دقیق جدا می‌کند.

بر اساس Flutter Pulse (2023)، Floor در 28% از پروژه‌های Flutter که نیاز به پایگاه داده محلی دارند انتخاب می‌شود. دلایل اصلی انتخاب — آشنایی با SQL (نیازی به یادگیری زبان پرس‌وجوی جدید نیست) و بررسی پرس‌وجوها در مرحله کامپایل است. علاوه بر این، Floor کد قابل خواندنی تولید می‌کند که برخلاف ORMهای انتزاعی‌تر با DSL سفارشی، به راحتی اشکال‌زدایی می‌شود و آستانه ورود را برای توسعه‌دهندگان جدید تیم کاهش می‌دهد.

معماری Floor

Floor از سه لایه تشکیل شده است: Entity (مدل جدول)، DAO (رابط پرس‌وجو) و Database (نقطه ورود). مولد پیاده‌سازی‌های _$_Entity برای نگاشت فیلدها و _$_Dao برای اجرای SQL ایجاد می‌کند. هنگام تغییر Entity یا DAO کافی است build_runner را مجدداً اجرا کنید — کد به طور خودکار به‌روزرسانی می‌شود. برای مهاجرت بین نسخه‌های طرح، Floor از شماره نسخه‌های متوالی استفاده می‌کند که یکپارچگی داده‌ها را در هنگام به‌روزرسانی برنامه در دستگاه‌های کاربران تضمین می‌کند.

Floor در Flutter چگونه کار می‌کند؟

Floor از SQLite از طریق بسته sqflite برای بیلدهای پلتفرمی و sqlite3 برای دسکتاپ و وب استفاده می‌کند. هنگام راه‌اندازی برنامه، Floor فایل SQLite را ایجاد یا باز می‌کند، مهاجرت‌ها را اعمال می‌کند و متدهای DAO را برای اجرای پرس‌وجوها آماده می‌سازد. تمام عملیات در حالت ناهمگام از طریق Future و Stream انجام می‌شوند.

تولید کد در Floor بر اساس اصل زیر کار می‌کند: تجزیه‌گر annotationها را از کدهای منبع می‌خواند، AST (درخت نحو انتزاعی) مدل‌ها و پرس‌وجوها را ایجاد می‌کند، سپس فایل‌های Dart با پیشوند _$ تولید می‌کند. کد تولید شده شامل نگاشت‌گرهای ResultSet → Entity و برعکس است.

ایمنی نخ

Floor با SQLite در یک ایزوله کار می‌کند. تمام پرس‌وجوها به صورت ناهمگام اجرا می‌شوند، اما نوشتن‌های همزمان در سطح SQLite مسدود می‌شوند. برای تراکنش‌ها از annotation @transaction استفاده می‌شود که اتمی بودن گروه پرس‌وجوها و بازگشت در صورت خطا را تضمین می‌کند.

Floor در مقابل Drift: مقایسه ORM برای Flutter

هم Floor و هم Drift — ORM روی SQLite هستند، اما در فلسفه تفاوت دارند. Floor به Room در Android نزدیک‌تر است، Drift — واکنش‌گراتر با Stream API داخلی و کامپایل پرس‌وجو از طریق فایل‌های SQL. انتخاب بین آنها به تجربه تیم و واکنش‌گرایی مورد نیاز بستگی دارد.

ویژگیFloorDrift
نوع پرس‌وجورشته‌های SQL در @Queryمتدهای Dart + فایل‌های sql
تولید کدfloor_generator (build_runner)drift_dev (build_runner)
واکنش‌گراییStream از DAOStream API داخلی + به‌روزرسانی خودکار
پیچیدگیکم (SQL آشنا)متوسط (DSL خاص خود)
مهاجرت‌هااسکریپت‌های دستی SQLخودکار + دستی
سازگاریAndroid، iOS، macOSAndroid، iOS، Web، macOS، Linux

چه زمانی Floor را انتخاب کنیم

Floor — انتخاب تیم‌هایی است که قبلاً با SQL و Android Room آشنا هستند. اگر توسعه‌دهندگان عادت دارند پرس‌وجوهای SQL را دستی بنویسند و یک پوشش حداقلی روی SQLite می‌خواهند — Floor بدون یادگیری DSL جدید، تایپ‌سازی ارائه می‌دهد. همچنین اشکال‌زدایی آن آسان‌تر است، زیرا کد تولید شده قابل خواندن و قابل پیش‌بینی است.

چه زمانی Drift بهتر است

Drift واکنش‌گرایی قدرتمندتری ارائه می‌دهد و پلتفرم‌های بیشتری را پشتیبانی می‌کند. اگر برنامه به طور فعال از Stream برای به‌روزرسانی UI استفاده می‌کند، نیاز به پرس‌وجوهای پیچیده با JOIN و زیرپرس‌وجو دارد یا برای وب ساخته می‌شود — Drift ارجح است. با این حال، آستانه ورود آن به دلیل نیاز به یادگیری DSL خاص خود بالاتر است.

مثال‌های کد با Floor

Floor حول annotationها ساخته شده است. در زیر یک مثال کامل از Entity، DAO و Database برای یک برنامه لیست وظایف آورده شده است. پس از اجرای build_runner، کلاس‌های تولید شده آماده استفاده هستند.

تعریف Entity و DAO

کلاس TaskEntity با annotation @Entity به جدول task نگاشت می‌شود. فیلد با @primaryKey به کلید اصلی تبدیل می‌شود. رابط TaskDao شامل متدهایی برای عملیات روی جدول است — هر متد با @Query، @Insert، @Update یا @Delete annotation شده است.

dart
@entity
class TaskEntity {
    @PrimaryKey(autoGenerate: true)
    final int id;
    final String title;
    final bool isCompleted;
    final int priority;

    TaskEntity({this.id, required this.title,
        this.isCompleted = false, this.priority = 0});
}

@dao
abstract class TaskDao {
    @Query('SELECT * FROM TaskEntity ORDER BY priority DESC')
    Future<List<TaskEntity>> getAllTasks();

    @Insert
    Future<int> insertTask(TaskEntity task);

    @Update
    Future<void> updateTask(TaskEntity task);

    @Query('SELECT * FROM TaskEntity WHERE isCompleted = :status')
    Stream<List<TaskEntity>> watchTasks(bool status);
}

راه‌اندازی پایگاه داده

کلاس انتزاعی با annotation @Database Entity و DAO را به هم متصل می‌کند. متد databaseBuilder یک نمونه از پایگاه داده ایجاد می‌کند. پس از فراخوانی build، پایگاه داده آماده است: Floor فایل SQLite را باز می‌کند، مهاجرت‌ها را اعمال می‌کند و DAO را برای کار برمی‌گرداند.

dart
@Database(version: 1, entities: [TaskEntity])
abstract class AppDatabase extends FloorDatabase {
    TaskDao get taskDao;
}

// استفاده
final database = await $FloorAppDatabase.databaseBuilder('app.db').build();
final taskDao = database.taskDao;
final tasks = await taskDao.getAllTasks();

پرس‌وجوهای واکنش‌گرا از طریق Stream

Floor از بازگرداندن Stream از متدهای DAO پشتیبانی می‌کند. با هر تغییری در جدول، Stream یک لیست جدید منتشر می‌کند. این با StreamBuilder در Flutter یکپارچه می‌شود — UI با اضافه شدن، تغییر یا حذف رکوردها به طور خودکار به‌روزرسانی می‌شود.

dart
@Query('SELECT * FROM TaskEntity ORDER BY priority DESC')
Stream<List<TaskEntity>> watchAllTasks();

// در ویجت Flutter
StreamBuilder<List<TaskEntity>>(
    stream: taskDao.watchAllTasks(),
    builder: (context, snapshot) {
        final tasks = snapshot.data ?? [];
        return ListView.builder(
            itemCount: tasks.length,
            itemBuilder: (_, i) => TaskTile(tasks[i]),
        );
    },
)

مهاجرت‌ها و نسخه‌بندی در Floor

Floor از نسخه‌بندی پایگاه داده از طریق پارامتر version در annotation @Database پشتیبانی می‌کند. هنگام تغییر Entity (اضافه یا حذف فیلدها) باید نسخه را افزایش داده و یک مهاجرت اضافه کنید. مهاجرت — یک تابع Dart است که یک تراکنش دریافت می‌کند و پرس‌وجوهای SQL ALTER TABLE را اجرا می‌کند.

مثال مهاجرت

فرض کنید در نسخه 2 فیلد dueDate را به TaskEntity اضافه کردیم. مهاجرت با پرس‌وجوی SQL ALTER TABLE اجرا می‌شود. اگر مهاجرت مشخص نشده باشد، Floor MigrationStrategy را فراخوانی می‌کند، جایی که می‌توان fallback تنظیم کرد (مثلاً بازسازی جدول با از دست دادن داده).

تست پرس‌وجوهای Floor

Floor چارچوب داخلی mock ارائه نمی‌دهد، اما پایگاه داده را می‌توان به راحتی در تست‌ها جایگزین کرد. inMemoryDatabaseBuilder ایجاد کنید — یک پایگاه داده SQLite در حافظه می‌سازد که از نظر طرح با تولید یکسان است. پس از هر تست، داده‌ها را از طریق deleteDatabase برای ایزوله‌سازی سناریوهای تست پاک کنید.

تراکنش‌ها و عملیات دسته‌ای در Floor

Floor از تراکنش‌ها از طریق annotation @transaction روی متد DAO پشتیبانی می‌کند. در داخل تراکنش، چندین پرس‌وجو به صورت متوالی با تضمین بازگشت در صورت خطا اجرا می‌شوند. درج دسته‌ای از طریق @Insert با پارامتر List<T> درج چندین رکورد را در یک فراخوانی بهینه می‌کند — این چندین برابر سریع‌تر از درج تک‌تک رکوردها در یک حلقه است. برای عملیات انبوه از درج دسته‌ای 100–200 رکورد استفاده کنید: این تعادل بهینه بین سرعت اجرا و مصرف RAM در دستگاه‌های همراه با منابع محدود است.

dart
final migration1to2 = Migration(1, 2, (database) async {
    await database.execute(
        'ALTER TABLE TaskEntity ADD COLUMN dueDate TEXT'
    );
});

final database = await $FloorAppDatabase.databaseBuilder('app.db')
    .addMigrations([migration1to2])
    .build();

سوالات متداول

Floor چه تفاوتی با sqflite خام دارد؟

sqflite نیاز به نوشتن دستی پرس‌وجوهای SQL و نگاشت ResultSet به اشیاء دارد. Floor این کد را به طور خودکار تولید می‌کند: شما Entity و DAO را توصیف می‌کنید و متدهای تایپ‌شده اشیاء Dart آماده را برمی‌گردانند. Floor همچنین پرس‌وجوهای SQL را در مرحله کامپایل از طریق annotationها بررسی می‌کند.

آیا Floor از روابط بین جداول پشتیبانی می‌کند؟

Floor annotationهای داخلی برای روابط (ForeignKey، @Relation) مانند Room ندارد. روابط از طریق پرس‌وجوهای دستی SQL JOIN در @Query پیاده‌سازی می‌شوند. برای طرح‌های رابطه‌ای پیچیده بهتر است Drift را با پشتیبانی داخلی از روابط در نظر بگیرید.

چگونه پرس‌وجوهای SQL Floor را اشکال‌زدایی کنیم؟

Floor امکان فعال‌سازی callback callback را هنگام ایجاد DatabaseBuilder فراهم می‌کند — نمونه sqflite.Database به آن ارسال می‌شود که می‌توان یک logger به آن متصل کرد. جایگزین، از floor_doctor برای تجسم طرح و داده‌ها در حالت توسعه استفاده کنید.

آیا می‌توان از Floor برای بیلد وب استفاده کرد؟

Floor از sqflite استفاده می‌کند که در محیط وب کار نمی‌کند. برای وب به یک بیلد جداگانه با sqlite3 از طریق WASM نیاز است. در نسخه فعلی، Floor به طور رسمی از Android، iOS و macOS پشتیبانی می‌کند. برای وب از Drift با آداپتور sqlite3 استفاده کنید.

کش کردن پرس‌وجوها در Floor چگونه کار می‌کند؟

Floor کش داخلی ندارد — هر پرس‌وجو به SQLite اجرا می‌شود. برای کش کردن پرس‌وجوهای تکراری از لایه Repository با کش درون حافظه استفاده کنید (مثلاً dart_cache). Floor فقط کد کار با SQLite را تولید می‌کند بدون اینکه لایه‌های اضافی روی آن اضافه کند.

خلاصه

  • Floor — ORM روی SQLite با تولید کد Entity، DAO و Database از طریق annotationها
  • ایمنی نوع — پرس‌وجوهای SQL در مرحله کامپایل از طریق annotation @Query بررسی می‌شوند
  • الگوی DAO — پرس‌وجوهای SQL در متدهای Dart کپسوله شده، مدل و منطق را جدا می‌کند
  • مهاجرت‌ها — نسخه‌بندی طرح از طریق Migration با ALTER TABLE دستی
  • واکنش‌گرایی — Stream از DAO برای به‌روزرسانی خودکار UI هنگام تغییرات
  • محدودیت‌ها — عدم وجود روابط داخلی، عدم پشتیبانی از بیلد وب
  • توصیه — Floor را برای پروژه‌های Flutter انتخاب کنید که تیم با SQL و رویکرد Room آشناست

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید