【iOS开发】数据存储——SQLite3框架FMDB的使用

【iOS开发】数据存储——SQLite3框架FMDB的使用

在学习FMDB框架之前,我们首先来大概了解下SQL(Structed Query Language)语句:

  • 特点:1)不区分大小写;2)每条语句都必须以分号结尾
  • 常用的关键字:selectinsertupdatedeletefromcreatewheredescorderbygrouptablealterviewindex等等
  • 数据库中不可以使用关键字来命名字段
  • 字段类型:1)integer:整型值;2)real:浮点值;3)text:文本字符串;4)blob:二进制数据
  • 语句的种类
    • 数据定义语句(DDL:Data Definition Language):包括createdrop等操作,新建表:create table,删除表:drop table
    • 数据操作语句(DML: Data Manipulation Language): 包括insert/update/delete等
    • 数据查询语句(DQL: Data Query Language): select是DQL(也是所有SQL)用得最多的语句,其他常用的关键字有whereorder bygroup byhaving

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 nullunique连个约束;3)如果想让主键自动增长,那么字段的类型必须是integer类型,并且增加autoincrement关键字。

外键约束

利用外键约束可以建立表与表之间的联系。外键一般是:一张表的某个字段引用着另外一张表的主键字段。

代码演示

数据准备

这个例子演示用到了两个类:studentclass

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地址 >>

如果文中有错误,请指出!我们共同学习,共同进步。谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容