最近一段时间,在学 Flutter,打算把自己写过的项目 HOO 用 Flutter 实现。

里面用到了一些数据库的知识,正好这周我在组内也是分享的SQLite,所以我们就来聊一聊 SQLite 的基础,以及怎么在 Flutter 中去使用数据库。

学习使我快乐

目录

目录

一、SQLite 基础

1. SQLite 定义

SQLite 是一个进程内的库,实现了自给自足、无服务器的、零配置的、事务性的SQL数据库引擎。

2. SQLite 特点

从上面的定义中,我们已经看到 SQLite 的一些特点,我在这里做一下详细的解释:

  • 嵌入式数据库:与MySQL、Oracle 这类数据库服务器相比,SQLite 无需单独的服务器进程或者操作系统,并且可以和应用的进程处在同一个进程。
  • 无需配置:不需要进行一大堆的配置。
  • 存储在磁盘文件:可以跨平台的存储在一个磁盘文件中。
  • 轻量级:完全配置时小于400kb,省略可选功能时低于250kb。
  • 无需外部依赖。
  • 跨平台:可以在 Unix 和 Windows 中运行。

所以,Android 和 ios 设备一般都会采用 SQLite 作为应用的本地数据库。

二、SQL 语法

SQL 语法是比较重要的,因为无论是什么基于 SQLite 的第三方库,它们在进行数据库操作的时候,最后都会转化为 SQL 语句,这个时候即使遇到 bug,我们也能够迎刃而解。

推荐看一下基础教程:SQLite教程

数据库的语言学习可以分为三个部分:

SQL语言

1. 创建数据库

在进行学习基础语言之前,我们需要了解一下如何创建一个数据库,它通常对应着一个 .db 文件。

如果是在项目中,我们通常会在代码中进行配置,在进入 App 中就会生成对应的 .db 文件。

如果是在电脑中,我们以 mac 为例,需要在系统中安装 SQLite,成功以后,输入命令:

sqlite3 数据库名

就可以成功创建一个数据库。

2. 数据库定义语言

先说一下我们的目标,要建一个用户收藏商品的关系,思考一下,我们要建立几张表?

没错需要建立三张表,分别是 User(用户表)、Goods(商品表) 和 Fav_Goods(收藏表)。UserGoods 是独立的表,Fav_Goods 则要存放 其他两张表的主键。

2.1 CREATE

数据库在之前已经建立完,下一步就是建立表,对应建立表的语法就是 CREATE TABLE,有几个点需要注意。

基础的存储类型只有五种:

存储类型 解释
NULL 值是一个 NULL 值
INTEGER 值是一个带符号的整数,值支持 1,2,3,4,6,8个字节
REAL 浮点数
TEXT 文本字符串
BLOB 二进制大对象,用来存储图片,视频等

一些常用的关键字:

关键字 解释
PRIMARY KEY 主键
AUROINCREMENT 主键自增长,一般需要将主键设置为 INTEGER
FOREIGN KEY 外键

详细的 USER表、GOODS表和 FAV_GOODS 表创建语句:

# main 对应数据库名称CREATE TABLE main.USER(   id INTEGER PRIMARY KEY AUTOINCREMENT,   name TEXT,   account TEXT,   pwd TEXT,   age INT);

CREATE TABLE main.GOODS(   id INTEGER PRIMARY KEY AUTOINCREMENT,   name TEXT,   price REAL,   category TEXT,   brand TEXT);

CREATE TABLE main.FAV_GOODS(   id INTEGER PRIMARY KEY AUTOINCREMENT,   date STRING,   user_id INTEGER,   good_id INTEGER,   FOREIGN KEY(user_id) REFERENCES STUDENT(id),   FOREIGN KEY(good_id) REFERENCES GOODS(id));

2.2 ALTER 和 DROP

DROP 代表删除表的操作,具体的命令是:

DROP TABLE 数据库名.表名;

ALTER 代表修改表的操作,支持修改表名和在已有的表中增加新的列。具体的命令:

# 修改表名ALTER TABLE database_name.table_name RENAME TO new_table_name;

# 增加新列ALTER TABLE database_name.table_name ADD COLUMN 列名 存储类型;

3. 数据库操作语言

3.1 INSERT

INSERT 对应着增加数据记录,需要注意的是插入的字段和列名要一一对应:

# 新增用户 # name 对应 "lisi", account 对应 "13222",pwd 对应 "123456",age 对应 28INSERT INTO USER (name,account,pwd,age) VALUES ("lisi", "13222", "123456", 28);

另外一个需要注意的地方是,如果表设置主键自增长,那么这张表插入数据就可以忽略主键。

3.2 DELETE 和 UPDATE

UPDATE 对应这更新表中的记录,对应的命令是:

UPDATE table_name SET column1 = value1, column2 = value2...., columnN = valueN WHERE [condition];

UPDATE USER SET name = "小王" WHERE id = 1;

DELETE 对应删除表中的记录,对应的命令是:

DELETE FROM table_name WHERE [condition];

DELETE FROM USER WHERE name = "九心";

4. 数据库查询语言

最复杂的就是数据库的查询,里面使用了很多关键字,我用了一张脑图来表达:

查询关键字

对应的命令是:

SELECT * 或者 具体的字段FROM 表名[ WHERE  ][ GROUP BY 列名 ][ HAVING  ][ ORDER BY 列名  |  ][ LIMIT 行数 OFFSET 偏移量]# 查询 姓张并且年龄大于20 的用户SELECT * FROM USER WHERE name like "张%" and age > 20;# 统计每个名字的用户数,按降序排列SELECT name,count(name) FROM USER GROUP BY name ORDER BY count(name) DESC;

除了上述,还有复合查询,也就是交叉连接、内连接和外连接,不过 SQLite 不支持右外连接,感兴趣的同学可以自行了解。

三、Flutter 实战

有了一定的基础,我们就可以在 Flutter 中运用了。如果我们想要在 Flutter 中使用数据,就需要引入 sqflite

1. 添加依赖

# 当前最新版本是 1.3.0dependencies:  ...  sqflite: ^1.3.0

2. 建表

创建表格是一个数据库操作,数据库操作应该是一个异步操作。

上面讲解SQL语法的时候,使用的用户收藏商品,这里我改成了用户收藏鞋子,三张表依次是 usershoefav_shoe 表,建表使用的原生 SQL 语句:

  // 创建表  Future _onCreate(Database db, int version) async {    db.execute(        "CREATE TABLE IF NOT EXISTS user("            "id INTEGER PRIMARY KEY autoincrement, "            "account TEXT, "            "pwd TEXT, "            "name TEXT, "            "headImage Text);");    db.execute(        "CREATE TABLE IF NOT EXISTS shoe("            "id INTEGER PRIMARY KEY autoincrement, "            "name TEXT, "            "description TEXT, "            "price REAL, "            "brand Text, "            "imageUrl Text);");    db.execute(        "CREATE TABLE IF NOT EXISTS fav_shoe("            "id INTEGER PRIMARY KEY autoincrement, "            "show_id INTEGER, "            "user_id INTEGER, "            "date INTEGER , "            "FOREIGN KEY(user_id) REFERENCES user(id), "            "FOREIGN KEY(show_id) REFERENCES shoe(id));");    return;  }

创建表的操作放在哪里呢?sqflite 是放在打开数据库的时候,打开数据库的时候不仅要添加hook数据库创建的方法 onCreate,还需要添加 数据库存放的路径数据库的版本

  Future _initDB() async {    // 路径获取是通过 path_provider 库,需要自行添加依赖    Directory documentsDirectory = await getApplicationDocumentsDirectory();    String path = join(documentsDirectory.path, 'dbHoo');return await openDatabase(path, onCreate: _onCreate, version: 1);  }

3. 创建实体类

user 表 和 shoe 表是类似的,我们放一个 shoe 表即可,对于 fav_shoe 这种有外键的表,我们也要考虑一下如何去创建。

3.1 User 表

用户表构建很简单,除了必须的基础属性和构造方法外,最好再写一下 User 对象和 Map 互转的方法,方便后面的数据库操作。

class User {  int id;  String account;  String pwd;  String name;  String headImage;

  User(this.account, this.pwd, this.name, this.headImage, {this.id});

  User.fromJson(Map json){    id = json['id'];    account = json['account'];pwd = json['pwd'];    name = json['name'];    headImage = json['headImage'];  }  Map toMap() {return {'id': id,'account': account,'pwd': pwd,'name': name,'headImage': headImage,    };  }}

3.2 FavShoe 表

对于有外键约束的表,我猜想查询结果的时候,会不会提供给我们外键相关的对象?比如说 FavShoe 表,外键有 useId,会不会提供给我们 User 对象呢?

从前面 User 实体中的方法来看,显然是不会的,如果我们想要在得到结果的时候也要获取到外键相关的对象,我们就要做更多的事情,我们在设计实体的时候,就可以考虑把这些东西加进来了。

除了创建基础的属性以外,我还创建了跟外键相关的对象属性 usershoe

class FavShoe {  final int id;  final int userId;  User user;  final int shoeId;  Shoe shoe;  final int date;

  FavShoe(this.id, this.userId, this.shoeId, this.date);

  factory FavShoe.fromJson(Map map){return FavShoe(map['id'], map['userId'], map['shoeId'], map['date']);  }  Map toMap() {return {'id': id,'userId': userId,'shoeId': shoeId,'date': date    };  }}

4. 插入数据

插入数据有两种方法,rawInsertinsert 方法,rawInsert 更接近原生 SQL 语句,insert 方法更简单。

4.1 rawInsert 方法

这个 db 对象类型 Future,我这里做了一层封装,简单起见,就不展示了:

  // 新增用户  Future insertUserRaw(User user) async {    var _db = await db;    await _db.rawInsert("INSERT INTO USER (account, pwd, name, headImage) VALUES (?, ?, ?, ?);",[user.account,user.pwd,user.name,user.headImage]);  }

如果使用rawInsert方法,相当于直接操作的原生语句,手写 SQL 语句的时候,很容易发生小问题,更推荐直接使用第二种方法。

后面 rawUpdaterawDeleterawQuery 就不讲解了。

4.2 insert 方法

  Future insertUser(User user) async {    var _db = await db;    await _db.insert("user", user.toMap(),        conflictAlgorithm: ConflictAlgorithm.fail);  }

主要会用到三个参数:第一个指定表名,第二个是我们 User 对象转化成的 Map,第三个是插入发生冲突使用的规则。

这里有两点注意,分别是二和三。

先谈二,当对象转化成 Map 的时候, 对于普通表来说,没什么难的,对于有外键的表呢,只处理外键就行了,我在实体中生成的外键对象是不用管的。

class FavShoe {  final int id;  final int userId;  User user;  final int shoeId;  Shoe shoe;  final int date;

  // ...

  Map toMap() {return {'id': id,'userId': userId,'shoeId': shoeId,'date': date    };  }}

上面的 usershoe 对象就不用管了。

再谈三,当相同主键的记录再一次插入的时候,我们处理冲突规则如下:

规则 解释
rollback 回滚,事务支持回滚
abort 终止,冲突发生的事务前的操作都会被保存
fail 失败,冲突前SQL操作结果都会被保存
ignore 忽略,发生冲突时
replace 发生冲突时,直接代替已有的记录

5. 查询操作

查询是数据库中最为繁琐的一部分,因为有些东西得记住。重点来看 query 方法:

Future>> query(String table, // 表名      {bool distinct, // 是否去重      List columns, // 查询的列      String where, // 查询条件      List whereArgs, // 条件对应的参数      String groupBy, // 分组      String having, // 分组条件      String orderBy, // 排序      int limit, // 限制数量      int offset}); // 偏移量

知道了这些参数的意义,使用就变得简单起来,举几个例子。

5.1 获取用户

重点:如何使用 Where 进行条件查询。

  // 获取用户  Future searchUserByNameAndPwd(String account, String pwd) async {    var _db = await db;    List> result = await _db.query("user",where: 'name = ? and pwd = ?', whereArgs: [account, pwd]);    Map r = result.isNotEmpty ? result.first : null;if (r == null) return null;return User.fromJson(r);  }

5.2 获取指定位置和指定品牌的鞋子

重点:如何使用排序。

  // 获取指定位置的Shoe列表  Future> queryShoeByPosAndBrand(int startPos, int endPos, String brand) async {    var _db = await db;    List> result = await _db.query('shoe',where: ' id > ? and id <= ? and brand = ?',        whereArgs: [startPos, endPos, brand],        orderBy: 'id ASC');if (result.isEmpty) return List();return List.generate(result.length, (i) => Shoe.fromJson(result[i]));  }

5.3 处理包含外键的查询

对于 FavShoe 表来说,我希望,当查到收藏记录的时候,我也能够得到 FavShoe 对应的 Shoe 的数据:

  // 查询某人的收藏记录  Future queryFavShoeByUserID(int userId, int shoeId) async {    var _db = await db;    List> result = await _db.query('fav_shoe',where: ' user_id = ? and shoe_id = ?',        whereArgs: [userId,shoeId]);if (result.isEmpty) return null;    var _favShoe = FavShoe.fromJson(result.first);if(_favShoe == null)return null;    // 单独查询鞋子表    var _shoe = await queryShoeById(_favShoe.shoeId);    _favShoe.shoe = _shoe;return _favShoe;  }

可以看到,我是在进行过一次收藏记录查询的同时,又根据 shoeIdShoe表进行了一次查询,假设我现在还想获取到用户的信息,那么就需要对 User 表再进行一次查询。

如果我查询的某个用户的是一个列表呢?我们肯定不想对每条记录进行遍历,然后再为每条记录再进行一次查询。

5.4 复合查询

我们的目标是获取某个用户的所有收藏记录,我想获取到所有收藏记录的同时,还能获取到每条搜藏记录对应鞋子的价格和名称,这个时候就可以使用复合查询。

query 方法貌似不支持复合查询,所以我们得使用 rawQuery

  // 查询某人的收藏记录  Future> queryFavShoesByUserID(int userId) async {    var _db = await db;    List> result = await _db.rawQuery("SELECT fav_shoe.id, shoe_id, user_id, name, price ""FROM fav_shoe LEFT OUTER JOIN ON fav_shoe.shoe_id = shoe.id ""WHERE user_id = ?;",[userId]);if (result.isEmpty) return List();return List.generate(result.length, (i) => FavShoe.fromJson(result[i]));  }

看一下这里的 map 是如何转化的:

class FavShoe {  final int id;  final int userId;  User user;  final int shoeId;  Shoe shoe;  final int date;

  // ...

  factory FavShoe.fromJsonAndShoe(Map map) {    Shoe shoe =        Shoe(map['name'], null, map['price'], null, null, id: map['shoeId']);return FavShoe(map['userId'], map['shoeId'], map['date'], id: map['id'], shoe: shoe);  }  //...}

同样也是创建一个 Shoe 对象,存入我们直接查询的数据,比刚刚再进行一次查询方便多了。

6. 更新和删除

更新和删除都是比较简单的操作,我们放在一起讲。

6.1 更新

  // 更新用户  Future updateUser(User user) async {    var _db = await db;    await _db.update("user", user.toMap(), where: "id = ?",whereArgs: [user.id]);  }

6.2 删除

根据用户 id 删除用户:

  // 删除  Future deleteUser(int userId) async {    var _db = await db;    await _db.delete("user", where: "id = ?",whereArgs: [userId]);  }

7. 事务

事务的概念很简单,就是一组 SQL 操作要么全执行,要么都不执行,

sqflite 提供了两种处理事务的方法:

  • 直接使用事务。
  • 使用 Batch

7.1 直接调用

  Future doTransaction() async {    var _db = await db;    _db.transaction((txn) async {        // 数据库操作一        // 数据库操作二        // ...    });  }

数据库操作直接放在代码块中即可。

7.2 Batch使用

Batch 的操作和前面的增删查改的操作大致上是一直的,不过会有一点区别,使用 Batch 进行增删查改的时候没有返回值,它会在 Batch#commit() 方法获取所有的结果,和前面操作的顺序一致:

  Future doTransaction() async {    var _db = await db;    Batch batch = _db.batch();    User one = new User("200722649@qq.com", "123456", "ChenHa", null);    User two = new User("200622649@qq.com", "123456", "WangHa", null);    batch.insert("user", one.toMap());    batch.insert("user", two.toMap());    batch.delete("user",where: "id = ?",whereArgs: [6]);    batch.query("user");    // results 的数量有四个    // 第一个 - 插入用户one的结果    // 第二个 - 插入用户two的结果    // 第三个 - 删除用户id为6的结果    // 第四个 - 查询所有用户的结果    List results = await batch.commit();  }

可以看到,Batch 的理念和事务是一致的,并且它也是用事务实现的,感兴趣的同学可以自行研究。

四、总结

关于数据库的学习到此就结束了,个人觉得还是得先学会SQL语法,因为很多第三方库进行数据库处理的时候最后都转化为SQL语句,后面即使遇到问题,你也能很快定位到问题。

另外,Flutter 中 sqflite 这个库的使用还是比较接近使用原生SQL操作的,这也进一步要求开发者对 SQL 语法的掌握。

如果你有更好的理解,欢迎下方评论交流,如果你觉得本文不错,『三连』是对我最好的肯定!

打工人

sqlite复制数据主键重复_Flutter 数据库指南相关推荐

  1. springboot 主键重复导致数据重复_程序员:MySQL处理插入过程中主键或唯一键重复值的解决办法

    向MySQL插入数据有时会遇到主键重复的场景,原来的做法是先在程序代码中SELECT一下,判断是否存在指定主键或唯一键的数据,如果没有则插入,有的话则执行UPDATE操作,或另外一套逻辑,这种方法是不 ...

  2. mysql 联合主键重复数据库_联合主键和复合主键有什么区别

    联合主键和复合主键有什么区别 发布时间:2020-07-11 09:52:20 来源:亿速云 阅读:171 作者:Leah 这期内容当中小编将会给大家带来有关联合主键和复合主键有什么区别,文章内容丰富 ...

  3. mysql主键可以重复吗_mysql 处理主键重复

    select * from stock; -- 1.批量插入,忽略主键重复的数据 insert IGNORE into insert IGNORE into `stock` (`id`,`name`, ...

  4. mysql 主键重复问题解决 Duplicate entry '787192513' for key 'PRIMARY'

    Mysql导入大量数据时,会报如下错误,主键重复,不能再继续执行. Query: INSERT INTO `FNDCN_MNG` VALUES(787390578, 1, '杨明', 95585, ' ...

  5. MySQL主键重复问题解决 Duplicate entry '787192513' for key 'PRIMARY'

    Mysql导入大量数据时,会报如下错误,主键重复,不能再继续执行. Query: INSERT INTO `FNDCN_MNG` VALUES(787390578, 1, '杨明', 95585, ' ...

  6. java 插入数据 主键_JDBC插入数据返回数据主键代码实例

    Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法.JD ...

  7. 解决mybatisPlus插入数据主键突然很大

    解决mybatisPlus插入数据主键突然很大 1.在管理员窗口进入对应的数据库,然后输入下面的指令: alter table sys_user AUTO_INCREMENT=30; ​sys_use ...

  8. mysql update主键冲突_mysql主键重复,不报错,只更新的操作

    项目中对接一个单点登陆的回调api,需要判断用户是否在库,不在库新增用户,在库更新登陆次数,大概代码如下:<?php $isExist = true; if ($isExist) { inser ...

  9. hive中多表full join主键重复问题

    目录 0. 其他 1. 问题描述 2. 问题复现 2.1. 建表语句 2.2. 插入数据 2.3. 查询SQL以及问题 3. 问题原因 4. 问题解决 0. 相关文章链接 开发随笔文章汇总 1. 问题 ...

最新文章

  1. 台式计算机,笔记本电脑如何查看自己的电池损耗情况!
  2. 基于OpenCV的表格文本内容提取
  3. 【知识图谱】关于知识图谱,我们接下来该研究什么?斯坦福教授们给出了答案...
  4. UDT协议实现分析——UDT初始化和销毁
  5. Nature | 基于单细胞RNA测序绘制人类肺组织分子细胞图谱,成功鉴定多种未知细胞类型...
  6. 国企营业收入逾17万亿 同比增长24.2%
  7. linux 系统命令总结之ubuntu 系列命令 持续更新中~
  8. 控制变量!控制变量!
  9. 连接linux服务器安卓模拟器,云服务器怎么运行安卓模拟器
  10. 2020-11-30 DOA估计/方向谱分析 中文书单
  11. c语言.jpg图片转成数组_jpg图片不仅能转成PDF文档格式,还可以一键合并!
  12. 编写一个C语言程序 实现自我介绍,用c语言编程实现,别出心裁的情侣拍照
  13. 0基础学绘画怎么临摹
  14. form 表单 onsubmit 属性
  15. swpuctf 2021
  16. [2012山东ACM省赛] Fruit Ninja II (三重积分,椭球体积)
  17. JAVA编写的纯色背景图片去除底色变成透明背景图片的工具
  18. 财路网每日原创推送:区块链行业没有大佬
  19. 住在储藏室的小夫妻【zt】
  20. jperf linux运行,Linux网络性能评估工具iperf

热门文章

  1. Mat,Iplimage,vector,vector_vector_Point2f等类型之间的相互转换
  2. Learn OpenGL (四):纹理
  3. 在Ubuntu 14.04 64bit上安装下载管理器FlareGet 4.2.92
  4. C语言单链表求环,并返回环的起始节点
  5. linux进程通信:pipe实现进程同步
  6. 再记一次ceph object unfound的艰辛历程
  7. 计算机网络基础 1.0 -- 概述
  8. 移动端丨-webkit-overflow-scrolling:touch属性导致页面卡住
  9. JavaScript闭包函数箭头函数调用与执行
  10. Mac OS Terminal Commands