Floor — その概要、FlutterでSQLite上のORM

著者: IT Sectr 公開日: 2026-03-13 読了時間: 9 分

Floor — Flutter用のORM(Object-Relational Mapping)であり、SQLiteの上に型付けされたレイヤーを提供します。生のSQLiteクエリとは異なり、Floorはアノテーション付きのDartモデルからDAOクラスを生成します。Pub.dev(2024年)によると、Floorは3500以上のFlutterプロジェクトで使用されており、driftやhiveと並んでローカルデータストレージのための最も人気のあるORMソリューションのトップ3に入っています。

重要なポイント

  • Floor — アノテーションによるDAOとEntityのコード生成を備えたSQLite上のORM
  • 型安全性 — クエリはコンパイル時にチェックされ、実行時のSQLエラーを排除
  • DAOパターン — Data Access ObjectがSQLクエリをDartメソッドにカプセル化
  • マイグレーション — SQLiteスキーマのバージョン管理の組み込みサポート
  • リアクティビティ — UI自動更新のためのStreamを介したFlowクエリ

Floorとは?

FloorはSQLite上に構築されたFlutterおよびDart用のORMライブラリです。エンティティ(Entity)、Data Access Objects(DAO)、データベース(Database)を記述するためにアノテーションを使用します。コード生成はbuild_runnerfloor_generatorを介して実行されます — コンパイラがDAO実装と管理データベースクラスを作成します。生のsqfliteとは異なり、Floorは型リフレクションを介してカラムをEntityフィールドに自動的にマッピングすることにより、ResultSetからDartオブジェクトへの手動変換の必要性を完全に排除します。

FloorはAndroid開発者にはRoomでおなじみのRepository + DAOパターンに従います。各テーブルは@Entityアノテーションが付いたDartクラスで表現され、SQLクエリは@daoアノテーションが付いたインターフェースにグループ化され、データベースは@Databaseが付いた抽象クラスで組み立てられます。このアプローチはデータモデルをクエリロジックから厳密に分離します。

Flutter Pulse(2023年)によると、ローカルデータベースを必要とするFlutterプロジェクトの28%でFloorが選択されています。選択する主な理由は、SQLへの親しみやすさ(新しいクエリ言語を学ぶ必要がない)とコンパイル時のクエリチェックです。さらに、FloorはカスタムDSLを持つより抽象的なORMと比較してデバッグが容易な読みやすいコードを生成するため、チームの新しい開発者にとっての参入障壁を低くします。

Floorのアーキテクチャ

Floorは3つの層で構成されています:Entity(テーブルモデル)、DAO(クエリインターフェース)、Database(エントリポイント)。ジェネレータはフィールドマッピング用の_$_Entity実装とSQL実行用の_$_Daoを作成します。EntityまたはDAOが変更された場合、build_runnerを再起動するだけでコードが自動的に更新されます。スキーマバージョン間のマイグレーションのために、Floorはシーケンシャルなバージョン番号を使用し、ユーザーのデバイスでアプリを更新する際のデータ整合性を保証します。

FlutterでのFloorの仕組み

Floorはプラットフォームビルドにはsqfliteパッケージを、デスクトップとウェブにはsqlite3を介してSQLiteを使用します。アプリの起動時に、FloorはSQLiteファイルを作成または開き、マイグレーションを適用し、クエリ実行のためのDAOメソッドを準備します。すべての操作はFutureStreamを介して非同期に実行されます。

Floorでのコード生成は次のように機能します:パーサーがソースファイルからアノテーションを読み取り、モデルとクエリのAST(抽象構文木)を作成し、_$プレフィックスを持つDartファイルを生成します。生成されたコードにはResultSet → Entityマッパーとその逆が含まれます。

スレッドセーフティ

Floorは単一のアイソレートでSQLiteと連携します。すべてのクエリは非同期に実行されますが、同時書き込みはSQLiteレベルでロックされます。トランザクションには@transactionアノテーションが使用され、クエリグループの原子性とエラー時のロールバックを保証します。

Floor vs Drift:Flutter用ORMの比較

FloorDriftはどちらもSQLite上のORMですが、哲学が異なります。FloorはAndroidのRoomに近く、Driftは組み込みのStream APIとSQLファイルを介したクエリコンパイルを備えよりリアクティブです。どちらを選択するかは、チームの経験と必要なリアクティビティに依存します。

特性FloorDrift
クエリタイプ@Query内のSQL文字列Dartメソッド + sqlファイル
コード生成floor_generator(build_runner)drift_dev(build_runner)
リアクティビティDAOからのStream組み込みStream API + 自動更新
複雑さ低い(使い慣れたSQL)中程度(独自DSL)
マイグレーション手動SQLスクリプト自動 + 手動
互換性Android、iOS、macOSAndroid、iOS、Web、macOS、Linux

Floorを選ぶべき時

Floorは、すでにSQLとAndroid Roomに精通しているチームにとっての選択肢です。開発者が手動でSQLクエリを書くことに慣れており、SQLiteの上に最小限のラッパーを求めている場合 — Floorは新しいDSLを学ばずに型付けを提供します。生成されたコードは読みやすく予測可能であるため、デバッグも容易です。

Driftが適している時

Driftはより強力なリアクティビティを提供し、より多くのプラットフォームをサポートします。アプリがUI更新にStreamを積極的に使用し、JOINやサブクエリを含む複雑なクエリを必要とする場合、またはウェブをターゲットにする場合 — 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はDAOメソッドからのStreamの返却をサポートしています。テーブルに変更があると、Streamが新しいリストを発行します。これはFlutterのStreamBuilderと統合され — レコードが追加、変更、または削除されると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は@Databaseアノテーションのversionパラメータを介したデータベースのバージョン管理をサポートします。Entityが変更された場合(フィールドの追加や削除)、バージョンを上げてマイグレーションを追加する必要があります。マイグレーションはトランザクションを受け取り、ALTER TABLE SQLクエリを実行するDart関数です。

マイグレーションの例

バージョン2でTaskEntityにdueDateフィールドを追加したとします。マイグレーションはALTER TABLESQLクエリを介して実行されます。マイグレーションが指定されていない場合、FloorはMigrationStrategyを呼び出し、そこでフォールバックを設定できます(例:データ損失を伴うテーブルの再作成)。

Floorクエリのテスト

Floorは組み込みのモックフレームワークを提供していませんが、テストでデータベースを簡単に置き換えることができます。inMemoryDatabaseBuilderを作成すると — 本番環境と同じスキーマのインメモリSQLiteデータベースが作成されます。各テストの後、deleteDatabaseを介してデータをクリアし、テストシナリオを分離します。

Floorでのトランザクションとバッチ操作

FloorはDAOメソッドの@transactionアノテーションを介したトランザクションをサポートします。トランザクション内では、複数のクエリがエラー時のロールバック保証付きで順次実行されます。Listパラメータを持つ@Insertを介したバッチ挿入は、単一の呼び出しで複数のレコードを挿入することを最適化します — これはループで一度に1つのレコードを挿入するよりも数倍高速です。一括操作には、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クエリもチェックします。

Floorはテーブル間のリレーションシップをサポートしていますか?

FloorにはRoomのようなリレーションシップ用の組み込みアノテーション(ForeignKey、@Relation)はありません。リレーションシップは@Query内の手動SQL JOINクエリを介して実装されます。複雑なリレーショナルスキーマの場合は、リレーションシップの組み込みサポートを備えたDriftを検討してください。

FloorのSQLクエリをデバッグするには?

FloorではDatabaseBuilder作成時にcallbackを有効にできます — これにはsqflite.Databaseのインスタンスが渡され、ロガーをアタッチできます。代替として、devモードでスキーマとデータを可視化するためにfloor_doctorを使用してください。

Floorはウェブビルドに使用できますか?

Floorはsqfliteを使用しており、ウェブ環境では動作しません。ウェブの場合は、WASMを介したsqlite3を使用した別のビルドが必要です。現在のバージョンでは、Floorは公式にAndroid、iOS、macOSをサポートしています。ウェブの場合は、sqlite3アダプターと共にDriftを使用してください。

Floorでのクエリキャッシュの仕組みは?

Floorには組み込みのキャッシュはありません — 各クエリはSQLiteに対して実行されます。繰り返しのクエリをキャッシュするには、インメモリキャッシュ(例:dart_cache)を備えたRepositoryレイヤーを使用してください。FloorはSQLiteで動作するコードを生成するだけで、その上にオーバーヘッドを追加しません。

まとめ

  • Floor — アノテーションを介したEntity、DAO、Databaseのコード生成を備えたSQLite上のORM
  • 型安全性 — @Queryアノテーションを介してSQLクエリがコンパイル時にチェックされる
  • DAOパターン — SQLクエリがDartメソッドにカプセル化され、モデルをロジックから分離
  • マイグレーション — 手動ALTER TABLEを伴うMigrationによるスキーマバージョン管理
  • リアクティビティ — 変更時のUI自動更新のためのDAOからのStream
  • 制限事項 — 組み込みのリレーションシップなし、ウェブビルドをサポートしない
  • 推奨 — チームがSQLとRoomアプローチに精通しているFlutterプロジェクトにはFloorを選択

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください