Floor — что это, ORM поверх SQLite во Flutter

Автор: IT Sectr Опубликовано: 2026-03-13 Время чтения: 9 мин

Floor — ORM (Object-Relational Mapping) для Flutter, предоставляющий типизированную прослойку поверх SQLite. В отличие от raw SQLite-запросов, Floor генерирует DAO-классы из аннотированных Dart-моделей. По данным Pub.dev, 2024, Floor используется более чем в 3500 Flutter-проектах и входит в тройку самых популярных ORM-решений для локального хранения данных наряду с drift и hive.

Главное

  • Floor — ORM поверх SQLite с кодогенерацией DAO и Entity через аннотации
  • Типобезопасность — запросы проверяются на этапе компиляции, исключая runtime-ошибки SQL
  • DAO-паттерн — Data Access Object инкапсулирует SQL-запросы в Dart-методах
  • Миграции — встроенная поддержка версионирования схемы SQLite
  • Реактивность — Flow-запросы через Stream для автоматического обновления UI

Что такое Floor?

Floor — это ORM-библиотека для Flutter и Dart, построенная на основе SQLite. Она использует аннотации для описания сущностей (Entity), Data Access Object (DAO) и базы данных (Database). Генерация кода выполняется через build_runner и floor_generator — компилятор создаёт реализации DAO и управляющий класс базы данных. В отличие от raw sqflite, Floor полностью устраняет необходимость ручного преобразования ResultSet в Dart-объекты, автоматически маппя столбцы на поля Entity через отражение типов.

Floor следует шаблону Repository + DAO, знакомому разработчикам Android по Room. Каждая таблица представлена Dart-классом с аннотацией @Entity, SQL-запросы группируются в интерфейсах с аннотацией @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 для десктопа и web. При запуске приложения Floor создаёт или открывает SQLite-файл, применяет миграции и подготавливает DAO-методы для выполнения запросов. Все операции выполняются в асинхронном режиме через Future и Stream.

Генерация кода в Floor работает по следующему принципу: парсер считывает аннотации из исходников, создаёт AST (Abstract Syntax Tree) моделей и запросов, после чего генерирует Dart-файлы с префиксом _$. Сгенерированный код включает мапперы ResultSet → Entity и обратно.

Потокобезопасность

Floor работает с SQLite в одном изоляте. Все запросы выполняются асинхронно, но конкурентные записи блокируются на уровне SQLite. Для транзакций используется @transaction аннотация, которая гарантирует атомарность группы запросов и откат при ошибке.

Floor vs Drift: сравнение ORM для Flutter

И Floor, и Drift — ORM поверх SQLite, но они различаются по философии. Floor ближе к Room из Android, Drift — более реактивный с встроенным Stream API и компиляцией запросов через SQL-файлы. Выбор между ними зависит от опыта команды и требуемой реактивности.

ХарактеристикаFloorDrift
Тип запросовSQL-строки в @QueryDart-методы + sql-файлы
Кодогенерацияfloor_generator (build_runner)drift_dev (build_runner)
РеактивностьStream из DAOВстроенный Stream API + auto-updating
СложностьНизкая (знакомый SQL)Средняя (свой DSL)
МиграцииРучные SQL-скриптыАвтоматические + ручные
СовместимостьAndroid, iOS, macOSAndroid, iOS, Web, macOS, Linux

Когда выбирать Floor

Floor — выбор команд, уже знакомых с SQL и Android Room. Если разработчики привыкли писать SQL-запросы вручную и хотят минимальную обёртку над SQLite — Floor даёт типизацию без изучения нового DSL. Он также проще в отладке, так как сгенерированный код читаем и предсказуем.

Когда лучше Drift

Drift даёт более мощную реактивность и поддерживает больше платформ. Если приложение активно использует Stream для обновления UI, требует сложных запросов с JOIN и подзапросами или собирается под web — Drift предпочтительнее. Однако его порог входа выше из-за необходимости изучения собственного DSL.

Примеры кода с Floor

Floor строится вокруг аннотаций. Ниже приведён полный пример Entity, DAO и Database для приложения-списка задач. После запуска build_runner сгенерированные классы готовы к использованию.

Определение Entity и DAO

Класс TaskEntity с аннотацией @Entity маппится на таблицу task. Поле с @primaryKey становится первичным ключом. Интерфейс TaskDao содержит методы для операций с таблицей — каждый метод аннотирован @Query, @Insert, @Update или @Delete.

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);
}

Инициализация базы данных

Абстрактный класс с аннотацией @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 в аннотации @Database. При изменении Entity (добавлении или удалении полей) нужно увеличить версию и добавить миграцию. Миграция — это Dart-функция, которая получает транзакцию и выполняет SQL-запросы ALTER TABLE.

Пример миграции

Допустим, в версии 2 мы добавили поле dueDate в TaskEntity. Миграция выполняется SQL-запросом ALTER TABLE. Если миграция не указана, Floor вызывает MigrationStrategy, где можно задать fallback (например, пересоздание таблицы с потерей данных).

Тестирование запросов Floor

Floor не предоставляет встроенного мок-фреймворка, но базу данных можно легко заменить в тестах. Создайте inMemoryDatabaseBuilder — он создаёт SQLite-базу в памяти, идентичную по схеме продакшну. После каждого теста очищайте данные через deleteDatabase для изоляции тестовых сценариев.

Транзакции и пакетные операции в Floor

Floor поддерживает транзакции через аннотацию @transaction на DAO-методе. Внутри транзакции выполняются несколько запросов последовательно с гарантией отката при ошибке. Пакетная вставка через @Insert с параметром List оптимизирует вставку множества записей за один вызов — это в несколько раз быстрее, чем вставка по одной записи в цикле. Для массовых операций используйте batch-вставку по 100–200 записей: это оптимальный баланс между скоростью выполнения и потреблением оперативной памяти на мобильных устройствах с ограниченными ресурсами.

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 отличается от raw sqflite?

sqflite требует ручного написания SQL-запросов и маппинга ResultSet в объекты. Floor генерирует этот код автоматически: вы описываете Entity и DAO, а типизированные методы возвращают готовые Dart-объекты. Floor также проверяет SQL-запросы на этапе компиляции через аннотации.

Поддерживает ли Floor связи между таблицами?

Floor не имеет встроенных аннотаций для связей (ForeignKey, @Relation), как Room. Связи реализуются через ручные SQL JOIN-запросы в @Query. Для сложных реляционных схем лучше рассмотреть Drift с его встроенной поддержкой отношений.

Как отлаживать SQL-запросы Floor?

Floor позволяет включить callback callback при создании DatabaseBuilder — в него передаётся экземпляр sqflite.Database, на который можно повесить логгер. Альтернативно используйте floor_doctor для визуализации схемы и данных в dev-режиме.

Можно ли использовать Floor для web-сборки?

Floor использует sqflite, который не работает в web-окружении. Для web потребуется отдельная сборка с sqlite3 через WASM. В текущей версии Floor официально поддерживает Android, iOS и macOS. Для web используйте Drift с sqlite3-адаптером.

Как работает кэширование запросов в Floor?

Floor не имеет встроенного кэширования — каждый запрос выполняется к SQLite. Для кэширования повторяющихся запросов используйте слой Repository с in-memory кэшем (например, dart_cache). Floor лишь генерирует код для работы с SQLite, не добавляя надстроек над ним.

Итоги

  • Floor — ORM поверх SQLite с кодогенерацией Entity, DAO и Database через аннотации
  • Типобезопасность — SQL-запросы проверяются на этапе компиляции через аннотацию @Query
  • DAO-паттерн — SQL-запросы инкапсулированы в Dart-методах, разделяя модель и логику
  • Миграции — версионирование схемы через Migration с ручными ALTER TABLE
  • Реактивность — Stream из DAO для автоматического обновления UI при изменениях
  • Ограничения — нет встроенных связей, не поддерживает web-сборку
  • Рекомендация — выбирайте Floor для Flutter-проектов, где команда знакома с SQL и Room-подходом

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

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также