Flutter利用ORM框架簡(jiǎn)化本地?cái)?shù)據(jù)庫管理詳解
前言
前面兩篇我們介紹了使用 sqflite
管理 Flutter 本地 SQLite
數(shù)據(jù)庫。使用 sqflite
相對(duì)來說還是有點(diǎn)復(fù)雜,比如需要自己寫數(shù)據(jù)庫數(shù)據(jù)到實(shí)體類對(duì)象的轉(zhuǎn)換,遇到數(shù)據(jù)不兼容的時(shí)候需要手動(dòng)轉(zhuǎn)換,增加了不少繁瑣的代碼。本篇我們就來介紹一個(gè) ORM 框架,來簡(jiǎn)化數(shù)據(jù)庫的管理,這個(gè)框架就是 floor
。
floor 簡(jiǎn)介
floor
是基于 sqflite
的一個(gè)輕量級(jí)的 ORM 框架,通過注解和代碼生成可以將數(shù)據(jù)庫數(shù)據(jù)直接映射為實(shí)體類對(duì)象。floor
內(nèi)置了很多操作數(shù)據(jù)庫的方法,比如增刪改查,讓我們快速接入數(shù)據(jù)庫。同時(shí),也可以在注解中編寫 SQL來實(shí)現(xiàn)復(fù)雜的數(shù)據(jù)庫查詢,比如 IN
查詢、數(shù)據(jù)統(tǒng)計(jì)等等。通過注解和代碼生成能夠減少大量手寫代碼,提高我們的開發(fā)效率和代碼的可維護(hù)性。floor 的文檔非常完善,大家可以到github閱讀相關(guān)的文檔:pinchbv.github.io/floor/getti…。 floor 需要引入的開發(fā)依賴如下,都是用于基于注解生成代碼。
dev_dependencies: flutter_test: sdk: flutter # ... floor_generator: ^1.4.1 build_runner: ^2.3.3
接下來我們就以之前的備忘錄為例,來看看使用 floor
后的改善。
ORM 映射
我們之前的備忘錄類 Memo
需要自己編寫 fromJson
和 toJson
方法來實(shí)現(xiàn)數(shù)據(jù)庫數(shù)據(jù)到實(shí)體類對(duì)象的轉(zhuǎn)換。此外,遇到 SQLite 不支持的數(shù)據(jù)類型(如 DateTime
和 List<String>
)時(shí),還需要處理轉(zhuǎn)換代碼。我們來看 floor
如何處理。 floor
將數(shù)據(jù)庫操作分為實(shí)體類和 DAO,實(shí)體類與數(shù)據(jù)庫的映射通過注解完成。例如我們的 Memo
類,調(diào)整后的代碼如下所示。
@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
:表示這是一個(gè)實(shí)體類,會(huì)和數(shù)據(jù)庫的某個(gè)數(shù)據(jù)表映射,默認(rèn)表名就是類名。如果要手動(dòng)指定表名,可以使用@Entity(tableName: tableName)
通過tableName
指定數(shù)據(jù)表名稱。floor 會(huì)自動(dòng)根據(jù)@entity
注解生成創(chuàng)建數(shù)據(jù)表的 SQL 語句。@primaryKey
:表示字段為主鍵,如果需要使用自增主鍵,可以使用@PrimaryKey(autoGenerate: true)
。@ColumnInfo(name: name)
:設(shè)置實(shí)體類成員屬性和數(shù)據(jù)表字段的映射關(guān)系,默認(rèn) floor 使用的數(shù)據(jù)表字段名稱和類成員屬性名稱一致,如果需要指定數(shù)據(jù)表字段名,就可以使用這個(gè)注解。@ignore
:忽略某個(gè)成員屬性,即該屬性不產(chǎn)生相應(yīng)的數(shù)據(jù)表字段。注意,通過 get 方法產(chǎn)生的計(jì)算屬性默認(rèn)就會(huì)被忽略,例如長(zhǎng)方形面積double get area => width * height
。
DAO 用于從數(shù)據(jù)庫查詢數(shù)據(jù)并轉(zhuǎn)換為實(shí)體類對(duì)象,從數(shù)據(jù)庫查詢數(shù)據(jù)和轉(zhuǎn)換的代碼通過注解直接生成。DAO 提供了基礎(chǔ)的插入、更新和刪除方法,這些方法可以通過注解@insert
、@update
和@delete
完成,不需要編寫 SQL。 同時(shí),對(duì)于插入和更新可以設(shè)置沖突策略,策略可以是中止(abort)、回滾(rollback)、替換(replace)、忽略(ignore)、失敗(fail)。其中除了替換以外,其他都是和數(shù)據(jù)庫事務(wù)有關(guān)。
@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); }
轉(zhuǎn)換器
使用 floor
可以統(tǒng)一 Dart 數(shù)據(jù)類型到 SQLite 字段的轉(zhuǎn)換方式。通過定義不同的類型轉(zhuǎn)換器TypeConverter
實(shí)現(xiàn)數(shù)據(jù)庫和Dart
數(shù)據(jù)類型的轉(zhuǎn)換,從而避免了每個(gè)實(shí)體類都要單獨(dú)編寫轉(zhuǎn)換代碼。比如我們?cè)趥渫浻玫搅藘蓚€(gè)類型 DateTime
和 List<String>
就定義了對(duì)應(yīng)的轉(zhuǎn)換器。
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; } }
使用轉(zhuǎn)換器只需要在定義數(shù)據(jù)庫FloorDatabase
的抽象類的時(shí)候引入到注解@TypeConverters
就可以了。
@TypeConverters([StringListConverter, DateTimeConverter]) @Database(version: 1, entities: [Memo]) abstract class MemoDatabase extends FloorDatabase { MemoDao get memoDao; }
代碼改造
通常來說 DAO 對(duì)象會(huì)在很多地方共用,適合使用單例方式來構(gòu)造。這里我們?cè)贏pp啟動(dòng)的時(shí)候就使用 GetIt來實(shí)現(xiàn)MemoDao 的單例注冊(cè)。
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()); }
這里調(diào)用ensureInitialized
這個(gè)方法是保證 Flutter 和原生交互的部分已經(jīng)完成,因?yàn)樵?sqflite 中需要使用原生的文件存儲(chǔ)。 備忘錄列表的代碼改造涉及數(shù)據(jù)操作的有兩處,分別是列表刷新和刪除備忘錄。列表模糊搜索時(shí)需要自己組裝模糊搜索的字符,比如我們這里使用了百分號(hào)將搜索關(guān)鍵詞包裹實(shí)現(xiàn)任意匹配。刪除備忘錄需要根據(jù)是否有搜索調(diào)用不同的方法,這是因?yàn)閷?duì)應(yīng)的 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; }); }
刪除就非常簡(jiǎn)單了,直接調(diào)用刪除方法就好了。
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), )); } }
添加備忘錄的頁面只需要更改保存?zhèn)渫浀姆椒ǎ乙驗(yàn)椴恍枰賹?duì)時(shí)間做轉(zhuǎn)換,方法更為簡(jiǎn)潔。
Future<void> _saveMemo(BuildContext context) async { var memo = Memo( title: _title, content: _content, createdTime: DateTime.now(), modifiedTime: DateTime.now(), tags: _tags); // 保存?zhèn)渫? await GetIt.I<MemoDao>().insertMemo(memo); }
編輯備忘錄頁面也類似,調(diào)用 updateMemo
方法即可完成保存。
Future<void> _saveMemo(BuildContext context) async { widget.memo.title = _title; widget.memo.content = _content; widget.memo.modifiedTime = DateTime.now(); // 保存?zhèn)渫? await GetIt.I<MemoDao>().updateMemo(widget.memo); }
總結(jié)
代碼已經(jīng)提交到:本地存儲(chǔ)相關(guān)代碼,注意如果更改了 ORM 相關(guān)的類,需要運(yùn)行下面的命令重新生成代碼。
flutter packages pub run build_runner build
可以看到,通過 floor
這樣的 ORM 框架可以讓整個(gè)本地?cái)?shù)據(jù)庫管理的代碼更為簡(jiǎn)潔,復(fù)用性更高。如果說是本地?cái)?shù)據(jù)存儲(chǔ)比較復(fù)雜的,推薦使用 ORM 框架來管理。
到此這篇關(guān)于Flutter利用ORM框架簡(jiǎn)化本地?cái)?shù)據(jù)庫管理詳解的文章就介紹到這了,更多相關(guān)Flutter ORM框架簡(jiǎn)化本地?cái)?shù)據(jù)庫內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android仿美團(tuán)分類下拉菜單實(shí)例代碼
這篇文章主要為大家詳細(xì)介紹了Android仿美團(tuán)分類下拉菜單實(shí)例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05[Android] 通過GridView仿微信動(dòng)態(tài)添加本地圖片示例代碼
本篇文章主要介紹了[Android] 通過GridView仿微信動(dòng)態(tài)添加本地圖片示例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-01-01使用Android自定義控件實(shí)現(xiàn)滑動(dòng)解鎖九宮格
最近由于Android項(xiàng)目需要,要求做一個(gè)類似于支付寶的九宮格解鎖組件,下面小編給大家分享了具體實(shí)現(xiàn)代碼,需要的朋友可以參考下2015-10-10Android開發(fā)之在程序中時(shí)時(shí)獲取logcat日志信息的方法(附demo源碼下載)
這篇文章主要介紹了Android開發(fā)之在程序中時(shí)時(shí)獲取logcat日志信息的方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了實(shí)時(shí)獲取logcat日志的原理、步驟與相關(guān)實(shí)現(xiàn)技巧,并附帶相應(yīng)的demo源碼供讀者下載參考,需要的朋友可以參考下2016-02-02Android啟動(dòng)頁優(yōu)化之實(shí)現(xiàn)應(yīng)用秒開
現(xiàn)在很多應(yīng)用都會(huì)在進(jìn)入主界面之前,添加一個(gè)啟動(dòng)頁,然后加入幾秒鐘的廣告,我覺得這個(gè)不能算是 “真正意義上的 “ 啟動(dòng)頁,應(yīng)該叫廣告頁。2021-05-05Android Studio綁定下拉框數(shù)據(jù)詳解
這篇文章主要為大家詳細(xì)介紹了Android Studio綁定下拉框數(shù)據(jù),Android Studio綁定網(wǎng)絡(luò)JSON數(shù)據(jù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10Android中SurfaceView用法簡(jiǎn)單實(shí)例
這篇文章主要介紹了Android中SurfaceView用法,以一個(gè)簡(jiǎn)單的圖形繪制及改變位置實(shí)現(xiàn)方法分析了SurfaceView的使用技巧,需要的朋友可以參考下2015-10-10Android Scroll滑動(dòng)效果實(shí)例
這篇文章主要為大家分享了Android Scroll滑動(dòng)效果實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-04-04