跳转至

floor 数据库使用教程

floor 是类似 Android Room 的 SQLite ORM,简单易学,适合有 Android 背景的开发者。


一、floor 简介

1.1 什么是 floor?

floor 是 Flutter 的 SQLite ORM,API 设计灵感来自 Android Room: - 注解驱动:通过注解自动生成代码 - 类型安全:编译时检查 SQL 错误 - DAO 模式:数据访问对象分离 - 依赖注入:与 GetX 等状态管理完美结合

1.2 官方资源

资源 链接
Pub 页面 https://pub.dev/packages/floor
GitHub https://github.com/puszekajnik/floor
示例项目 https://github.com/vincenzopalazzo/floor_tutorial

1.3 floor vs drift vs sqflite

特性 floor drift sqflite
类型安全 ✅ 完整 ✅ 完整
代码生成 ✅ 注解 ✅ 声明式
流式查询
Web 支持
学习曲线 中高
API 风格 类似 Android Room Dart 原生风格 原生 SQL

二、安装配置

2.1 添加依赖

# pubspec.yaml
dependencies:
  floor: ^1.4.2
  flutter:
    sdk: flutter

dev_dependencies:
  floor_generator: ^1.4.2
  build_runner: ^2.4.7

2.2 安装命令

flutter pub get

三、创建数据库

3.1 定义实体(Entity)

// lib/database/entity/user.dart
import 'package:floor/floor.dart';

@Entity(tableName: 'users')
class User {
  @PrimaryKey(autoGenerate: true)
  final int id;

  @ColumnInfo(name: 'name')
  final String name;

  @ColumnInfo(name: 'email')
  final String email;

  @ColumnInfo(name: 'age')
  final int? age;

  @ColumnInfo(name: 'created_at')
  final DateTime createdAt;

  User({
    required this.id,
    required this.name,
    required this.email,
    this.age,
    required this.createdAt,
  });
}

3.2 定义 DAO(数据访问对象)

// lib/database/dao/user_dao.dart
import 'package:floor/floor.dart';
import '../entity/user.dart';

@dao
abstract class UserDao {
  @Query('SELECT * FROM users')
  Future<List<User>> findAllUsers();

  @Query('SELECT * FROM users WHERE id = :id')
  Future<User?> findUserById(int id);

  @Query('SELECT * FROM users WHERE name LIKE :name')
  Future<List<User>> searchUsers(String name);

  @Insert(onConflict: OnConflictStrategy.replace)
  Future<void> insertUser(User user);

  @Update
  Future<void> updateUser(User user);

  @Delete
  Future<void> deleteUser(User user);

  @Query('DELETE FROM users WHERE id = :id')
  Future<void> deleteUserById(int id);
}

3.3 定义 Database 类

// lib/database/app_database.dart
import 'package:floor/floor.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart' as sqflite;

import 'entity/user.dart';
import 'dao/user_dao.dart';

part 'app_database.g.dart';

@Database(version: 1, entities: [User])
abstract class AppDatabase extends FloorDatabase {
  UserDao get userDao;
}

3.4 初始化数据库

// lib/database/database_builder.dart
import 'package:floor/floor.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import '../entity/user.dart';
import '../dao/user_dao.dart';
import 'app_database.dart';

Future<AppDatabase> getDatabase() async {
  return await $FloorAppDatabase
      .databaseBuilder('my_database.db')
      .build();
}

3.5 生成代码

dart run build_runner build

生成后会产生 app_database.g.dart 文件。


四、进阶用法

4.1 多表关联

// 帖子表
@Entity(tableName: 'posts', foreignKeys: [
  ForeignKey(
    childColumns: ['user_id'],
    parentColumns: ['id'],
    entity: User,
  )
])
class Post {
  @PrimaryKey(autoGenerate: true)
  final int id;

  final String title;

  final String content;

  final int userId;

  final DateTime createdAt;

  Post({
    required this.id,
    required this.title,
    required this.content,
    required this.userId,
    required this.createdAt,
  });
}

4.2 自定义查询

@dao
abstract class PostDao {
  // 多条件查询
  @Query('SELECT * FROM posts WHERE user_id = :userId ORDER BY created_at DESC')
  Future<List<Post>> findPostsByUser(int userId);

  // 分页查询
  @Query('SELECT * FROM posts ORDER BY created_at DESC LIMIT :limit OFFSET :offset')
  Future<List<Post>> findPostsPaginated(int limit, int offset);

  // 聚合查询
  @Query('SELECT COUNT(*) FROM posts WHERE user_id = :userId')
  Future<int> countPostsByUser(int userId);

  // 联表查询
  @Query('''
    SELECT posts.*, users.name as author_name
    FROM posts
    INNER JOIN users ON posts.user_id = users.id
    WHERE posts.id = :postId
  ''')
  Future<PostWithAuthor?> findPostWithAuthor(int postId);
}

// 使用 @Embedded 关联对象
class PostWithAuthor {
  @Embedded()
  final Post post;

  final String authorName;

  PostWithAuthor({required this.post, required this.authorName});
}

4.3 响应式查询(Stream)

@dao
abstract class UserDao {
  // 流式查询 - 数据变化自动通知
  @Query('SELECT * FROM users')
  Stream<List<User>> watchAllUsers();

  // 条件流式查询
  @Query('SELECT * FROM users WHERE id = :id')
  Stream<User?> watchUserById(int id);
}

五、数据库迁移

5.1 版本升级

Future<AppDatabase> getDatabase() async {
  return await $FloorAppDatabase
      .databaseBuilder('my_database.db')
      .addMigrations([
        // v2 迁移
        Migration1to2(),
        // v3 迁移
        Migration2to3(),
      ])
      .build();
}

// 定义迁移
class Migration1to2 extends Migration {
  @override
  void migrate(DriftDatabase database) {
    database.execute('''
      CREATE TABLE IF NOT EXISTS posts (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL,
        content TEXT NOT NULL,
        user_id INTEGER NOT NULL,
        created_at TEXT NOT NULL,
        FOREIGN KEY (user_id) REFERENCES users (id)
      )
    ''');
  }
}

5.2 数据迁移示例

class Migration2to3 extends Migration {
  @override
  void migrate(DriftDatabase database) async {
    // 添加新列
    await database.execute('''
      ALTER TABLE users ADD COLUMN bio TEXT
    ''');

    // 迁移旧数据
    await database.execute('''
      UPDATE users SET bio = '默认简介' WHERE bio IS NULL
    ''');
  }
}

六、结合 GetX 使用

6.1 创建 Service

// lib/services/database_service.dart
import 'package:get/get.dart';
import '../database/app_database.dart';

class DatabaseService extends GetxService {
  late final AppDatabase _database;

  AppDatabase get database => _database;

  Future<DatabaseService> init() async {
    _database = await getDatabase();
    return this;
  }

  @override
  void onClose() {
    _database.close();
    super.onClose();
  }
}

6.2 初始化

// main.dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Get.putAsync(() => DatabaseService().init());

  runApp(MyApp());
}

6.3 创建 Controller

// lib/controllers/user_controller.dart
import 'package:get/get.dart';
import '../database/entity/user.dart';
import '../services/database_service.dart';

class UserController extends GetxController {
  final _dbService = Get.find<DatabaseService>();

  final RxList<User> users = <User>[].obs;
  final RxBool isLoading = false.obs;

  @override
  void onInit() {
    super.onInit();
    loadUsers();
  }

  Future<void> loadUsers() async {
    isLoading.value = true;
    try {
      final result = await _dbService.database.userDao.findAllUsers();
      users.value = result;
    } finally {
      isLoading.value = false;
    }
  }

  Future<void> addUser(String name, String email, int? age) async {
    final user = User(
      id: 0,  // autoGenerate 会忽略这个值
      name: name,
      email: email,
      age: age,
      createdAt: DateTime.now(),
    );
    await _dbService.database.userDao.insertUser(user);
    await loadUsers();
  }

  Future<void> deleteUser(int id) async {
    await _dbService.database.userDao.deleteUserById(id);
    await loadUsers();
  }
}

七、实战示例:任务清单应用

7.1 定义实体

@Entity(tableName: 'tasks')
class Task {
  @PrimaryKey(autoGenerate: true)
  final int id;

  final String title;

  final String? description;

  final bool isCompleted;

  final int priority;  // 1=低 2=中 3=高

  final DateTime dueDate;

  final DateTime createdAt;

  Task({
    required this.id,
    required this.title,
    this.description,
    required this.isCompleted,
    required this.priority,
    required this.dueDate,
    required this.createdAt,
  });
}

7.2 定义 DAO

@dao
abstract class TaskDao {
  @Query('SELECT * FROM tasks ORDER BY isCompleted ASC, priority DESC, dueDate ASC')
  Stream<List<Task>> watchAllTasks();

  @Query('SELECT * FROM tasks WHERE isCompleted = 0 ORDER BY priority DESC, dueDate ASC')
  Stream<List<Task>> watchPendingTasks();

  @Query('SELECT * FROM tasks WHERE id = :id')
  Future<Task?> findTaskById(int id);

  @Insert
  Future<void> insertTask(Task task);

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

  @Delete
  Future<void> deleteTask(Task task);

  @Query('UPDATE tasks SET isCompleted = :completed WHERE id = :id')
  Future<void> updateTaskStatus(int id, bool completed);

  @Query('DELETE FROM tasks WHERE isCompleted = 1')
  Future<void> deleteCompletedTasks();
}

7.3 Controller 实现

class TaskController extends GetxController {
  final _dbService = Get.find<DatabaseService>();
  final TaskDao _taskDao => _dbService.database.taskDao;

  final RxList<Task> tasks = <Task>[].obs;
  StreamSubscription<List<Task>>? _subscription;

  @override
  void onInit() {
    super.onInit();
    _subscription = _taskDao.watchAllTasks().listen((taskList) {
      tasks.value = taskList;
    });
  }

  @override
  void onClose() {
    _subscription?.cancel();
    super.onClose();
  }

  Future<void> addTask(String title, String? description, int priority, DateTime dueDate) async {
    final task = Task(
      id: 0,
      title: title,
      description: description,
      isCompleted: false,
      priority: priority,
      dueDate: dueDate,
      createdAt: DateTime.now(),
    );
    await _taskDao.insertTask(task);
  }

  Future<void> toggleTaskStatus(Task task) async {
    await _taskDao.updateTaskStatus(task.id, !task.isCompleted);
  }

  Future<void> deleteTask(Task task) async {
    await _taskDao.deleteTask(task);
  }

  Future<void> clearCompleted() async {
    await _taskDao.deleteCompletedTasks();
  }
}

八、常见问题

8.1 编译报错 "MissingDataException"

确保在 @Database 注解中正确注册所有实体:

@Database(version: 1, entities: [User, Post, Comment])
abstract class AppDatabase extends FloorDatabase {
  UserDao get userDao;
  PostDao get postDao;
  CommentDao get commentDao;
}

8.2 代码生成失败

# 清理缓存
dart run build_runner clean

# 重新生成
dart run build_runner build --delete-conflicting-outputs

8.3 类型转换错误

使用 @TypeConverter 自定义类型转换:

class DateTimeConverter extends TypeConverter<DateTime, int> {
  @override
  DateTime decode(int databaseValue) {
    return DateTime.fromMillisecondsSinceEpoch(databaseValue);
  }

  @override
  int encode(DateTime value) {
    return value.millisecondsSinceEpoch;
  }
}

// 在实体中使用
@Entity(tableName: 'tasks', typeConverters: [DateTimeConverter])
class Task { ... }

8.4 数据库文件路径问题

// 自定义数据库路径
Future<AppDatabase> getDatabase(String dbPath) async {
  return await $FloorAppDatabase
      .databaseBuilder(dbPath)  // 传入完整路径
      .build();
}

九、总结

floor 是 Flutter 中学习曲线最低的 SQLite ORM:

  • 类似 Android Room(Android 开发者友好)
  • 注解驱动代码生成
  • 类型安全
  • 流式查询支持
  • DAO 模式清晰

如果你是 Android 开发者或熟悉 Room,floor 是入门最快的选择。

如果需要 Web 支持或更强大的功能,建议使用 drift。