版權聲明:本文為作者原創書籍。轉載請注明作者和出處,未經授權,嚴禁私自轉載,侵權必究!!!
情感語錄: 生活本來就是一場惡戰,給止疼藥也好,給巴掌也罷,最終都是要單槍匹馬練就自身膽量,誰也不例外。
歡迎來到本章節,上一章節介紹了用shared_preferences和文件操作實現持久化
,知識點回顧 戳這里 Flutter基礎第十二章
承繼上一篇文章的伏筆,本章知識點主要介紹 Flutter 的數據持久化之數據庫。很多時候我們的數據并不是單一結構且存在關系性,并需對大批量數據有增、刪、改、查 操作時那么對數據庫的使用那就是必不可少了。
本章簡要:
1、sqflite 數據庫
2、數據庫的 CRUD操作
3、數據庫的事務和批處理
一、sqflite 數據庫
sqflite
數據庫是一款輕量級的關系型數據庫,如同 iOS和Android中的SQLite。sqflite地址:https://github.com/tekartik/sqflite
sqflite 插件引入
1、在pubspec.yaml
文件中添加依賴
dependencies:
fluttertoast: ^3.0.3
flutter:
sdk: flutter
#添加持久化插件 sp
shared_preferences: ^0.5.3+1
#添加文件庫
path_provider: ^1.2.0
#添加數據庫
sqflite: ^1.1.6
本人使用的當前最新版本 1.1.6
,讀者想體驗最新版本請在使用時參看最新版本號進行替換。
2、安裝依賴庫
執行 flutter packages get
命令;AS 開發工具直接右上角 packages get
也可。
3、在需要使用的地方導包引入
import 'package:sqflite/sqflite.dart';
sqflite 支持的數據類型
存儲類 描述
NULL 值是一個 NULL 值。
INTEGER 值是一個帶符號的整數,-2^63 到 2^63 - 1
REAL 值是一個數字類型,dart中的 num
TEXT 值是一個文本字符串,dart中的 String
BLOB 值是一個 blob 數據,dart中的 Uint8List 或者 List<int>
可以看出 sqflite 中支持的數據類型比較少,比如 bool 、DateTime
都是不支持的;開發中需要 bool 類型可以使用 INTEGER的 0和1來表示,DateTime 類型可以使用 時間戳 字符串。
sqflite 中常用的 API:
getDatabasesPath() :
獲取數據庫位置,在Android上,它通常是data/data/包名/databases;在iOS上,它是Documents目錄。
join("參數1", "參數2"):
該方法表示創建數據庫, 參數1: getDatabasesPath() 獲取到的數據庫存放路徑,參數2: 數據庫的名字,如:User.db
openDatabase():
該方法表示打開數據,具體有以下幾個重要參數
Future<Database> openDatabase(
String path,
{int version,
OnDatabaseConfigureFn onConfigure,
OnDatabaseCreateFn onCreate,
OnDatabaseVersionChangeFn onUpgrade,
OnDatabaseVersionChangeFn onDowngrade,
OnDatabaseOpenFn onOpen,
bool readOnly = false,
bool singleInstance = true})
path:
必傳參數,join() 創建數據庫后的返回值。
version:
當前的版本號。
onConfigure:
數據庫的相關配置
onCreate:
創建表的方法
onUpgrade、onDowngrade:
數據庫版本的升降級
readOnly:
是否為只讀方式打開。
CRUD 相關API:
1、插入數據的兩種方式:
Future<int> insert(String table, Map<String, dynamic> values,
{String nullColumnHack, ConflictAlgorithm conflictAlgorithm});
Future<int> rawInsert(String sql, [List<dynamic> arguments]);
insert()
方法第一個參數為操作的表名,第二個參數 map 中是想要添加的字段名和對應字段值。
rawInsert()
方法第一個參數為一條插入sql 語句,語句中?
作為占位符,通過第二個參數填充占位數據。
2、查詢數據的兩種方式:
Future<List<Map<String, dynamic>>> query(String table,
{bool distinct,
List<String> columns,
String where,
List<dynamic> whereArgs,
String groupBy,
String having,
String orderBy,
int limit,
int offset});
Future<List<Map<String, dynamic>>> rawQuery(String sql,
[List<dynamic> arguments]);
2.1 query() 方式查詢的參數介紹:
參數 描述
table 表名
distinct 是否去重
columns 查詢字段集合
where WHERE子句(使用?作為占位符)
whereArgs WHERE子句占位符參數值
groupBy 結果集分組
having 結合groupBy使用過濾結果集
orderBy 排序方式
limit 查詢的條數
offset 查詢的偏移位
2.2 rawQuery()
方法第一個參數為一條查詢sql語句,使用 ?
作為占位符,通過第二個參數填充數據。
3. 修改數據的兩種方式
Future<int> update(String table, Map<String, dynamic> values,
{String where,
List<dynamic> whereArgs,
ConflictAlgorithm conflictAlgorithm});
Future<int> rawUpdate(String sql, [List<dynamic> arguments]);
update()
方法第一個參數為操作的表名,第二個參數為修改的字段和對應值,后邊的可選參數依次表示WHERE子句、WHERE子句占位符參數值、發生沖突時的操作算法(包括回滾、終止、忽略等)。
rawUpdate()
方法第一個參數為一條更新sql語句,使用?
作為占位符,通過第二個參數填充數據。
4. 刪除數據的兩種方式
Future<int> delete(String table, {String where, List<dynamic> whereArgs});
Future<int> rawDelete(String sql, [List<dynamic> arguments]);
delete()
方法第一個參數為操作的表名,后邊的可選參數依次表示WHERE子句、WHERE子句占位符參數值。
rawDelete()
方法第一個參數為一條刪除sql語句,使用?
作為占位符,通過第二個參數填充數據。
close()
關閉數據庫。
transaction()
開啟事務。
batch()
獲取批處理對象。
可以看到 Flutter 在 增、刪、改、查
中都提供了兩套方法使用,更傾向于寫 sql 語句的客官們使用 rawxxx
方式就比較好;但我還是喜歡通過參數拼接組合的方式,可以屏蔽很多細節問題。
二、數據庫的 CRUD
上面對 sqflite 引入和相關 API 都做了介紹,如果還是不清楚怎么使用,接下來就通過 案例的方式去學習。為了不重復的敘述,這里需要提前先做兩個準備工作:① 新建一個用戶實體類(方便數據操作)。 ② 建庫、建表。
用戶實體類
class UserInfo{
String name;
String password;
int age;
UserInfo({this.name,this.age,this.password});
UserInfo.toUser(Map<String, dynamic> json) {
name = json['name'];
age = json['age'];
password = json['password'];
}
Map<String, dynamic> toMap() {
Map<String, dynamic> data = new Map<String, dynamic>();
data['name'] = this.name;
data['age'] = this.age;
data['password'] = this.password;
return data;
}
}
建庫、建表
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'UserInfo.dart';
class SqlUserHelper{
//數據庫
final String dataBaseName = "User.db";
//數據表
final String tableName = "USER_TABLE";
//以下是表中的列名
final String columnId = 'id';
final String name = 'name';
final String password = 'password';
final String age ="age";
// 靜態私有成員
static SqlUserHelper _instance;
Database _database;
// 私有構造函數
SqlUserHelper._() {
initDb();
}
//私有訪問點
static SqlUserHelper helperInstance() {
if (_instance == null) {
_instance = SqlUserHelper._();
}
return _instance;
}
//初始化數據庫
void initDb() async {
String databasesPath = await getDatabasesPath();
String path = join(databasesPath, dataBaseName );
// openDatabase 指定是數據庫路徑,版本號,和執行表的創建
_database = await openDatabase(path, version: 1, onCreate: _onCreate);
}
//創建UserInfo表
void _onCreate(Database db, int newVersion) async {
await db.execute('CREATE TABLE $tableName($columnId INTEGER PRIMARY KEY AUTOINCREMENT, $name TEXT, $password TEXT, $age INTEGER)');
}
///關閉數據庫
Future<void> close() async {
return _database.close();
}
}
UserInfo 實體比較簡單只聲明了三個屬性而已,另外兩個方法只是為了插入數據和查詢時方便觀看而已。SqlUserHelper 是一個單例模式的用戶數據庫操作類( 單例模式不清楚的請看上一章節)。在使用完數據庫時一定要及時關閉數據庫
,避免造成不必要的資源浪費。不持續叨逼叨.... 代碼中已有詳細注釋。
1、添加數據
首先向數據庫插入幾條數據,方便后面查詢使用。在 SqlUserHelper 中添加插入數據的方法。
插入數據的兩種方式
/// insert第一種
Future<int> insert(UserInfo userInfo){
return _database.insert(tableName, userInfo.toMap());
}
/// insert第二種
Future<int> rawInsert(UserInfo userInfo){
return _database.rawInsert("INSERT INTO $tableName ($name,$password,$age) VALUES(?, ?, ?)", [userInfo.name,userInfo.password,userInfo.age]);
}
實例代碼:
//構建一個user 對象
UserInfo user = UserInfo(name: userName,password: userPass,age: age);
//向數據庫插入該條數據
sqlUserHelper.insert(user).then((value){
print("the last insert id $value");
});
//構建一個user 對象
UserInfo user = UserInfo(name: userName,password: userPass,age: age);
//向數據庫插入該條數據
sqlUserHelper.rawInsert(user).then((value){
print("the last rawInsert id $value");
});
無論是通過 insert 還是 rawInsert
方式插入數據,只要成功插入就會返回最后一條插入的記錄 ID 回來。
實操演示:
控制臺輸出:
I/flutter: the last insert id 1
I/flutter: the last rawInsert id 2
控制臺打印出了通過兩種方式插入數據后的記錄 ID,證明此時已經成功插入了兩條一樣的數據到數據庫。
2、查詢數據
查詢數據的兩種方式
///第一種 query
Future<List<Map>> query() async {
List<Map> maps = await _database.query(tableName);
if (maps.isNotEmpty) {
return maps;
}
return null;
}
///第二種 query
Future<List<Map>> rawQuery() async {
List<Map> maps = await _database.rawQuery("SELECT * FROM $tableName");
if (maps.isNotEmpty) {
return maps;
}
return null;
}
實例代碼:
///查詢全部
sqlUserHelper.query().then((value){
print("the query info ${value.toString()}");
});
///查詢全部
sqlUserHelper.rawQuery().then((value){
print("the rawQuery info ${value.toString()}");
});
兩種方式都是查詢整個 user表中的全部信息,并將結果打印輸出。下面來查詢上面添加的數據。
實操演示:
可以看到在我添加數據后,分別點了 query 和 rawQuery
方式查詢,控制臺輸出結果如下:
I/flutter: the query info [{id: 1, name: zhengzaihong, password: 123456, age: 18}, {id: 2, name: zhengzaihong, password: 123456, age: 18}]
I/flutter: the rawQuery info [{id: 1, name: zhengzaihong, password: 123456, age: 18}, {id: 2, name: zhengzaihong, password: 123456, age: 18}]
兩種方式都查詢出了 全部的信息,實際開發中一般都是條件查詢,這里只是簡單除暴的演示而已。
3、修改數據
上面有了兩條一樣的數據,接下來方便我們做修改,然后再次查詢輸出結果看是否正確修改。
修改數據的兩種方式
///第一種 update
Future<int> update(UserInfo user,int id) async {
return await _database.update(tableName,user.toMap(),where: '$columnId = ?', whereArgs: [id]);
}
///第二種 rawUpdate
Future<int> rawUpdate(UserInfo user,int id) async {
return await _database.rawUpdate("UPDATE $tableName SET $name = ? WHERE $columnId = ? ",[user.name,id]);
}
實例代碼:
//構建一個user 對象 根據 ID 修改
UserInfo user = UserInfo(name: userName,password: userPass,age: age);
sqlUserHelper.update(user, 1).then((value){
print("the update info ${value.toString()}");
});
UserInfo user = UserInfo(name: userName,password: userPass,age: age);
sqlUserHelper.rawUpdate(user, 2).then((value){
print("the rawUpdate info ${value.toString()}");
});
實操演示
控制臺輸出:
I/flutter: the update info 1
I/flutter: the rawUpdate info 1
I/flutter: the query info [{id: 1, name: zzh, password: 123456, age: 20}, {id: 2, name: LQ, password: 123456, age: 18}]
I/flutter: the rawQuery info [{id: 1, name: zzh, password: 123456, age: 20}, {id: 2, name: LQ, password: 123456, age: 18}]
從輸出可以看出 無論是 update 還是 rawUpdate
方式修改數據都打印出有一行受影響,這表示數據被成功修改了。update 方式把輸入框中的數據重新帶入把 id =1 的這條數據全部重新賦值一遍,實現了數據修改;而 rawUpdate 方式只在 sql 語句中加入了對名字的修改,可以看到在輸入框填寫了年齡值也并未修改成功。
4、刪除數據
刪除數據的兩種方式
///第一種 delete 根據id刪除
Future<int> delete(int id) async {
return await _database.delete(tableName,
where: "$columnId = ?", whereArgs: [id]);
}
///第二種 delete 根據id刪除
Future<int> rawDelete(int id) async {
return await _database.rawDelete("DELETE FROM $tableName WHERE $columnId = ?", [id]);
}
實例代碼:
/// 根據 id 刪除
sqlUserHelper.delete(1).then((value){
print("the delete info ${value.toString()}");
});
/// 根據 id 刪除
sqlUserHelper.rawDelete( 2).then((value){
print("the rawDelete info ${value.toString()}");
});
實操演示:
在我點擊兩次刪除按鈕后,再次查詢結果如下:
I/flutter: the delete info 1
I/flutter: the rawDelete info 1
I/flutter: the query info null
查詢結果輸出為 null
表示數據庫中已經沒有開始存入的兩條數據了,證明兩次刪除都是成功的。
三、數據庫的事務和批處理
1、事務
sqflite同時支持事務,所謂事務,它是一個操作序列,這些操作要么都執行,要么都不執行,即:執行單個邏輯功能的一組指令或操作稱為事務。
下面通過事務來添加兩條數據:
/// 開啟事務添加
Future<bool> transactionInsert(UserInfo userInfo1, UserInfo userInfo2) async {
return await _database.transaction((Transaction transaction) async {
int id1 = await transaction.insert(tableName, userInfo1.toMap());
int id2 = await transaction.insert(tableName, userInfo2.toMap());
return id1 != null && id2 != null;
});
}
實例代碼:
//構建兩個用戶對象
UserInfo user1 = UserInfo(name: "zhengxian",password: "123456",age: 18);
UserInfo user2 = UserInfo(name: "zzh",password: "123456",age: 20);
sqlUserHelper.transactionInsert(user1, user2).then((value){
print("transaction result: $value");
});
這里就不貼圖了,下面直接來看通過事務來添加的兩條數據和查詢結果,控制臺輸出:
I/flutter: transaction result: true
I/flutter: the query info [{id: 1, name: zhengxian, password: 123456, age: 18}, {id: 2, name: zzh, password: 123456, age: 20}]
結果返回 ture ,查詢結果也是上面構建的兩條數據信息,說明都是成功的。
2、批處理
sqflite支持批處理操作,批處理指的是一次操作中執行多條SQL語句,批處理相比于一次一次執行效率會提高很多。
下面通過批處理來新增一條數據和修改一條數據
/// 批處理
Future<List<dynamic>> batch(UserInfo user,UserInfo user2) async {
Batch batch = _database.batch();
//先添加一條數據
batch.insert(tableName, user.toMap());
//修改 id 為1的值
batch.update(tableName,user2.toMap(),where: '$columnId = ?', whereArgs: [1]);
return batch.commit();
}
實例代碼:
UserInfo user1 = UserInfo(name: "zhengxian",password: "123456",age: 18);
UserInfo user2 = UserInfo(name: "LiShi",password: "123456",age: 55);
sqlUserHelper.batch( user1,user2).then((value){
print("the batch info ${value.toString()}");
});
控制臺打印批處理和查詢結果:
I/flutter: the batch info [3, 1]
I/flutter: the query info [{id: 1, name: LiShi, password: 123456, age: 55}, {id: 2, name: zzh, password: 123456, age: 20},
{id: 3, name: zhengxian, password: 123456, age: 18}]
[3, 1]
兩個數字分別表示最后一次插入數據后的記錄 id,和有一行修改受影響。通過查詢我可以看出,數據確實成功插入到了數據庫,且 id=1 的這條數據被成功修改了。
下面貼出下數據庫操作的源碼,界面源碼就不貼了,代碼全部詳情會在文末給出:
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'UserInfo.dart';
class SqlUserHelper{
//數據庫
final String dataBaseName = "User.db";
//數據表
final String tableName = "USER_TABLE";
//以下是表中的列名
final String columnId = 'id';
final String name = 'name';
final String password = 'password';
final String age ="age";
// 靜態私有成員
static SqlUserHelper _instance;
Database _database;
// 私有構造函數
SqlUserHelper._() {
initDb();
}
//私有訪問點
static SqlUserHelper helperInstance() {
if (_instance == null) {
_instance = SqlUserHelper._();
}
return _instance;
}
//初始化數據庫
void initDb() async {
String databasesPath = await getDatabasesPath();
String path = join(databasesPath, dataBaseName );
// openDatabase 指定是數據庫路徑,版本號,和執行表的創建
_database = await openDatabase(path, version: 1, onCreate: _onCreate);
}
//創建UserInfo表
void _onCreate(Database db, int newVersion) async {
await db.execute('CREATE TABLE $tableName($columnId INTEGER PRIMARY KEY AUTOINCREMENT, $name TEXT, $password TEXT, $age INTEGER)');
}
/// 插入數據的兩種方式
///
/// insert第一種
Future<int> insert(UserInfo userInfo){
print("插入數據:${ userInfo.toMap()}");
return _database.insert(tableName, userInfo.toMap());
}
/// insert第二種
Future<int> rawInsert(UserInfo userInfo){
return _database.rawInsert("INSERT INTO $tableName ($name,$password,$age) VALUES(?, ?, ?)", [userInfo.name,userInfo.password,userInfo.age]);
}
///查詢數據的兩種方式
///第一種 query
Future<List<Map>> query() async {
List<Map> maps = await _database.query(tableName);
if (maps.isNotEmpty) {
return maps;
}
return null;
}
///第二種 query
Future<List<Map>> rawQuery() async {
List<Map> maps = await _database.rawQuery("SELECT * FROM $tableName");
if (maps.isNotEmpty) {
return maps;
}
return null;
}
///修改數據的兩種方式
///第一種 update
Future<int> update(UserInfo user,int id) async {
return await _database.update(tableName,user.toMap(),where: '$columnId = ?', whereArgs: [id]);
}
///第二種 rawUpdate
Future<int> rawUpdate(UserInfo user,int id) async {
return await _database.rawUpdate("UPDATE $tableName SET $name = ? WHERE $columnId = ? ",[user.name,id]);
}
///刪除數據的兩種方式
///
///第一種 delete 根據id刪除
Future<int> delete(int id) async {
return await _database.delete(tableName,
where: "$columnId = ?", whereArgs: [id]);
}
///第二種 delete 根據id刪除
Future<int> rawDelete(int id) async {
return await _database.rawDelete("DELETE FROM $tableName WHERE $columnId = ?", [id]);
}
/// 開啟事務添加
Future<bool> transactionInsert(UserInfo userInfo1, UserInfo userInfo2) async {
return await _database.transaction((Transaction transaction) async {
int id1 = await transaction.insert(tableName, userInfo1.toMap());
int id2 = await transaction.insert(tableName, userInfo2.toMap());
return id1 != null && id2 != null;
});
}
/// 批處理
Future<List<dynamic>> batch(UserInfo user,UserInfo user2) async {
Batch batch = _database.batch();
//先添加一條數據
batch.insert(tableName, user.toMap());
//修改 id 為1的值
batch.update(tableName,user2.toMap(),where: '$columnId = ?', whereArgs: [1]);
return batch.commit();
}
///關閉數據庫
Future<void> close() async {
return _database.close();
}
}
至此數據庫的一些用法也就介紹完了,加上前一篇文章中介紹的 ShardPreferences 和 IO 文件讀寫方式總共介紹了三種方式可以實現數據持久化。ShardPreferences多用于一些簡單無關系性的數據存儲;IO 文件的讀寫其實在開發中很少使用來存儲數據,往往都是做一些文檔文件才會使用。
好了本章節到此結束,又到了說再見的時候了,如果你喜歡請留下你的小紅星,創作真心也不容易;你們的支持才是創作的動力,如有錯誤,請熱心的你留言指正, 謝謝大家觀看,下章再會 O(∩_∩)O
實例源碼地址:https://github.com/zhengzaihong/flutter_learn/tree/master/lib/page/database