sqlite3基本用法(FMDB底层)详解

看这篇文章之前你得:

  • 已经了解了基本的SQL语法, 基本会使用
    • 推荐一个桌面数据库软件: Navigate Premium(有需要的留言)
  • 知道移动端开发, SQLite是干什么的

直接把我xcode中的文件考过来了, 已经写得很详细了, (可以直接考到xcode中, 这样就能按分类来看)耐心看吧…

import UIKit

class SQLiteManager: NSObject {

    static let shareInstance: SQLiteManager = SQLiteManager()
    
    /*数据库基本操作:
            1. 数据库文件
            2. 新建表
            3. 指定字段名称
            4. 添加记录
    */
    
    /// 创建数据库文件
    var db: COpaquePointer = nil
    
    
    // MARK:- 打开或创建数据库文件(一般就在appdelegate中打开一次就行了)
    func openDB(dbName: String) -> Bool {
        
        // 1. 生成db文件的路径, 没有就会创建一个
        let path = dbName.cacheDir()
        
        guard let cPath = path.cStringUsingEncoding(NSUTF8StringEncoding) else {
            return false
        }
        
        // 2. 创建数据库文件
        // 专门用户打开数据库文件, 如果不存在系统会自动创建一个新的
        // 第一个参数: 数据库文件的路径
        // 第二个参数: 数据库的指针(句柄), 只要打开成功系统就会将打开的数据库赋值给该指针
        // 后续所有关于数据库的操作, 都依赖于该指针
        // sqlite3_open方法会返回一个整型的值, 告诉我们是否打开成功
        
        if sqlite3_open(cPath, &db) != SQLITE_OK {
            return false
        }
        
        // 3. 新建表
        if !createTable() {
            return false
        }
        
        return true
    }
    
    // MARK:- 创建表
    func createTable() -> Bool {
        
        // 1.定义sql语句(可以在navcat中写好考进来, 换行写, 一目了然)
        let sqlStr = "CREATE TABLE 'T_Person' (" +
        "'name' TEXT," +
        "'age' INTEGER," +
        "'id' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT" +
        ");"
        
        // 执行创建表的sql语句
        /*
        第1个参数: 一个已经打开的数据库
        第2个参数: 需要执行的SQL语句
        第3个参数: 执行SQL之后的回调
        第4个参数: 第三个参数的第一个参数
        第5个参数: 错误信息的指针, 如果执行过程中发生错误就会给传入的指针赋值
        */
        
        if sqlite3_exec(db, sqlStr.cStringUsingEncoding(NSUTF8StringEncoding)!, nil, nil, nil) != SQLITE_OK {
            
            return false
        }
        
        return true
    }
    
    // MARK:- 执行sql语句
    /*
    第1个参数: 一个已经打开的数据库
    第2个参数: 需要执行的SQL语句
    第3个参数: 执行SQL之后的回调
    第4个参数: 第三个参数的第一个参数
    第5个参数: 错误信息的指针, 如果执行过程中发生错误就会给传入的指针赋值
    */
    
    /*
      写入: sql语句
        let sqlStr =  "INSERT INTO T_Person \n" +
        "(name, age) \n" +
        "VALUES \n" +
        "('\(name!)', \(age));"
    */
    
    func execSQL(sqlStr: String) -> Bool {
        
        if sqlite3_exec(db, sqlStr.cStringUsingEncoding(NSUTF8StringEncoding)!, nil, nil, nil) != SQLITE_OK {
            return false
        }
        return true
    }
    
    
    // MARK:- 执行查询语句
    /*
      查询语句(条件语句的使用where)
         let sqlStr = "SELECT id, name, age FROM T_Person;"
    */
    func querySQL(sqlStr: String) -> [[String: AnyObject]]? {
            
        // 1. 预编译
        /*
        第一个参数: 一个已经打开的数据库
        第二个参数: 需要执行的SQL语句
        第三个参数: 需要执行的SQL语句的长度, 传入-1系统会自动计算
        第四个参数: 结果集指针(以后所有获取数据的操作, 都依赖于该指针)
        第五个参数: 没用过
        */

        // 结果集, 查新结果都依赖此
        var stmt: COpaquePointer = nil
        if sqlite3_prepare_v2(db, sqlStr.cStringUsingEncoding(NSUTF8StringEncoding)!, -1, &stmt, nil) != SQLITE_OK {
            return nil
        }
        
        // 2. 利用结果集取出查询结果
        // 只要是调用一次sqlite3_step方法, 系统就会利用stmt取出一条数据
        // 如果该方法的返回值等于SQLITE_ROW, 就代表可以获取数据
        
        var array = [[String: AnyObject]]()
        
        while sqlite3_step(stmt) == SQLITE_ROW {
            
            // 1. 获取当前一共多少列
            let count = sqlite3_column_count(stmt)
            // 定义一条空数据信息, 用于保存
            var dict = [String: AnyObject]()
            
            for i in 0..<count {
                // 2. 获取每一列的名称
                let cName = sqlite3_column_name(stmt, i)
                let name = String(CString: cName, encoding: NSUTF8StringEncoding)!
                
                // 3. 获取每一列的数据类型
                let type = sqlite3_column_type(stmt, i)
                
                switch type
                {
                case SQLITE_INTEGER:
                    let intValue = sqlite3_column_int(stmt, i)
                    dict[name] = Int(intValue)
                case SQLITE_FLOAT:
                    let floatValue = sqlite3_column_double(stmt, i)
                    dict[name] = floatValue
                case SQLITE_BLOB:
                    print("blob") // blob是二进制数据, 一般不做sqlite数据存储对象
                case SQLITE_NULL:
                    dict[name] = NSNull()
                case SQLITE_TEXT:
                    let cTextValue = UnsafePointer<Int8>(sqlite3_column_text(stmt, i))
                    let textValue = String(CString: cTextValue, encoding: NSUTF8StringEncoding)
                    dict[name] = textValue
                default:
                    print("other")
                }
            }
            
            array.append(dict)
        }
        return array
        
    }
    
    
    // MARK:- 数据库的事务处理(防止多线程不安全)
            /*
            1.事务能够保证多条SQL语句, 要么一起成功, 要么一起失败
            2.想要保证多条SQL语句一起成功或者一起失败, 就必须在执行多条SQL语句之前开启事物
            只有遇到了提交事务, 数据库才会修改数据
            3.如果中途遇到了意外, 我们可以通过回滚事物来还原以前的数据
            4.提高性能
            */

    //      - sqlite3执行sql语句是默认开启事务处理的, 当我们多条数据时, 应手动开启事务, 这样就只要开启一次, 提升性能
    //      - 重复的开启和提交是非常非常消耗性能的, 所以为了避免这种问题, 我们可以自己开启事物, 只要我们自己开了事物 ,系统就不会自动帮我们开启事物了
    
    // 1. 开启事务
    func beginTransaction() -> Bool {
        return execSQL("BEGIN TRANSACTION;")
    }
    
    // 2. 提交事务
    func commit() -> Bool {
        return execSQL("COMMIT TRANSACTION;")
    }
    
    // 3. 回滚事务
    func rollBack() -> Bool {
        return execSQL("ROLLBACK TRANSACTION;")
    }
    
    
    // MARK:- 数据写入之动态绑定插入, 提高性能, 多数SQLite框架都是采用这种方式来写入数据, FMDB也是这么干的
    private let SQLITE_TRANSIENT = unsafeBitCast(-1, sqlite3_destructor_type.self)
    
    func insert(sql: String, args: CVarArgType...) -> Bool
    {
        
        var stmt: COpaquePointer = nil
        // 1.预编译SQL语句
        if sqlite3_prepare_v2(db, sql.cStringUsingEncoding(NSUTF8StringEncoding)!, -1, &stmt, nil) != SQLITE_OK
        {
            print("预编译失败")
            return false
        }
        // 2.绑定参数
        var index: Int32 = 1
        for objc in args
        {
            if objc is Int
            {
                /*
                第一个参数: stmt对象
                第二个参数: 需要绑定的字段的索引
                注意: 字段索引从1开始
                第三个参数: 需要绑定到SQL上的值
                */
                sqlite3_bind_int64(stmt, index, sqlite3_int64(objc as! Int))
                
            }else if objc is Double
            {
                sqlite3_bind_double(stmt, index, objc as! Double)
            }else if objc is String
            {
                /*
                第一个参数: stmt对象
                第二个参数: 需要绑定的字段的索引
                第三个参数: 需要绑定到SQL上的值
                第四个参数: 没用过
                第五个参数: bind方法中如何处理传入的字符串
                处理方式有两种: 1.方法内部不管传入的字符串, 不会对传入的字符串进行retain(copy)操作, 所以在使用该字符串时字符串可能已经释放了, 此时系统就会随便设置一个值
                2.方法内部会管理传入的字符串, 所以在使用字符串时字符串不会被释放, 使用完毕之后bind方法内部会自动释放该字符串
                #define SQLITE_STATIC      ((sqlite3_destructor_type)0)  上述1
                #define SQLITE_TRANSIENT   ((sqlite3_destructor_type)-1)  上述2
                
                */
                let text = objc as! String
                let cText = text.cStringUsingEncoding(NSUTF8StringEncoding)!
                sqlite3_bind_text(stmt, index, cText, -1, SQLITE_TRANSIENT)
            }
            
            index++
        }
        
        // 3.执行SQL语句
        if sqlite3_step(stmt) != SQLITE_DONE
        {
            print("执行SQL语句失败")
            return false
        }
        // 4.重置STMT
        sqlite3_reset(stmt)
        // 5.关闭STMT
        sqlite3_finalize(stmt)
        
        // 6.返回结果
        return true
    }
    
    // MARK:- sqlite异步执行
    // 1. 创建一个串行队列
    let queue = dispatch_queue_create("sqliteQueue", DISPATCH_QUEUE_SERIAL)
    
    /// 2. 异步执行SQL语句
    func execSQLQueue(action: (db: COpaquePointer) -> ()) {
        
        dispatch_async(queue) { () -> Void in
            action(db: self.db)
            // 在闭包中执行SQL语句
        }
    }
    
}

    原文作者:德蒙_托尔斯泰
    原文地址: https://www.jianshu.com/p/adcd86090d1c
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞