Flutter 二次封装Sqlite基类
2023-3-22 14:26:26 Author: www.uedbox.com(查看原文) 阅读量:22 收藏

  • 发表于
  • flutter

安装 Sqlite 插件

首先我们需要安装 Sqlite 插件

创建基类,用来实例化数据库

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

import 'package:sqflite/sqflite.dart';

abstract class EntityPlus {

static const String _dbName = "xxx";//数据库名称

  static const int _newVersion = 1;//数据库版本

  static int _oldVersion = 0;//数据库上一个版本

  static String? _dbBasePath;//数据库地址

  static Database? _database;//数据库实例

  EntityPlus() {

   _initDatabase();

  }

  ///初始化数据库

  Future<Database> _initDatabase() async {

    //获取数据库的位置

    _dbBasePath ??= await getDatabasesPath() + "/$_dbName.db";

    //打开数据库

    _database ??= await openDatabase(

      _dbBasePath!,

      version: _newVersion,

      // onConfigure: (db) { },//数据库初始化时触发的回调

      // onOpen: (db) { },//数据库被打开时触发的回调

      // onCreate: (db, version){},//创建数据库时触发的回调

      onUpgrade: (db, oldVersion, newVersion){//数据库升级时触发的回调

        /*

         这里需要注意, 在后面时会用到_oldVersion, _oldVersion的变化会触发子类的某些方法

        */

        _oldVersion = old;

      },

      onDowngrade: (db, oldVersion, newVersion){//数据库降级时触发的回调

        /*

         这里需要注意, 在后面时会用到_oldVersion, _oldVersion的变化会触发子类的某些方法

        */

        _oldVersion = old;

      },

    );

    return _database!;

  }

}

基类已经做好了初始化数据库的准备, 当子类继承基类时会触发初始化数据库事件, 初始化数据库完成后基类还需要做哪些事情?

  1. 建表, 触发子类建表事件, 但是表如果已经存在了, 重复创建会报错, 所以这个函数只能触发一次;
  2. 数据库升级或降级,触发子类的升级或降级事件, 并且也只能触发一次;

添加建表功能

如何触发子类建表事件, 并且只触发一次? 我们可以在基类中定义创建表的函数,并且该函数在子类中必须重写, 判断该表是否存在,如果不存在则创建

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

import 'package:sqflite/sqflite.dart';

abstract class EntityPlus {

//...代码省略

  abstract String tableName;//数据表名称,在子类中必须要重写的字段

  bool exists = false;//数据表是否存在

  ///建表函数, 在子类中必须重写

Future<void> onCreate(Database db, int version);

  EntityPlus() {

   _initDatabase();

  }

  ///初始化数据库

  Future<Database> _initDatabase() async {

    //...省略获取数据库的位置代码

    //...省略打开数据库代码

    //判断表是否存在

    exists = await tableExists();

    if(!exists){

      //表不存在时调用建表函数

      await onCreate(_database!, _newVersion);

      exists = true;

    }

    return _database!;

  }

  ///判断表是否存在

  Future<bool> tableExists() async {

    //内建表sqlite_master

    var res = await _database!.rawQuery(

      "SELECT * FROM sqlite_master WHERE TYPE = 'table' AND NAME = '$tableName'",

    );

    return res.isNotEmpty;

  }

}

数据库升级或降级

在基类中我们实现了建表的功能, 同理数据库升级或降级也可以这样写

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

import 'package:sqflite/sqflite.dart';

abstract class EntityPlus {

//...代码省略

  abstract String tableName;//数据表名称,在子类中必须要重写的字段

  bool exists = false;//数据表是否存在

  ///建表函数, 在子类中必须重写

Future<void> onCreate(Database db, int version);

  ///数据库升级时触发的函数,子类中可以根据需要时进行重写

  onUpgrade(Database db, int oldVersion, int newVersion) {}

  ///数据库降级触发的函数,子类中可以根据需要时进行重写

  onDowngrade(Database db, int oldVersion, int newVersion) {}

  EntityPlus() {

   _initDatabase();

  }

  ///初始化数据库

  Future<Database> _initDatabase() async {

    //...省略获取数据库的位置代码

    //...省略打开数据库代码

    //...省略建表代码

    //数据第一次创建时_oldVersion等于0, 所以忽略

    if (_oldVersion != 0) {

      if (_oldVersion > _newVersion) { //判断是否降级了

        print("_oldVersion === $_oldVersion");

        print("_newVersion === $_newVersion");

        //数据库降级了,如果子类重写了onDowngrade方法, 则调用的是子类的;

        await onDowngrade(

          _database!,

          await _database!.getVersion(),

          _newVersion,

        );

      } else if (_oldVersion < _newVersion) { //判断是否升级了

        print("_oldVersion === $_oldVersion");

        print("_newVersion === $_newVersion");

        //数据库升级了,如果子类重写了onUpgrade方法, 则调用的是子类的;

        await onUpgrade(

          _database!,

          await _database!.getVersion(),

          _newVersion,

        );

      }

    }

    return _database!;

  }

}

简易版增删改查

好了现在我们有了建表的功能, 但是我们还需要对表进行增删改查, 所以接下来我们封装一个简易的增删改查功能

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

import 'package:sqflite/sqflite.dart';

abstract class EntityPlus {

//...代码省略

  Database get database {

    return _database!;

  }

  ///插入数据

  insert(Map<String, Object?> values) async {

    return database.insert(tableName, values);

  }

  ///删除数据

  remove(Map<String, Object?> json) async {

    var database = await getDatabase();

    List<String> keys = json.keys.toList();

    List<String> where = [];

    for (int i = 0; i < keys.length; i++) {

      String key = keys[i];

      where.add("$key=${json[key]}");

    }

    return database.delete(

      tableName,

      where: where.join(" and "),

    );

  }

  ///修改数据

  update(Map<String, Object?> json1, Map<String, Object?> json2) async {

    List<String> keys = json1.keys.toList();

    List<String> where = [];

    for (int i = 0; i < keys.length; i++) {

      String key = keys[i];

      if (json1[key].runtimeType == String) {

        where.add("$key='${json1[key]}'");

      } else {

        where.add("$key=${json1[key]}");

      }

    }

    return database.update(

      tableName,

      json2,

      where: where.isEmpty ? null : where.join(" and "),

    );

  }

  ///缓存的数据

  static final Map<String, List<Map<String, Object?>>> _findCache = {};

  ///查找数据

  Future<List<Map<String, Object?>>> find({

    Map<String, dynamic>? where,

    int? page,

    int? pageSize,

  }) async {

    List<String> keys = where?.keys.toList() ?? [];

    List<String> whereList = [];

    for (int i = 0; i < keys.length; i++) {

      String key = keys[i];

      if (where![key].runtimeType == String) {

        whereList.add("$key='${where[key]}'");

      } else {

        whereList.add("$key=${where[key]}");

      }

    }

    String sql = whereList.join(" and ");

    String mapKey = "${tableName}_${sql}_page=${page}_pageSize=$pageSize";

    List data = sql.isEmpty ? [] : (_findCache[mapKey] ?? []);

    if (data.isNotEmpty) {

      return _findCache[mapKey]!;

    }

    var result = await database.query(

      tableName,

      where: sql.isEmpty ? null : sql,

      offset: page == null ? null : (page - 1) * (pageSize ?? 1),

      limit: pageSize,

    );

    if (sql.isNotEmpty) {

      _findCache[mapKey] = result;

    }

    return result;

  }

  rawQuery(String sql) async {

    return database.rawQuery(sql);

  }

}

开始实验

新建一个user_info实体类, 继承EntityPlus

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

class UserInfoEntity extends EntityPlus {

  @override

  String tableName = "user_info";

  //建表函数,当数据库中没有这个表时,基类会触发这个函数

  @override

  onCreate(db, version) async {

    print("创建 $tableName 数据表");

    await db.execute("""

      CREATE TABLE $tableName (

        id integer primary key autoincrement,

        name TEXT,

        sex INTEGER,

        phone TEXT

      )

    """);

  }

  ///当数据库升级时,基类会触发的函数

  @override

  onUpgrade(db, oldVersion, newVersion) {}

  ///当数据库降级,基类会触发的函数

  @override

  onDowngrade(db, oldVersion, newVersion) {}

}

页面中操作user_info表

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

class Test extends StatefulWidget {

  const Test({Key? key}) : super(key: key);

  @override

  State<Test> createState() => _TestState();

}

class _TestState extends State<Test> {

  @override

  void initState() {

    super.initState();

    try {

      UserInfoEntity userInfoEntity = UserInfoEntity();

      userInfoEntity.find();

    } catch (err) {

      //注意由于打开数据库属于异步操作, 虽然UserInfoEntity已经实例化了, 但是数据库的初始化并没有完成

      print("报错了: $err");

    }

  }

  @override

  Widget build(BuildContext context) {

    return const Scaffold();

  }

}

此时我们需要考虑一些问题:

  1. 打开数据库属于异步操作, 直接实例化时, 数据库并没有初始化完成, 此时操作数据库肯定会发生异常;
  2. 每次操作数据库都要实例化一次是不是太浪费内存了;

基于以上问题, 我们可以将 UserInfoEntity 改成单例模式

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

class UserInfoEntity extends EntityPlus {

  static UserInfoEntity? _ins;

  UserInfoEntity._();

  static UserInfoEntity instan() {

    if (_ins == null) {

      print("实例化");

    }

    return _ins ??= UserInfoEntity._();

  }

  @override

  String tableName = "user_info";

  //建表函数,当数据库中没有这个表时,基类会触发这个函数

  @override

  onCreate(db, version) async {

    print("创建 $tableName 数据表");

    await db.execute("""

      CREATE TABLE $tableName (

        id integer primary key autoincrement,

        name TEXT,

        sex INTEGER,

        phone TEXT

      )

    """);

  }

  ///当数据库升级时,基类会触发的函数

  @override

  onUpgrade(db, oldVersion, newVersion) {}

  ///当数据库降级,基类会触发的函数

  @override

  onDowngrade(db, oldVersion, newVersion) {}

}

然后再新建一个类,用来存放所有实体类, 将所有实体类先进行实例化

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

import 'entitys/user_info.entity.dart';

class MySqlite {

  static forFeature() async {

    var list = [

      UserInfoEntity.instan(),

      //...其他的表实体类

    ];

    for (int i = 0; i < list.length; i++) {

      var entity = list[i];

      //是否还记得基类中定义的 exists 字段,这是用来判断表是否创建完成

      while (!entity.exists) {

        //等待数据表创建完成

        await Future.delayed(const Duration(milliseconds: 60), () {});

      }

    }

  }

}

由于第一次安装应用, 设备并不存在我们需要的数据表, 所以还需要等待数据表的创建完成, 防止进入页面时直接对表进行操作时发生异常

在main函数中执行 MySqlite.forFeature, 等数据库初始化完成, 并且所有的表都已经创建完成, 再执行下面的代码

void main() async {

  await MySqlite.forFeature();

  runApp(const MyApp());

}

需要注意一个问题, 等待数据库和表的初始化完成是需要时间的, 这种方式会阻塞页面, 如果表很多的话白屏时间会加长, 可以将这个方法放在启动页中, 等初始化完毕后再进入主页面, 因为我的程序数据表并不多, 所以我就直接写在mian函数中了;

此时再页面中可以直接对数据库进行增删改查的操作了

UserInfoEntity userInfoEntity = UserInfoEntity.instan();

userInfoEntity.insert({

  "name": "刘小明",

  "sex": 1,

  "phone": "123456789",

});

userInfoEntity.find();

userInfoEntity.update();

userInfoEntity.remove({"id": 1});

基类的完整代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

import 'package:sqflite/sqflite.dart';

abstract class EntityPlus {

  static const String _dbName = "zjdk_plus";

  static const int _newVersion = 1;

  static int _oldVersion = 0;

  static String? _dbBasePath;

  static Database? _database;

  ///表名称

  abstract String tableName;

  ///表是否存在

  bool exists = false;

  ///数据库实例化完成

  onReload(Database db, int version) {}

  ///创建表

  Future<void> onCreate(Database db, int version);

  ///更新表

  onUpgrade(Database db, int oldVersion, int newVersion) {}

  ///数据库降级

  onDowngrade(Database db, int oldVersion, int newVersion) {}

  EntityPlus() {

    _initDatabase();

  }

  ///创建数据库

  Future<Database> _initDatabase() async {

    _dbBasePath ??= await getDatabasesPath() + "/$_dbName.db";

    _database ??= await openDatabase(

      _dbBasePath!,

      version: _newVersion,

      // onConfigure: (db) { },

      // onCreate: onCreate,

      onUpgrade: (db, old, newV) {

        _oldVersion = old;

      },

      onDowngrade: (db, old, newV) {

        _oldVersion = old;

      },

      // onOpen: onOpen,

    );

    onReload(_database!, _newVersion);

    //判断表是否存在

    exists = await tableExists();

    if (!exists) {

      await onCreate(_database!, _newVersion);

      exists = true;

    }

    if (_oldVersion != 0) {

      if (_oldVersion > _newVersion) {

        print("_oldVersion === $_oldVersion");

        print("_newVersion === $_newVersion");

        //数据库降级了

        await onDowngrade(

          _database!,

          await _database!.getVersion(),

          _newVersion,

        );

      } else if (_oldVersion < _newVersion) {

        print("_oldVersion === $_oldVersion");

        print("_newVersion === $_newVersion");

        //数据库升级了

        await onUpgrade(

          _database!,

          await _database!.getVersion(),

          _newVersion,

        );

      }

    }

    return _database!;

  }

  ///表是否存在

  Future<bool> tableExists() async {

    //内建表sqlite_master

    var res = await _database!.rawQuery(

      "SELECT * FROM sqlite_master WHERE TYPE = 'table' AND NAME = '$tableName'",

    );

    return res.isNotEmpty;

  }

  ///表列是否存在

  Future<bool> columnExists(String columnName) async {

    var result = await _database!.rawQuery("""

      SELECT sql FROM sqlite_master WHERE type='table' AND name='$tableName' COLLATE NOCASE limit 1

    """);

    String sql = result[0]["sql"] as String;

    int startIndex = sql.indexOf("(") + 1;

    int endIndex = sql.indexOf(")");

    sql = sql.substring(startIndex, endIndex);

    List<String> sqlList = sql.split(",").map((e) => e.trim()).toList();

    bool exists = false;

    for (int j = 0; j < sqlList.length; j++) {

      var rowStr = sqlList[j].trim().split(",").join("");

      var colName = rowStr.split(" ")[0].trim();

      if (colName == columnName) {

        exists = true;

        break;

      }

    }

    return exists;

  }

  ///新增列

  Future addColumn(String columnName, String type) async {

    return await _database!.rawQuery("""

      ALTER TABLE $tableName ADD  $columnName $type

    """);

  }

  ///删表

  dropTable() async {

    if (_database == null) {

      await _initDatabase();

    }

    await _database!.execute("""

      drop table if exists $tableName;

    """);

  }

  Database get database => _database!;

  ///插入数据

  insert(Map<String, Object?> values) async {

    return database.insert(tableName, values);

  }

  ///删除数据

  remove(Map<String, Object?> json) async {

    List<String> keys = json.keys.toList();

    List<String> where = [];

    for (int i = 0; i < keys.length; i++) {

      String key = keys[i];

      where.add("$key=${json[key]}");

    }

    return database.delete(

      tableName,

      where: where.join(" and "),

    );

  }

  ///修改数据

  update(Map<String, Object?> json1, Map<String, Object?> json2) async {

    List<String> keys = json1.keys.toList();

    List<String> where = [];

    for (int i = 0; i < keys.length; i++) {

      String key = keys[i];

      if (json1[key].runtimeType == String) {

        where.add("$key='${json1[key]}'");

      } else {

        where.add("$key=${json1[key]}");

      }

    }

    return database.update(

      tableName,

      json2,

      where: where.isEmpty ? null : where.join(" and "),

    );

  }

  ///缓存的数据

  static final Map<String, List<Map<String, Object?>>> _findCache = {};

  ///查找数据

  Future<List<Map<String, Object?>>> find({

    Map<String, dynamic>? where,

    int? page,

    int? pageSize,

  }) async {

    List<String> keys = where?.keys.toList() ?? [];

    List<String> whereList = [];

    for (int i = 0; i < keys.length; i++) {

      String key = keys[i];

      if (where![key].runtimeType == String) {

        whereList.add("$key='${where[key]}'");

      } else {

        whereList.add("$key=${where[key]}");

      }

    }

    String sql = whereList.join(" and ");

    String mapKey = "${tableName}_${sql}_page=${page}_pageSize=$pageSize";

    List data = sql.isEmpty ? [] : (_findCache[mapKey] ?? []);

    if (data.isNotEmpty) {

      return _findCache[mapKey]!;

    }

    var result = await database.query(

      tableName,

      where: sql.isEmpty ? null : sql,

      offset: page == null ? null : (page - 1) * (pageSize ?? 1),

      limit: pageSize,

    );

    if (sql.isNotEmpty) {

      _findCache[mapKey] = result;

    }

    return result;

  }

  rawQuery(String sql) async {

    return database.rawQuery(sql);

  }

}

作者:向往全栈的Web小白


文章来源: https://www.uedbox.com/post/68785/
如有侵权请联系:admin#unsafe.sh