【iOS开发】数据存储——SQLite3框架FMDB的使用
在学习FMDB框架之前,我们首先来大概了解下SQL(Structed Query Language)语句:
- 特点:1)不区分大小写;2)每条语句都必须以分号结尾
- 常用的关键字:
select
、insert
、update
、delete
、from
、create
、where
、desc
、order
、by
、group
、table
、alter
、view
、index
等等 - 数据库中不可以使用关键字来命名字段
- 字段类型:1)
integer
:整型值;2)real
:浮点值;3)text
:文本字符串;4)blob
:二进制数据 - 语句的种类
- 数据定义语句(DDL:Data Definition Language):包括
create
和drop
等操作,新建表:create table
,删除表:drop table
- 数据操作语句(DML: Data Manipulation Language): 包括insert/update/delete等
- 数据查询语句(DQL: Data Query Language):
select
是DQL(也是所有SQL)用得最多的语句,其他常用的关键字有where
/order by
/group by
/having
等
- 数据定义语句(DDL:Data Definition Language):包括
FMDB框架
FMDB有三个主要的类
-
FMDatabase
: 代表一个SQLite数据库,用于执行SQL语句 -
FMResultSet
: 代表查询结果 -
FMDatabaseQueue
: 在多线程中执行查询或者更新操作时使用
数据库存储路径
我们需要使用一个数据库的存储路径来创建一个FMDatabase
实例,这个路径可以时以下三种情况之一:
- 一个文件路径,这个路径可以时目前不存在的。如果不存在,它会自动创建。
- 一个空字符
""
。使用空字符,它会在一个临时的地方创建一个空数据库,在FMDatabase
连接关闭时,创建的数据库会被删除。 -
nil
。会创建一个内存中临时的数据库,当FMDatabase
连接关闭时,数据会被销毁
简单约束
建表时,我们可以给字段设置一些约束条件,而且我们建议给字段设定严格的约束,以保证数据的规范性
-
not null
: 规定字段的值不能为null -
unique
: 规定字段的值必须是唯一的 -
default
: 指定字段的默认值
主键约束
良好的数据库编程规范应该保持每条记录的唯一性,因此,增加主键约束来保证记录的唯一性。
什么是主键:Primary Key,简称PK,用来唯一标示某一条记录。主键可以是一个或多个字段。
设计原则:1)主键应当是对用户没有意义的;2)永远也不要更新主键;3)主键不应该包含动态变化的数据;4)主键应当由计算机生成。
主键字段:1)只要声明为primary key,就说明是一个主键字段;2)主键字段默认包含了not null
和unique
连个约束;3)如果想让主键自动增长,那么字段的类型必须是integer
类型,并且增加autoincrement
关键字。
外键约束
利用外键约束可以建立表与表之间的联系。外键一般是:一张表的某个字段引用着另外一张表的主键字段。
代码演示
数据准备
这个例子演示用到了两个类:student
和class
:
class StudentModel: NSObject {
var name: String
var age: Int
var score: Double
var classModel: ClassModel
init(name: String, age: Int, score: Double, classModel: ClassModel) {
self.name = name
self.age = age
self.score = score
self.classModel = classModel
}
}
class ClassModel: NSObject {
var name: String
init(name: String) {
self.name = name
}
}
下面其他方便使用的函数:
// 获取全部班级
private func getClasses() -> [ClassModel] {
let ios = ClassModel(name: ClassName.ios.rawValue)
let android = ClassModel(name: ClassName.android.rawValue)
let html5 = ClassModel(name: ClassName.html5.rawValue)
let java = ClassModel(name: ClassName.java.rawValue)
return [ios, android, html5, java]
}
// 获取全部学生
private func getStudents() -> [StudentModel] {
// 班级
var ios, android, html5, java:ClassModel!
for aClass in getClasses() {
switch aClass.name {
case ClassName.ios.rawValue:
ios = aClass
case ClassName.android.rawValue:
android = aClass
case ClassName.html5.rawValue:
html5 = aClass
case ClassName.java.rawValue:
java = aClass
default:
break
}
}
// android班
let zhangsan = StudentModel(name: "张三", age: 18, score: 59, classModel: android)
let lisi = StudentModel(name: "李四", age: 19, score: 65, classModel: android)
let wangwu = StudentModel(name: "王五", age: 17, score: 80, classModel: android)
let zhaoliu = StudentModel(name: "赵六", age: 16, score: 95, classModel: android)
// HTML5班
let xiaoming = StudentModel(name: "小明", age: 20, score: 75, classModel: html5)
let xiaofang = StudentModel(name: "小芳", age: 21, score: 79, classModel: html5)
// Java班
let xiaohong = StudentModel(name: "小红", age: 25, score: 85, classModel: java)
let xiaolong = StudentModel(name: "小龙", age: 26, score: 80, classModel: java)
// iOS班
let james = StudentModel(name: "詹姆斯", age: 32, score: 95, classModel: ios)
let irving = StudentModel(name: "欧文", age: 24, score: 89, classModel: ios)
return [zhangsan, lisi, wangwu, zhaoliu, xiaoming, xiaofang, xiaohong, xiaolong, james, irving]
}
// 获取班级ID
private func getClassID(withName name: ClassName) -> Int {
switch name {
case .ios:
return ClassID.ios.rawValue
case .android:
return ClassID.android.rawValue
case .html5:
return ClassID.html5.rawValue
case .java:
return ClassID.java.rawValue
}
}
// 用于保存班级的名称
private enum ClassName: String {
case ios, android, html5, java
}
// 用于保存班级的id
private enum ClassID: Int {
case ios, android, html5, java
}
创建数据库
// 获取路径
let fileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let schoolPath = fileURL!.path + "/school.db"
print(schoolPath)
// 创建表格
if let db = FMDatabase(path: schoolPath) {
self.db = db
}
else {
print("无法创建数据库")
return
}
// 打开表格
guard db.open() else {
print("无法打开数据库")
return
}
创建表格
private func createTable() {
// 创表学生表:主键名称为integer类型的id、自动增长;文本类型的name、不为空;integer类型的age、不为空;real类型的score、不为空;
// integer类型的class_id、不为空;fk_student_class外键,student表格的class_id字段引用class表格的id字段。
let createStudentTable = "create table student (id integer primary key autoincrement, name text not null, age integer not null, score real not null, class_id integer not null, constraint fk_student_class foreign key (class_id) references class (id));"
// 创建班级表:主键名称为integer类型的id、自动增长;文本类型的name、不为空
let createClassTable = "create table class (id integer primary key autoincrement, name text not null);"
do {
try db.executeUpdate(createStudentTable, values: nil)
try db.executeUpdate(createClassTable, values: nil)
print("表格创建成功")
}
catch {
print("创建表格错误: \(error.localizedDescription)")
}
}
删除表格
private func dropTable() {
do {
try db.executeUpdate("drop table student", values: nil)
try db.executeUpdate("drop table class", values: nil)
print("表格删除成功")
}
catch {
print("删表错误: \(error.localizedDescription)")
}
}
插入数据
private func insert(students: [StudentModel], andClasses classes: [ClassModel]) {
for aClass in classes {
do {
try db .executeUpdate("insert into class (name) values (?);", values: [aClass.name])
print("\(aClass.name)插入成功")
}
catch {
print("数据插入错误: \(error.localizedDescription)")
}
}
for student in students {
do {
try db.executeUpdate("insert into student (name, age, score, class_id) values (?, ?, ?, ?);", values: [student.name, student.age, student.score, getClassID(withName: ViewController.ClassName(rawValue: student.classModel.name)!)])
print("\(student.name)插入成功")
}
catch {
print("failed: \(error.localizedDescription)")
}
}
}
更新数据
private func updateData() {
do {
// 把表格中所有年龄大于等于30的记录,年龄改为29
try db.executeUpdate("update student set age = 29 where age >= 30;", values: nil)
print("数据更新成功")
}
catch {
print("数据更新错误:\(error.localizedDescription)")
}
}
查询数据
private func queryData() {
do {
// 从student表格中查询年龄大于等于18的记录
let rs = try db.executeQuery("select * from student where age >= 18;", values: nil)
while rs.next() {
let name = rs.string(forColumn: "name")
let age = rs.int(forColumn: "age")
let score = rs.double(forColumn: "score")
print("姓名: \(name),年龄:\(age), 分数:\(score)")
}
}
catch {
print("数据更新错误:\(error.localizedDescription)")
}
}
计算记录的数量
private func computeCountOfAllRecords() {
do {
let rs = try db.executeQuery("select count(age) from student where age >= 18;", values: nil)
while rs.next() {
let count = rs.int(forColumnIndex: 0)
print("记录总数为:\(count)")
}
}
catch {
print("计算总数量错误:\(error.localizedDescription)")
}
}
排序
private func orderByAge() {
do {
// asc:升序。 desc:降序
let rs = try db.executeQuery("select * from student order by age asc;", values: nil)
while rs.next() {
let name = rs.string(forColumn: "name")
let age = rs.int(forColumn: "age")
let score = rs.double(forColumn: "score")
print("姓名: \(name),年龄:\(age), 分数:\(score)")
}
}
catch {
print("排序错误:\(error.localizedDescription)")
}
}
分页查询
private func selectDataWithLimitation() {
do {
let rs = try db.executeQuery("select * from student order by score desc limit 5;", values: nil)
while rs.next() {
let name = rs.string(forColumn: "name")
let age = rs.int(forColumn: "age")
let score = rs.double(forColumn: "score")
print("姓名: \(name),年龄:\(age), 分数:\(score)")
}
}
catch {
print("限制数量错误:\(error.localizedDescription)")
}
}
Demo地址 >>
如果文中有错误,请指出!我们共同学习,共同进步。谢谢!