说说SQLite在移动开发的那些事儿

SQLite

简介

http://www.sqlite.org/

  • 是一款轻型的数据库
  • 设计目标是嵌入式
  • 占用资源少
  • 处理速度快
  • 当前版本 3.8.10.2,MAC 内置已经安装了 SQLite

什么是 SQLite?

  • SQLite 是一个进程内的库,本质上就是一个文件,是一个 SQL 数据库引擎,具有:

    • 自给自足
      • 不需要任何外部的依赖
    • 无服务器
      • 不需要一个单独的服务器进程或操作的系统
    • 零配置
      • 不需要安装或管理
      • 一个完整的 SQLite 数据库就是一个单一的磁盘文件
    • 轻量级
      • 完全配置时小于 400K,省略可选功能配置时小于250K
    • 事务性支持
  • 而服务端使用的数据库,如:OrcalSQL ServerMySQL...则需要独立的服务器,安装,配置,维护……

关系型数据库的特点

  • 一个 字段(COL) 存储一个值,类似于对象的一个属性
  • 一 行(ROW) 存储一条记录,类似于一个对象
  • 一个 表(TABLE) 存储一系列数据,类似于对象数组
  • 多个  之间存在一定 关系,类似于对象之间的关系,例如:一条微博数据中包含用户记录

术语

  • 字段(Field/Col):一个字段存储一个值,可以存储 INTEGERREALTEXTBLOBNULL 五种类型的数据
    • SQLite 在存储时,本质上并不区分准确的数据类型
  • 主键:Primary Key唯一标示一条记录的字段,具有以下特点:
    • 名字:xxx_id
    • 类型:Integer
    • 自动增长
    • 准确数值由数据库决定,程序员不用关心
  • 外键:Foreign Key,对应其他关系表的标示,利用外键 可以和另外一个建立起"关系"
    • 方便数据维护
    • 节约存储空间

开发数据库的步骤

  1. 建立数据库 -> 有存储数据的文件
  2. 创建数据表 -> 每一张数据表存储一类数据
  3. 利用 SQL 命令 实现增/删/查/改,并在 UI 中显示

移动应用中使用数据库的好处

  1. 将网络数据存储在本地,不用每次都加载,减少用户网络流量开销
  2. 对本地数据进行查询

SQLite 命令

DDL - 数据定义语言

命令描述
CREATE创建一个新的表,一个表的视图,或者数据库中的其他对象
ALTER修改数据库中的某个已有的数据库对象,比如一个表
DROP删除整个表,或者表的视图,或者数据库中的其他对象
  • 不需要记忆,可以直接从客户端软件复制/粘贴

DML - 数据操作语言

命令描述
INSERT新增
UPDATE修改
DELETE删除
  • 需要掌握,语法固定,简单

DQL - 数据查询语言

命令描述
SELECT查询

  • 需要掌握一些简单的查询指令

常用 SQL

创建表

/*
    创建数据表

    CREATE TABLE '表名' (
        '字段名' 类型(INTEGER, REAL, TEXT, BLOB)
                NOT NULL    不允许为空
                PRIMARY KEY    主键
                AUTOINCREMENT 自增长,
        '字段名2' 类型,
        ...
    )

    注意:在开发中,如果是从 Navicat 粘贴的 SQL,需要自己添加一个指令
    IF NOT EXISTS 加在表名前,如果数据表已经存在,就什么也不做
*/

CREATE TABLE IF NOT EXISTS "T_Person" (
     "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
     "name" TEXT,
     "age" INTEGER,
     "heigth" REAL
)

/* 简单约束 */
CREATE TABLE IF NOT EXISTS t_student
(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT,
    age INTEGER
);

CREATE TABLE IF NOT EXISTS t_student
(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT UNIQUE,
    age INTEGER
);

/* 添加主键 */
CREATE TABLE IF NOT EXISTS t_student
(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT,
    age INTEGER,
    score REAL
);

/* 添加主键 */
CREATE TABLE IF NOT EXISTS t_student
(
    id INTEGER,
    name TEXT,
    age INTEGER,
    score REAL,
    PRIMARY KEY(id)
);


插入

 INSERT INTO t_student
    (age, score, name)
    VALUES
    ('28', 100, 'zhangsan');

 INSERT INTO t_student
    (name, age)
    VALUES
    ('lisi', '28');

INSERT INTO t_student
    (score)
    VALUES
    (100);


修改

UPDATE t_student
    SET name = 'MM'
    WHERE age = 10;

UPDATE t_student
    SET name = 'WW'
    WHERE age is 7;

UPDATE t_student
    SET name = 'XXOO'
    WHERE age < 20;

UPDATE t_student
    SET name = 'NNMM'
    WHERE age < 50 and score > 10;

/*更新记录的name*/
UPDATE t_student SET name = 'zhangsan';


删除

DELETE FROM t_student;

DELETE FROM t_student WHERE age < 50;


查询

/* 分页 */
SELECT * FROM t_student
ORDER BY id ASC LIMIT 30, 10;

/* 排序 */
SELECT * FROM t_student
WHERE score > 50
ORDER BY age DESC;

SELECT * FROM t_student
WHERE score < 50
ORDER BY age ASC , score DESC;

/* 计量 */
SELECT COUNT(*)
FROM t_student
WHERE age > 50;

/* 别名 */
SELECT name as myName, age as myAge, score as myScore
FROM t_student;

SELECT name myName, age myAge, score myScore
FROM t_student;

SELECT s.name myName, s.age myAge, s.score myScore
FROM t_student s
WHERE s.age > 50;

/* 查询 */
SELECT name, age, score FROM t_student;
SELECT * FROM t_student;


删除表

/*删除表*/
DROP TABLE IF EXISTS t_student;





SQLite核心对象

核心对象 & 核心接口

核心对象

  1. database_connection

    • 由 sqlite3_open 函数创建并返回
    • 在使用其他 SQLite 接口函数之前,必须先获得 database_connnection 对象
  2. prepared_statement

    • 可以简单的将它视为编译后的SQL语句

核心接口

  1. sqlite3_open

    • 可以打开已经存在的数据库文件
    • 如果数据库不存在,可以创建新的数据库文件
    • 返回的 database_connection 对象是其他 SQLite APIs 的句柄参数
    • 可以在多个线程之间共享该对象指针
  2. sqlite3_prepare

    • 将 SQL 文本转换为 prepared_statement 对象
    • 不会执行指定的 SQL 语句
    • 只是将 SQL 文本初始化为执行的状态
  3. sqlite3_step

    • 执行一次 sqlite3_prepare 函数返回的 prepared_statement 对象
    • 执行完该函数后,prepared_statement 对象的内部指针将指向其返回结果集的第一行
    • 如果要获得后续的数据行,则需要不断地调用该函数,直到所有的数据行遍历完毕
    • 对于 INSERTUPDATE 和 DELETE 等 DML 语句,执行一次即可完成
  4. sqlite3_column

    • 用于获取当前行指定列的数据
    • 以下函数分别对应不同的数据类型
      • sqlite3_column_blob
      • sqlite3_column_bytes
      • sqlite3_column_bytes16
      • sqlite3_column_double
      • sqlite3_column_int
      • sqlite3_column_int64
      • sqlite3_column_text
      • sqlite3_column_text16
      • sqlite3_column_type
      • sqlite3_column_value
      • sqlite3_column_count
        • 用于获取当前结果集中的字段数量
  5. sqlite3_finalize

    • 销毁 prepared_statement 对象,否则会造成内存泄露
  6. sqlite3_close

    • 关闭之前打开的 database_connection 对象
    • 所有和该对象相关的 prepared_statements 对象都必须在此之前被销毁

Swift 中使用 SQLite

准备工作

  • 添加 libsqlite3.tbd
  • 创建 SQLite-Bridge.h
    • SQLite3 框架是一套 C 语言的框架,因此需要添加桥接文件
  • 选择 项目-TARGETS-Build Settings,搜索 Bridg
  • 在 Objective-C Bridging Header 中输入 项目名/SQLite-Bridge.h
    • 如果之前设置过桥接文件,可以直接使用


编译测试

SQLiteManager

与网络接口的独立类似,数据库的底层操作,也应该有一个独立的对象单独负责

SQLiteManager 单例

  • 新建 SQLiteManager.swift,并且实现以下代码:
/// SQLite 管理器
class SQLiteManager {

    /// 单例
    static let sharedManager = SQLiteManager()
}

数据库访问操作需求

  1. 建立数据库 -> 有存储数据的文件
  2. 创建数据表 -> 每一张数据表存储一类数据
  3. 利用 SQL 命令 实现增/删/查/改,并在 UI 中显示
建立&打开数据库
/// 数据库句柄
private var db: COpaquePointer = nil

/// 打开数据库
///
/// - parameter dbname: 数据库文件名
func openDB(dbname: String) {
    let path = (NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).last! as NSString).stringByAppendingPathComponent(dbname)
    print(path)

    if sqlite3_open(path, &db) != SQLITE_OK {
        print("打开数据库失败")
        return
    }

    print("打开数据库成功")
}
代码小结
  • 建立数据库需要给定完整的数据库路径
  • sqlite3_open 函数会打开数据库,如果数据库不存在,会新建一个空的数据库,并且返回数据库指针(句柄)
  • 后续的所有数据库操作,都基于此 数据库句柄 进行
打开数据库
  • 在 AppDelegate 中添加以下代码
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

    SQLiteManager.sharedManager.openDB("my.db")

    return true
}
代码小结
  • SQLite 数据库是直接保存在沙盒中的一个文件,只有当前应用程序可以使用
  • 在移动端开发时,数据库通常是以 持久式 连接方式使用的
  • 所谓 持久式连接 指的是只做一次 打开数据库 的操作,永远不做 关闭 数据库的操作,从而可以提高数据库的访问效率
创建数据表
  • 如果是第一次运行,打开数据库之后,只能得到一个空的数据,没有任何的数据表
  • 为了让数据库正常使用,在第一次打开数据库后,需要执行 创表 操作

注意:创表操作本质上是通过执行 SQL 语句实现的

  • 执行 SQL 语句函数
/// 执行 SQL
///
/// - parameter sql: SQL
///
/// - returns: 是否成功
func execSQL(sql: String) -> Bool {
    /**
        参数
        1. 数据库句柄
        2. 要执行的 SQL 语句
        3. 执行完成后的回调,通常为 nil
        4. 回调函数第一个参数的地址,通常为 nil
        5. 错误信息地址,通常为 nil
    */
    return sqlite3_exec(db, sql, nil, nil, nil) == SQLITE_OK
}
  • 创建数据表
/// 创建数据表
///
/// - returns: 是否成功
private func createTable() -> Bool {
    let sql = "CREATE TABLE IF NOT EXISTS T_Person \n" +
        "('id' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \n" +
        "'name' TEXT, \n" +
        "'age' INTEGER);"
    print(sql)

    return execSQL(sql)
}
  • 调整 openDB 函数
if createTable() {
    print("创表成功")
} else {
    print("创表失败")
    db = nil
}
代码小结
  • 创表 SQL 可以从 Navicat 中粘贴,然后做一些处理
    • 将 " 替换成 '
    • 在每一行后面增加一个 \n 防止字符串拼接因为缺少空格造成 SQL 语句错误
    • 在 表名 前添加 IF NOT EXISTS防止因为数据表存在出现错误

数据模型

  • 建立 Person 模型
class Person: NSObject {

    /// id
    var id: Int = 0
    /// 姓名
    var name: String?
    /// 年龄
    var age: Int = 0

    /// 使用字典创建 Person 对象
    init(dict: [String: AnyObject]) {
        super.init()

        setValuesForKeysWithDictionary(dict)
    }
}
  • 新增数据
/// 将当前对象插入到数据库
///
/// - returns: 是否成功
func insertPerson() -> Bool {
    assert(name != nil, "姓名不能为空")

    let sql = "INSERT INTO T_Person (name, age) VALUES ('\(name!)', \(age));"

    return SQLiteManager.sharedManager.execSQL(sql)
}
  • 在视图控制器添加如下代码,测试新增数据
/// 测试插入数据
func demoInsert() {
    print(Person(dict: ["name": "zhangsan", "age": 18]).insertPerson())
}
  • 更新记录
/// 更新当前对象在数据库中的记录
///
/// - returns: 是否成功
func updatePerson() -> Bool {
    assert(name != nil, "姓名不能为空")
    assert(id > 0, "ID 不正确")

    let sql = "UPDATE T_Person SET name = '\(name!)', age = \(age) WHERE id = \(id);"

    return SQLiteManager.sharedManager.execSQL(sql)
}
  • 在视图控制器添加如下代码,测试更新数据
/// 测试更新记录
func demoUpdate() {
    print(Person(dict: ["id": 1, "name": "lisi", "age": 20]).updatePerson())
}
  • 删除数据
/// 删除当前对象在数据库中的记录
///
/// - returns: 是否成功
func deletePerson() -> Bool {
    assert(id > 0, "ID 不正确")

    let sql = "DELETE FROM T_Person WHERE ID = \(id);"

    return SQLiteManager.sharedManager.execSQL(sql)
}
  • 在视图控制器添加如下代码,测试删除数据
/// 测试删除记录
func demoDelete() {
    print(Person(dict: ["id": 1, "name": "lisi", "age": 20]).deletePerson())
}
  • 测试批量插入
/// 测试批量插入数据
func insertManyPerson() {
    print("开始")
    let start = CFAbsoluteTimeGetCurrent()
    for i in 0..<100000 {
        Person(dict: ["name": "lisi-\(i)", "age": Int(arc4random_uniform(10)) + 20]).insertPerson()
    }
    print(CFAbsoluteTimeGetCurrent() - start)
}

非常耗时,大概需要1分钟左右


查询数据

  • 准备伪代码
/// 加载 Person 对象数组
class func loadPersons() -> [Person]? {
    // 1. 从数据库获取字典数组
    SQLiteManager.sharedManager.execRecordSet("SELECT id, name, age FROM T_Person;")

    // 2. 遍历数组,字典转模型

    return nil
}
  • 在 SQLiteManager 中添加查询语句,准备结果集
/// 执行 SQL 返回结果集
///
/// - parameter sql: SQL
///
/// - returns: 字典数组
func execRecordSet(sql: String) -> [[String: AnyObject]]? {

    // 1. 准备(预编译) SQL
    var stmt: COpaquePointer = nil
    /**
        1. 已经打开的数据库句柄
        2. 要执行的 SQL
        3. 以字节为单位的 SQL 最大长度,传入 -1 会自动计算
        4. SQL 语句地址
        5. 未使用的指针地址,通常传入 nil
    */
    if sqlite3_prepare_v2(db, sql, -1, &stmt, nil) != SQLITE_OK {
        print("准备 SQL 失败")
        return nil
    }

    print("OK")

    // 释放语句
    sqlite3_finalize(stmt)

    return nil
}
  • 代码小结

    • 这一部分的工作可以看作是对字符串的 SQL 语句进行编译,并且检查是否存在语法问题
    • 编译成功后通过 sqlite3_step 执行 SQL,每执行一次,获取一条记录
    • 通过 while 循环直至执行完毕
    • 注意,指令执行完毕后需要释放
  • 单步执行

// 2. 单步执行获取结果集内容
var index = 0
while sqlite3_step(stmt) == SQLITE_ROW {
    print(index++)
}
  • 遍历 stmt 中的列数以及每列的列名 & 数据类型
// 2. 单步执行获取结果集内容
while sqlite3_step(stmt) == SQLITE_ROW {
    // 1> 结果集列数
    let colCount = sqlite3_column_count(stmt)

    // 2> 遍历每一列
    for col in 0..<colCount {
        let cName = sqlite3_column_name(stmt, col)
        let name = String(CString: cName, encoding: NSUTF8StringEncoding)!

        print(name + "\t", appendNewline: false)
    }
    print("\n", appendNewline: false)
}
  • 根据 数据类型 获取数据
for col in 0..<colCount {
    // 1) 字段名
    let cName = sqlite3_column_name(stmt, col)
    let name = String(CString: cName, encoding: NSUTF8StringEncoding)!

    // 2) 字段类型
    let type = sqlite3_column_type(stmt, col)

    // 3) 根据类型获取字段内容
    var v: AnyObject? = nil
    switch type {
    case SQLITE_INTEGER:
        v = Int(sqlite3_column_int64(stmt, col))
    case SQLITE_FLOAT:
        v = sqlite3_column_double(stmt, col)
    case SQLITE3_TEXT:
        let cText = UnsafePointer<Int8>(sqlite3_column_text(stmt, col))
        v = String(CString: cText, encoding: NSUTF8StringEncoding)
    case SQLITE_NULL:
        v = NSNull()
    default:
        print("不支持的格式")
    }

    print(name + "\t" + String(type) + "\t \(v) \t", appendNewline: false)
}
print("\n", appendNewline: false)
  • 抽取创建结果集代码
/// 从 stmt 获取记录字典
///
/// - parameter stmt: stmt
///
/// - returns: 返回记录集字典
private func recordDict(stmt: COpaquePointer) -> [String: AnyObject] {
    // 1> 结果集列数
    let colCount = sqlite3_column_count(stmt)

    // 2> 遍历每一列 - 创建字典
    var record = [String: AnyObject]()

    for col in 0..<colCount {
        // 1) 字段名
        let cName = sqlite3_column_name(stmt, col)
        let name = String(CString: cName, encoding: NSUTF8StringEncoding)!

        // 2) 字段类型
        let type = sqlite3_column_type(stmt, col)

        // 3) 根据类型获取字段内容
        var v: AnyObject? = nil
        switch type {
        case SQLITE_INTEGER:
            v = Int(sqlite3_column_int64(stmt, col))
        case SQLITE_FLOAT:
            v = sqlite3_column_double(stmt, col)
        case SQLITE3_TEXT:
            let cText = UnsafePointer<Int8>(sqlite3_column_text(stmt, col))
            v = String(CString: cText, encoding: NSUTF8StringEncoding)
        case SQLITE_NULL:
            v = NSNull()
        default:
            print("不支持的格式")
        }

        record[name] = v
    }

    return record
}
  • 重构后的代码
/// 执行 SQL 返回结果集
///
/// - parameter sql: SQL
///
/// - returns: 字典数组
func execRecordSet(sql: String) -> [[String: AnyObject]]? {

    // 1. 准备(预编译) SQL
    var stmt: COpaquePointer = nil
    if sqlite3_prepare_v2(db, sql, -1, &stmt, nil) != SQLITE_OK {
        print("准备 SQL 失败")
        return nil
    }

    // 2. 单步执行获取结果集内容
    // 2.1 结果集
    var recordset = [[String: AnyObject]]()
    // 2.2 遍历结果集
    while sqlite3_step(stmt) == SQLITE_ROW {
        recordset.append(recordDict(stmt))
    }

    // 3. 释放语句
    sqlite3_finalize(stmt)

    return recordset
}
  • 在 Person 模型中加载 Person 列表
/// 加载 Person 对象数组
class func loadPersons() -> [Person]? {
    // 1. 从数据库获取字典数组
    guard let array = SQLiteManager.sharedManager.execRecordSet("SELECT id, name, age FROM T_Person;") else {
        return nil
    }

    // 2. 遍历数组,字典转模型
    var persons = [Person]()
    for dict in array {
        persons.append(Person(dict: dict))
    }

    return persons
}

批量插入

在 SQLite 中如果要批量插入数据,通常需要引入 事务的概念

事务

  • 在准备做大规模数据操作前,首先开启一个事务,保存操作前的数据库的状态
  • 开始数据操作
  • 如果数据操作成功,提交事务,让数据库更新到数据操作后的状态
  • 如果数据操作失败,回滚事务,让数据库还原到操作前的状态
  • 事务处理函数
///  开启事务
func beginTransaction() -> Bool {
    return execSQL("BEGIN TRANSACTION;")
}

///  提交事务
func commitTransaction() -> Bool {
    return execSQL("COMMIT TRANSACTION;")
}

///  回滚事务
func rollBackTransaction() -> Bool {
    return execSQL("ROLLBACK TRANSACTION;")
}
  • 修改插入多人记录函数
///  插入许多人
private func insertManyPerson() {
    print("开始")
    let start = CFAbsoluteTimeGetCurrent()
    SQLiteManager.sharedSQLiteManager.beginTransaction()
    for i in 0..<100000 {
        let person = Person(dict: ["name": "lisi-" + String(i), "age": 18, "height": 1.8])
        person.insertPerson()
    }
    SQLiteManager.sharedSQLiteManager.commitTransaction()
    print("结束 " + String(CFAbsoluteTimeGetCurrent() - start))
}

测试结果不到 4s

  • 测试回滚
///  插入许多人
private func insertManyPerson() {
    print("开始")
    let start = CFAbsoluteTimeGetCurrent()

    SQLiteManager.sharedSQLiteManager.beginTransaction()
    for i in 0..<100000 {
        let person = Person(dict: ["name": "lisi-" + String(i), "age": 18, "height": 1.8])
        person.insertPerson()

        if i == 10000 {
            SQLiteManager.sharedSQLiteManager.rollBackTransaction()
            break
        }
    }
    SQLiteManager.sharedSQLiteManager.commitTransaction()

    print("结束 " + String(CFAbsoluteTimeGetCurrent() - start))
}

批量更新

  • 批量更新函数 - 绑定参数
func batchUpdate(sql: String, params: CVarArgType...) -> Bool {

    let cSQL = sql.cStringUsingEncoding(NSUTF8StringEncoding)!
    var stmt: COpaquePointer = nil

    if sqlite3_prepare_v2(db, cSQL, -1, &stmt, nil) == SQLITE_OK {
        // 绑定参数
        var col: Int32 = 1
        for arg in params {
            if arg is Int {
                sqlite3_bind_int64(stmt, col, sqlite3_int64(arg as! Int))
            } else if arg is Double {
                sqlite3_bind_double(stmt, col, (arg as! Double))
            } else if arg is String {
                let cStr = (arg as! String).cStringUsingEncoding(NSUTF8StringEncoding)
                sqlite3_bind_text(stmt, col, cStr!, -1, SQLITE_TRANSIENT)
            } else if arg is NSNull {
                sqlite3_bind_null(stmt, col)
            }
            col++
        }
    }

    sqlite3_finalize(stmt)

    return true
}

绑定字符串

  • 如果第5个参数传递 NULL 或者 SQLITE_STATIC 常量,SQlite 会假定这块 buffer 是静态内存,或者客户应用程序会小心的管理和释放这块 buffer,所以SQlite放手不管
  • 如果第5个参数传递的是 SQLITE_TRANSIENT 常量,则SQlite会在内部复制这块buffer的内容。这就允许客户应用程序在调用完 bind 函数之后,立刻释放这块 buffer(或者是一块栈上的 buffer 在离开作用域之后自动销毁)。SQlite会自动在合适的时机释放它内部复制的这块 buffer

  • 由于在 SQLite.h 中 SQLITE_TRANSIENT 是以宏的形式定义的,而在 swift 中无法直接利用宏传递函数指针,因此需要使用以下代码转换一下

swift 1.2
private let SQLITE_TRANSIENT = sqlite3_destructor_type(COpaquePointer(bitPattern: -1))
swift 2.0
private let SQLITE_TRANSIENT = unsafeBitCast(-1, sqlite3_destructor_type.self)
  • 而绑定字符串的函数必须写成(OC中可以使用 NULL,是因为 OC 中以 @"" 定义的函数都是保存在静态区的)
sqlite3_bind_text(stmt, index, cStr, -1, SQLITE_TRANSIENT)
  • 单步执行
var result = true
if sqlite3_step(stmt) != SQLITE_DONE {
    print("插入错误")
    result = false
}

// 语句复位
if sqlite3_reset(stmt) != SQLITE_OK {
    print("语句复位错误")
    result = false
}

sqlite3_finalize(stmt)

return result

注意:执行结束后,一定要对语句进行复位,以便后续查询语句能够继续执行

函数小结

  • 列数的计数从 1 开始
  • 对于数据更新操作,单步执行正确的结果是 SQLITE_DONE
  • 每单步执行之后,需要做一次 reset 操作

使用预编译 SQL 批量插入数据

  • 批量插入数据
///  批量插入
private func batchInsert() {
    print("开始")
    let start = CFAbsoluteTimeGetCurrent()

    let manager = SQLiteManager.sharedSQLiteManager
    let sql = "INSERT INTO T_Person (name, age, height) VALUES (?, ?, ?);"

    // 开启事务
    manager.beginTransaction()

    for _ in 0..<10000 {

        if !manager.batchUpdate(sql, params: "zhangsan", 18, 1.8) {
            manager.rollBackTransaction()
            break
        }
    }
    manager.commitTransaction()

    print("结束 " + String(CFAbsoluteTimeGetCurrent() - start))
}

运行测试,执行结果只需要 0.1s

多线程

  • 定义队列
/// 操作队列
private let queue = dispatch_queue_create("com.itheima.sqlite", DISPATCH_QUEUE_SERIAL)
  • 队列执行
///  队列更新
///
///  :param: action 在后台执行的任务
func queueUpdate(action: (manager: SQLiteManager) -> ()) {

    dispatch_async(queue) { [unowned self] in
        // 1. 开启事务
        self.beginTransaction()

        action(manager: self)

        // 2. 提交事务
        self.commitTransaction()
    }
}
  • 测试后台更新
private func queueUpdate() {
    print("开始")
    let start = CFAbsoluteTimeGetCurrent()

    SQLiteManager.sharedSQLiteManager.queueUpdate { (manager) -> () in

        let sql = "INSERT INTO T_Person (name, age, height) VALUES (?, ?, ?);"

        for i in 0..<10000 {
            if !manager.batchUpdate(sql, params: "zhangsan", 18, 1.8) {
                manager.rollBackTransaction()
                break
            }

            if i == 1000 {
                manager.rollBackTransaction()
                break
            }
        }

        print(NSThread.currentThread())
        print("结束 " + String(CFAbsoluteTimeGetCurrent() - start))
    }
}

注意:SQLite 数据库不允许同时并发写入输入,如果用多线程,也必须使用串行队列进行操作


FMDB

使用框架

官网地址

https://github.com/ccgus/fmdb

直接拖拽

  • 将 fmdb 文件夹拖入项目
  • 建立桥接文件
  • 将 Swift extensions 拖入项目

Podfile

  • 不推荐
use_frameworks!
pod 'FMDB', :git => 'https://github.com/robertmryan/fmdb.git'

代码演练

  • 除了查询都使用 executeUpdate
  • 查询使用 executeQuery
let documentsFolder = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
let path = documentsFolder.stringByAppendingPathComponent("test.sqlite")

let database = FMDatabase(path: path)

if !database.open() {
    println("Unable to open database")
    return
}

if !database.executeUpdate("create table test(x text, y text, z text)", withArgumentsInArray: nil) {
    println("create table failed: \(database.lastErrorMessage())")
}

if !database.executeUpdate("insert into test (x, y, z) values (?, ?, ?)", withArgumentsInArray: ["a", "b", "c"]) {
    println("insert 1 table failed: \(database.lastErrorMessage())")
}

if !database.executeUpdate("insert into test (x, y, z) values (?, ?, ?)", withArgumentsInArray: ["e", "f", "g"]) {
    println("insert 2 table failed: \(database.lastErrorMessage())")
}

if let rs = database.executeQuery("select x, y, z from test", withArgumentsInArray: nil) {
    while rs.next() {
        let x = rs.stringForColumn("x")
        let y = rs.stringForColumn("y")
        let z = rs.stringForColumn("z")
        println("x = \(x); y = \(y); z = \(z)")
    }
} else {
    println("select failed: \(database.lastErrorMessage())")
}

database.close()
  • 队列演练
let queue = FMDatabaseQueue(path: "/Users/liufan/Desktop/my.db")

let sql = "insert into t_person (name, age) VALUES (?, ?);"
queue.inTransaction { (db, rollBack) -> Void in
    db.executeUpdate(sql, "lisi", 28)
    db.executeUpdate(sql, "wangwu", 48)

    rollBack.memory = true
}

queue.inDatabase { (db) -> Void in
    if let result = db.executeQuery("select * from t_person") {

        while result.next() {
            let name = result.objectForColumnName("name")
            let age = result.intForColumn("age")

            print("\(name) \(age)")
        }
    }
}

要设置 rollBack 可以使用 rollBack.memory = true



  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值