1. 程式人生 > >說說SQLite在移動開發的那些事兒

說說SQLite在移動開發的那些事兒

SQLite

簡介

  • 是一款輕型的資料庫
  • 設計目標是嵌入式
  • 佔用資源少
  • 處理速度快
  • 當前版本 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

使用框架

官網地址

直接拖拽

  • 將 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、 安卓瀏覽器看背景圖片,有些裝置會模糊。 用同等比例的圖片在PC機上很清楚,但是手機上很模糊,原因是什麼呢? 經過研究,是devicePixelRatio作怪,因為手機解析度太小,如果按照解析度來顯示網頁,這樣字會非常小,所以蘋果當初就把iPhone 4的9

蘋果開發那些事兒-D-U-N-S 號申請

    這些天追劇去了,《大好時光》,我喜歡茅小春,其實我真正喜歡的是王曉晨哈。     言歸正傳,現在說說鄧白氏編碼申請的相關事兒。     去年初,搞了本《企業級iOS應用開發實戰》,開頭第一篇就講了申請公司開發者計劃和企業開發者計劃需要鄧白氏編碼,但是書中說的是需要一

【Spring註解驅動開發】聊聊Spring註解驅動開發那些事兒

## 寫在前面 > 今天,面了一個工作5年的小夥伴,面試結果不理想啊!也不是我說,工作5年了,問多執行緒的知識:就只知道繼承Thread類和實現Runnable介面!問Java集合,竟然說HashMap是執行緒安全的!問MySQL的MyISAM儲存引擎和InnoDB儲存引擎的區別,竟然說成是MyISA

說說SQLite移動開發那些事兒

SQLite 簡介 是一款輕型的資料庫設計目標是嵌入式的佔用資源少處理速度快當前版本 3.8.10.2,MAC 內建已經安裝了 SQLite什麼是 SQLite? SQLite 是一個程序內的庫,

移動端頁面布局的那些事兒

默認值 oat 暫時 占用 基於 正常 cep 兩個 均衡 移動端頁面布局的那些事兒 http://www.xiaoxiangzi.com/Programme/CSS/4298.html 一. viewport 什麽是viewport 簡單來講,viewport就是瀏覽

阿裏聚安全移動安全專家分享:APP渠道推廣作弊攻防那些事兒

工作人員 感染 androi 時代 right 有效 市場 實驗 刷的 移動互聯網高速發展,要保持APP持續並且高速增長所需的成本也越來越高。美團網CEO在今年的一次公開會議上講到:“2017年對移動互聯網公司來說是非常恐的。”。主要表現在三個方面,手機數不漲了、競品太多、

關於開發入門的那些事兒2

開發入門 eclips 自己 設置 C# 集成 F5 env rate 說一個新的名詞,開發工具,有了開發工具,你後續的工作才會更輕松。 先簡介一個新的東西IDE全名 Integrated Dexelopment Environment 翻譯過來就是集成開發環境,這是用

安卓開發之應用上架的那些事兒

應用市場 需要 不一致 安裝失敗 安卓開發 安卓 app 上架 那些事兒   工作的過程中遇到這樣的需求:要上架這個App到應用市場中,但是,應用市場中已經有了叫同樣名稱的App了,新上架的要頂替原來的,並且用戶安裝時以前下載到手機裏的這個名稱的app被替換掉。   這時我

ESP32那些事兒(二):磨刀不誤砍柴功-做好專案開發前的準備工作

       如果你是第一次接觸FreeRTOS和ESPRESSIF的產品,例如我,那還是要先來個整體印象,然後再逐個深化。做專案的都知道,老闆們是不允許我們四平八穩的研究完然後再開始專案。        那也不妨礙咱們

手機移動端展現報表的那些事兒

在各企業中或者電商平臺的商家,業務方,每天都有大量的人需要線上檢視大量的指標,用於監控、分析關鍵業務資料的發展趨勢。同時,又有著能夠隨時隨地,方便快捷的檢視分析資料的訴求。我們習慣於,使用潤乾報表在 PC 端或大屏中展現,但是你知道嗎?潤乾報表 V2018 是以 HTML5 方式輸出,不僅支援在 P

專案中站立會議和故事牆的那些事兒—敏捷開發

專案組一直在推敏捷開發,但發現一個關於每日例會的問題。 場景:   每日例會是早上9:00, 把大家召集起來,這時有個主持人(每日輪流), 一個一個詢問團隊成員昨天做了什麼,今天做了什麼, 並記錄在一個本子上。   有時大家比較忙時,主持人會一個個去詢問團隊成員工作狀況。

【本人禿頂程式設計師】開發銀聯支付之前要做的那些事兒

←←←←←←←←←←←← 快!點關注!!! 銀聯支付 之前做過一次銀聯支付的配置工作,然而第二次配置,雖然有點印象,但最終還是翻查了半天資料和程式碼,才完成了配置。這裡對銀聯支付所需做一個簡單的整理說明,以便後期查閱,同時分享給大家。 至於如何申請賬號,那是公司層面的操作,這裡我

【FPGA黑金開發板】Verilog HDL那些事兒--串列埠模組(十一)

關於FPGA串列埠通訊的問題,老實說看了好多資料,都沒有找到滿意的結果,直到在黑金動力論壇中看到這篇文章,一時竟有豁然開朗之感,老實說黑金寫的文章這的很不錯,本人在裡面受益頗多,在此對黑金的工作人員表示致敬! 3.4 實驗十:串列埠模組 微控制器?串列埠?這些已經是眾所

關於iOS開發中反編譯的那些事兒

現在僅討論程式碼層面的反編譯 1、反編譯工具 (1)class-dump 主要用來反編譯一個庫檔案或者app的方法名、屬性等宣告(即.h檔案,強大的是反編譯出來的.h不僅僅包含標頭檔案中的宣告,.m中的function方法名稱也同樣能夠反編譯出來)。class-dump的安

Hadoop那些事兒(二)---MapReduce開發環境搭建

上一篇文章介紹了在ubuntu系統中安裝Hadoop的偽分散式環境,這篇文章主要為MapReduce開發環境的搭建流程。 1.HDFS偽分散式配置 使用MapReduce時,如果需要與HDFS建立連線,及使用HDFS中的檔案,還需要做一些配置。 首先進

【FPGA黑金開發板】NIOSII那些事兒--基於AVALON匯流排的IP定製(十七)

簡介       NIOS II是一個建立在FPGA上的嵌入式軟核處理器,除了可以根據需要任意新增已經提供的外設外,使用者還可以通過定製使用者邏輯外設和定製使用者指令來實現各種應用要求。這節我們就來研究如何定製基於Avalon匯流排的使用者外設。 SOPC Builder提供了一個元件編輯器,通過這個

專案開發中git使用的那些事兒

Git作為一個版本工具,現在為大多數公司的技術團隊用於程式碼的版本控制,對其的掌握十分重要,這篇博文旨在從git在工作中的應用場景上做一些基本的介紹,以供公司git新人蔘考,快速上手。 Git一般分為遠端資料庫和本地資料庫兩種,從團隊程式設計角度來看,本地資料庫為個人開發所

ESP32那些事兒(六):功能開發之藍芽及WiFi功能

    我們的裝置類似藍芽音箱的功能,在使用a2dp的過程中也是有很多bug的,比方說暫停後雜音、藍芽聲音卡頓、系統panic等,希望後續的esp-idf都已經解決了,如果遇到類似的問題,我們也可以在部落格中討論,在此就不一一贅述了。

安卓關於圖片壓縮的那些事兒,希望給每個安卓開發人員一些幫助

從事安卓開發也有幾年了,本人喜歡開門見山,此篇文章是處理以java語言下的安卓開發過程中圖片壓縮問題。 圖片載入在我們的開發過程中都是一個記憶體大戶,以至於我們載入每一個圖片bitmap物件的時候都應該進行回收以減少記憶體的佔用,而如果單張圖片的大小載入在記憶體

那些年,那些事兒,我們一起php

做出 幹凈 程序員 彈出 高考 那種 家務 每次 哥哥 謹以此文悼念自己的堅持了8年的初戀以及逝去的青春。 晚上七點,寫完日報,上傳今天更新的代碼。簡單的收拾,擠上了回家的地鐵,天氣慢慢變熱了,地鐵中的味道也豐富了起來。站在角落,拿出手機,也就這段時間我可以玩