Flutter利用ORM框架简化本地数据库管理详解

 更新时间:2023年04月10日 09:08:20   作者:岛上码农  
使用 sqflite 相对来说还是有点复杂,比如遇到数据不兼容的时候需要手动转换,增加了不少繁琐的代码。本篇我们就来介绍一个 ORM 框架,来简化数据库的管理,感兴趣的可以了解一下

前言

前面两篇我们介绍了使用 sqflite 管理 Flutter 本地 SQLite 数据库。使用 sqflite 相对来说还是有点复杂,比如需要自己写数据库数据到实体类对象的转换,遇到数据不兼容的时候需要手动转换,增加了不少繁琐的代码。本篇我们就来介绍一个 ORM 框架,来简化数据库的管理,这个框架就是 floor

floor 简介

floor 是基于 sqflite 的一个轻量级的 ORM 框架,通过注解和代码生成可以将数据库数据直接映射为实体类对象。floor 内置了很多操作数据库的方法,比如增删改查,让我们快速接入数据库。同时,也可以在注解中编写 SQL来实现复杂的数据库查询,比如 IN 查询、数据统计等等。通过注解和代码生成能够减少大量手写代码,提高我们的开发效率和代码的可维护性。floor 的文档非常完善,大家可以到github阅读相关的文档:pinchbv.github.io/floor/getti…。 floor 需要引入的开发依赖如下,都是用于基于注解生成代码。

dev_dependencies:
    flutter_test:
        sdk: flutter

    # ...
    floor_generator: ^1.4.1
    build_runner: ^2.3.3

接下来我们就以之前的备忘录为例,来看看使用 floor 后的改善。

ORM 映射

我们之前的备忘录类 Memo 需要自己编写 fromJsontoJson 方法来实现数据库数据到实体类对象的转换。此外,遇到 SQLite 不支持的数据类型(如 DateTimeList<String>)时,还需要处理转换代码。我们来看 floor 如何处理。 floor 将数据库操作分为实体类和 DAO,实体类与数据库的映射通过注解完成。例如我们的 Memo 类,调整后的代码如下所示。

@entity
class Memo {
  @PrimaryKey(autoGenerate: true)
  final int? id;
  String title;
  String content;
  @ColumnInfo(name: 'created_time')
  DateTime createdTime;
  @ColumnInfo(name: 'modified_time')
  DateTime modifiedTime;
  List<String> tags;

  Memo({
    this.id,
    required this.title,
    required this.content,
    required this.createdTime,
    required this.modifiedTime,
    required this.tags,
  });
}

这里说明一下常见的注解:

  • @entity:表示这是一个实体类,会和数据库的某个数据表映射,默认表名就是类名。如果要手动指定表名,可以使用@Entity(tableName: tableName)通过 tableName 指定数据表名称。floor 会自动根据@entity 注解生成创建数据表的 SQL 语句。
  • @primaryKey:表示字段为主键,如果需要使用自增主键,可以使用@PrimaryKey(autoGenerate: true)
  • @ColumnInfo(name: name):设置实体类成员属性和数据表字段的映射关系,默认 floor 使用的数据表字段名称和类成员属性名称一致,如果需要指定数据表字段名,就可以使用这个注解。
  • @ignore:忽略某个成员属性,即该属性不产生相应的数据表字段。注意,通过 get 方法产生的计算属性默认就会被忽略,例如长方形面积 double get area => width * height

DAO 用于从数据库查询数据并转换为实体类对象,从数据库查询数据和转换的代码通过注解直接生成。DAO 提供了基础的插入、更新和删除方法,这些方法可以通过注解@insert@update @delete完成,不需要编写 SQL。 同时,对于插入和更新可以设置冲突策略,策略可以是中止(abort)、回滚(rollback)、替换(replace)、忽略(ignore)、失败(fail)。其中除了替换以外,其他都是和数据库事务有关。

@dao
abstract class MemoDao {
  @Query('SELECT * FROM Memo ORDER BY modified_time DESC')
  Future<List<Memo>> findAllMemos();

  @Query(
      'SELECT * FROM Memo WHERE title LIKE :searchKey OR content LIKE :searchKey ORDER BY modified_time DESC')
  Future<List<Memo>> findMemoWithSearchKey(String searchKey);

  @Query('SELECT * FROM Memo WHERE id = :id')
  Stream<Memo?> findMemoById(int id);

  @insert
  Future<void> insertMemo(Memo memo);

  @Update(onConflict: OnConflictStrategy.replace)
  Future<void> updateMemo(Memo memo);

  @delete
  Future<void> deleteMemo(Memo memo);
}

转换器

使用 floor 可以统一 Dart 数据类型到 SQLite 字段的转换方式。通过定义不同的类型转换器TypeConverter实现数据库和Dart 数据类型的转换,从而避免了每个实体类都要单独编写转换代码。比如我们在备忘录用到了两个类型 DateTimeList<String> 就定义了对应的转换器。

class StringListConverter extends TypeConverter<List<String>, String> {
  @override
  List<String> decode(String databaseValue) {
    return databaseValue.isNotEmpty ? databaseValue.split('|') : [];
  }

  @override
  String encode(List<String> value) {
    return value.join('|');
  }
}

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

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

使用转换器只需要在定义数据库FloorDatabase 的抽象类的时候引入到注解@TypeConverters就可以了。

@TypeConverters([StringListConverter, DateTimeConverter])
@Database(version: 1, entities: [Memo])
abstract class MemoDatabase extends FloorDatabase {
  MemoDao get memoDao;
}

代码改造

通常来说 DAO 对象会在很多地方共用,适合使用单例方式来构造。这里我们在App启动的时候就使用 GetIt来实现MemoDao 的单例注册。

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final database =
      await $FloorMemoDatabase.databaseBuilder('app_database.db').build();

  final dao = database.memoDao;

  getIt.registerSingleton<MemoDao>(dao, signalsReady: true);

  runApp(const MyApp());
}

这里调用ensureInitialized这个方法是保证 Flutter 和原生交互的部分已经完成,因为在 sqflite 中需要使用原生的文件存储。 备忘录列表的代码改造涉及数据操作的有两处,分别是列表刷新和删除备忘录。列表模糊搜索时需要自己组装模糊搜索的字符,比如我们这里使用了百分号将搜索关键词包裹实现任意匹配。删除备忘录需要根据是否有搜索调用不同的方法,这是因为对应的 SQL 不同。

void _refreshMemoList({String? searchKey}) async {
  List<Memo> memoList = searchKey == null
      ? await GetIt.I<MemoDao>().findAllMemos()
      : await GetIt.I<MemoDao>().findMemoWithSearchKey('%$searchKey%');
  setState(() {
    _memoList = memoList;
  });
}

删除就非常简单了,直接调用删除方法就好了。

void _deleteMemo(Memo memo) async {
    final confirmed = await _showDeleteConfirmationDialog(memo);
    if (confirmed != null && confirmed) {
      await GetIt.I<MemoDao>().deleteMemo(memo);
      _refreshMemoList();
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
        content: Text('已删除 "${memo.title}"'),
        duration: const Duration(seconds: 2),
      ));
    }
  }

添加备忘录的页面只需要更改保存备忘录的方法,而且因为不需要再对时间做转换,方法更为简洁。

Future<void> _saveMemo(BuildContext context) async {
  var memo = Memo(
      title: _title,
      content: _content,
      createdTime: DateTime.now(),
      modifiedTime: DateTime.now(),
      tags: _tags);
  // 保存备忘录
  await GetIt.I<MemoDao>().insertMemo(memo);
}

编辑备忘录页面也类似,调用 updateMemo 方法即可完成保存。

Future<void> _saveMemo(BuildContext context) async {
  widget.memo.title = _title;
  widget.memo.content = _content;
  widget.memo.modifiedTime = DateTime.now();
  // 保存备忘录
  await GetIt.I<MemoDao>().updateMemo(widget.memo);
}

总结

代码已经提交到:本地存储相关代码,注意如果更改了 ORM 相关的类,需要运行下面的命令重新生成代码。

flutter packages pub run build_runner build

可以看到,通过 floor 这样的 ORM 框架可以让整个本地数据库管理的代码更为简洁,复用性更高。如果说是本地数据存储比较复杂的,推荐使用 ORM 框架来管理。

到此这篇关于Flutter利用ORM框架简化本地数据库管理详解的文章就介绍到这了,更多相关Flutter ORM框架简化本地数据库内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Android仿美团分类下拉菜单实例代码

    Android仿美团分类下拉菜单实例代码

    这篇文章主要为大家详细介绍了Android仿美团分类下拉菜单实例代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-05-05
  • Android中Glide加载库的图片缓存配置究极指南

    Android中Glide加载库的图片缓存配置究极指南

    这篇文章主要介绍了Android中Glide加载库的图片缓存配置究极指南,Glide是一款高人气的安卓多媒体资源加载库,本文对其缓存设置和优化作了详细讲解,需要的朋友可以参考下
    2016-04-04
  • [Android] 通过GridView仿微信动态添加本地图片示例代码

    [Android] 通过GridView仿微信动态添加本地图片示例代码

    本篇文章主要介绍了[Android] 通过GridView仿微信动态添加本地图片示例代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-01-01
  • 使用Android自定义控件实现滑动解锁九宫格

    使用Android自定义控件实现滑动解锁九宫格

    最近由于Android项目需要,要求做一个类似于支付宝的九宫格解锁组件,下面小编给大家分享了具体实现代码,需要的朋友可以参考下
    2015-10-10
  • 详解Android中Service AIDL的使用

    详解Android中Service AIDL的使用

    作为一名Android开发人员,如果没听过Service,那就有点说不过去了啊,Service是Android四大组件之一,它是不依赖于用户界面的,就是因为Service不依赖与用户界面,所以我们常常用于进行一些耗时的操作,比如:下载数据等;本文将详细介绍Android中Service AIDL的使用。
    2021-06-06
  • Android开发之在程序中时时获取logcat日志信息的方法(附demo源码下载)

    Android开发之在程序中时时获取logcat日志信息的方法(附demo源码下载)

    这篇文章主要介绍了Android开发之在程序中时时获取logcat日志信息的方法,结合实例形式较为详细的分析了实时获取logcat日志的原理、步骤与相关实现技巧,并附带相应的demo源码供读者下载参考,需要的朋友可以参考下
    2016-02-02
  • Android启动页优化之实现应用秒开

    Android启动页优化之实现应用秒开

    现在很多应用都会在进入主界面之前,添加一个启动页,然后加入几秒钟的广告,我觉得这个不能算是 “真正意义上的 “ 启动页,应该叫广告页。
    2021-05-05
  • Android Studio绑定下拉框数据详解

    Android Studio绑定下拉框数据详解

    这篇文章主要为大家详细介绍了Android Studio绑定下拉框数据,Android Studio绑定网络JSON数据,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-10-10
  • Android中SurfaceView用法简单实例

    Android中SurfaceView用法简单实例

    这篇文章主要介绍了Android中SurfaceView用法,以一个简单的图形绘制及改变位置实现方法分析了SurfaceView的使用技巧,需要的朋友可以参考下
    2015-10-10
  • Android Scroll滑动效果实例

    Android Scroll滑动效果实例

    这篇文章主要为大家分享了Android Scroll滑动效果实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-04-04

最新评论