基于sqlite数据库

Posted ZH952016281

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于sqlite数据库相关的知识,希望对你有一定的参考价值。

#ios中的SQLite3的封装与详细应用


SQLite是一个开源的嵌入式关系数据库,特点是易使用、高效、安全可靠、可移植性强。



##IOS中的本地持久化存储

**NSUserDefault**:一般用于存储小规模数据、业务逻辑弱的数据。


**keychain**: 苹果提供的可逆存储,因为有着只要app不重装系统、可以同步iCloud的特性,一般用来对用户的标识符或者一些需要加密的小数据进行存储。


**归档**:主要原理是对数据进行序列化、反序列化操作后,写入、读出数据。方便便捷易使用,缺点查询、更改数据耗时耗性能。


**数据库**:主要的有三种sqlite3core datarealm。其中core data只是xcodesqlite的界面化的封装原理相似,realm[官方文档](https://realm.io/cn/docs/swift/latest/#model).


关于sqlite本文将主要介绍。


##SQLite3中主要函数介绍

sqlite3_open(文件路径,sqlite3 **):文件名若不存在,则会自动创建


sqlite3_close(sqlite3 *):关闭数据库


sqlite3__finalize(sqlite3_stmt *pStmt): 释放数据库


sqlite3_errmsg(sqlite3*):输出数据库错误


sqlite3__exec(sqlite3 *,const char *sql, sqlite3_callback,void *,char **errmsg)


参数1open函数得到的指针。


参数2:一条sql语句


参数3sqlite3_callback是回调,当这条语句执行后,sqlite3会调用你提供的这个函数,回调函数


参数4void *是自己提供的指针,可以传递任何指针到这里,这个参数最终会传到回调函数里面,如果不需要传到回调函数里面,则可以设置为NULL


参数5:错误信息,当执行失败时,可以查阅这个指针


sqlite3_prepare_v2(sqlite3 *db,const char *zSql, int nByte,sqlite3_stmt **ppStmt,const char **pzTail)


参数3:表示前面sql语句的长度,如果小于0sqlite会自动计算它的长度


参数4sqlite3_stmt指针的指针,解析后的sql语句就放在该结构里


参数5:一般设为0


sqlite3_step(sqlite3_stmt*)


参数为sqlite3_prepare_v2中的sqlite3_stmt

返回SQLITE_ROW 表示成功


sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n,                                                                         void(*)(void*)):


参数1sqlite3_prepare_v2中的sqlite3_stmt


参数2:对应行数


参数3:对应行数的值


参数4:对应行数的值的长度,小于0自动计算长度


参数5:函数指针,主要处理特殊类型的析构


sqlite3_key( sqlite3 *db, const void *pKey, int nKey)


参数2:密钥


参数3:密钥长度

##swiftc的类型转换

int => CInt


char => CChar / CSignedChar


char* => CString


unsigned long = > CUnsignedLong


wchar_t => CWideChar


double => CDouble


T* => CMutablePointer


void* => CMutableVoidPointer


const T* => CConstPointer


const void* => CConstVoidPointer


等等


程序中‘db’不能为空,如果为空,表示打开数据库失败或者关闭了数据库。


``` swift

@discardableResult   private func openDB() -> Bool

    

    if db == nil

        let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/\\(DB_NAME)"

        print(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])

        if sqlite3_open(path.cString(using: String.Encoding.utf8)!,&db) != SQLITE_OK

            closeDb()

            return false

        else

            //对数据库进行加密

            sqlite3_key(db, SAFE_KEY.cString(using: String.Encoding.utf8), Int32(SAFE_KEY.characters.count))

       

        sqlite3_busy_handler(db, (ptr,count) in

            

            usleep(500000)//如果获取不到锁,表示数据库繁忙,等待0.5

            print("sqlite is locak now,can not write/read.")

            return 1   //回调函数返回值为1,则将不断尝试操作数据库。

            

        , &db)

   

        

        

        return true

   

```

##通过sql执行数据库操作

由于防止多线程操作数据库,每次执行数据库操作添加同步锁。

sqlite3_exec函数基本上支持所有的数据库执行语句除了含有特殊类型的数据(二进制),含有特殊类型的数据会采用另一种方式处理下面会阐述。


``` swift

  @discardableResult public  func execSql(sql:String)->Bool

        

        objc_sync_enter(self)

        if  !self.openDB()

        objc_sync_exit(self)

        return false

       

        var err: UnsafeMutablePointer<Int8>? = nil

        if sqlite3_exec(db,sql.cString(using: String.Encoding.utf8)!,nil,nil,&err) != SQLITE_OK

            if let error = String(validatingUTF8:sqlite3_errmsg(db))

               print("execute failed to execute  Error: \\(error)")

           

            objc_sync_exit(self)

            return false

       


        objc_sync_exit(self)

        return true

   



```


##查询数据库

按照数据库的行数依次查询,输出sql条件的所有数据


``` swift

 public  func querySql(sql:String) -> [[String:Any]]?

        objc_sync_enter(self)

        if  !self.openDB()

            objc_sync_exit(self)

            return nil

       

        var arr:[[String:Any]] = []

        var  statement: OpaquePointer? = nil

        if sqlite3_prepare_v2(db,sql.cString(using: String.Encoding.utf8)!,-1,&statement,nil) == SQLITE_OK

            while sqlite3_step(statement) == SQLITE_ROW

                let columns = sqlite3_column_count(statement)

                var row:[String:Any] = Dictionary()

                for i in 0..<columns

                    let type = sqlite3_column_type(statement, i)

                    let chars = UnsafePointer<CChar>(sqlite3_column_name(statement, i))

                    let name =  String.init(cString: chars!, encoding: String.Encoding.utf8)

                    

                    var value: Any

                    switch type

                    case SQLITE_INTEGER:

                        value = sqlite3_column_int(statement, i)

                    case SQLITE_FLOAT:

                        value = sqlite3_column_double(statement, i)

                    case SQLITE_TEXT:

                        let chars = UnsafePointer<CUnsignedChar>(sqlite3_column_text(statement, i))

                        value = String.init(cString: chars!)

                        

                    case SQLITE_BLOB:

                        let data = sqlite3_column_blob(statement, i)

                        let size = sqlite3_column_bytes(statement, i)

                        value = NSData(bytes:data, length:Int(size))

                    default:

                        value = ""

                        ()

                   

                    

                    row.updateValue(value, forKey: "\\(name!)")

               

                arr.append(row)

           

       

        sqlite3_finalize(statement)


        objc_sync_exit(self)

        if arr.count == 0

            return nil

        else

            return arr

       

        

   

```


##引入事务,加快数据库写入

事务(Transaction)是一个对数据库执行工作单元。事务(Transaction)是以逻辑顺序完成的工作单位或序列,可以是由用户手动操作完成,也可以是由某种数据库程序自动完成。

事务(Transaction)是指一个或多个更改数据库的扩展。例如,如果您正在创建一个记录或者更新一个记录或者从表中删除一个记录,那么您正在该表上执行事务。重要的是要控制事务以确保数据的完整性和处理数据库错误。

实际上,您可以把许多的 SQLite 查询联合成一组,把所有这些放在一起作为事务的一部分进行执行


**BEGIN TRANSACTION** 开启一个事务


**COMMIT TRANSACTION** 提交事务是否成功


**ROLLBACK TRANSACTION** 回滚事务,当数据库事务操作失败后,还原之前的操作。


注意:事务并不能批量优化查询速度。


``` swift

   public func doTransaction(exec: ((_ db:OpaquePointer)->())?)

        objc_sync_enter(self)

        if  !self.openDB()

            objc_sync_exit(self)

            return

       

        if exec != nil

             var err: UnsafeMutablePointer<Int8>? = nil

            if sqlite3_exec(db, "BEGIN TRANSACTION", nil, nil, &err) == SQLITE_OK

                exec!(db!)

                if sqlite3_exec(db, "COMMIT TRANSACTION", nil, nil, &err) == SQLITE_OK

                    print("提交事务成功")

                else

                    print("提交事务失败原因\\(err)")

                    if let error = String(validatingUTF8:sqlite3_errmsg(db))

                        print("execute failed to execute  Error: \\(error)")

                   

                    if sqlite3_exec(db, "ROLLBACK TRANSACTION", nil, nil, &err) == SQLITE_OK

                        print("回滚事务成功")

                   

               

            else

                if sqlite3_exec(db, "ROLLBACK TRANSACTION", nil, nil, &err) == SQLITE_OK

                    print("回滚事务成功")

               

           

           sqlite3_free(err)

           

       


        objc_sync_exit(self)

   

```



##SQLite3支持有限的 ALTER TABLE 操作

SQLite 有有限地 ALTER TABLE 支持。你可以使用它来在表的末尾增加一列,可更改表的名称。 如果需要对表结构做更复杂的改变,则必须重新建表。重建时可以先将已存在的数据放到一个临时表中,删除原表, 创建新表,然后将数据从临时表中复制回来。在增加表列时,需注意:因为app在市场上存在许多版本,各个版本的数据库表的结构可能存在梯度的差异,代码中使用就需要加入版本控制了。例如代码中添加一个‘id’字段。


```swift

        let defaults = UserDefaults.standard

        let version = defaults.value(forKey: USER_SQL_VERSION)//控制删除数据库的版本记录

        let update = defaults.value(forKey: USER_SQL_UPDATE)//控制增加数据库字段的版本记录

        if let version = version ,(version as! String) == USER_SQL_VERSION_CODE

            if let update = update as? String

                if  let intUpdate = Int(update.replacingOccurrences(of: ".", with: ""))

                    if intUpdate > 100

                        

                        if let _ = SQLiteTable.shared.querySql(sql: "select id from \\(USER_TABLENAME)")

                            

                        else

                             SQLiteTable.shared.execSql(sql: "ALTER TABLE \\(USER_TABLENAME)  ADD COLUMN  \\("id") INTEGER DEFAULT 0 IF NOT EXISTS")

                

                       

                        

                        

                   

               

               

           

            

            defaults.setValue(USER_SQL_UPDATE_CODE, forKey: USER_SQL_UPDATE)

        else

            SQLiteTable.shared.dropTable(tableName: USER_TABLENAME)

            defaults.setValue(USER_SQL_VERSION_CODE, forKey: USER_SQL_VERSION)

       

```

##SQLite3中添加索引


```swift

    /// 创建一张表

    ///

    /// - Parameters:

    ///   - tableName: 表名

    ///   - data: 数据字段

    ///   - dataArray: 添加索引的字段

    /// - Returns: 是否成功   

public func createTable(tableName:String, andColoumName data:[String:String] ,andAddIndex dataArray:[String]) -> Bool

        let result = self.createTable(tableName: tableName, andColoumName: data)

        dataArray.forEach (str) in

            self.execSql(sql: "CREATE INDEX IF NOT EXISTS index_\\(str)  ON \\(tableName) (\\(str))")

       

        

         return result

        

   

    

```

##处理Dada类型数据

sqlite3__exec函数并不是万能的,它就无法处理二进制数据。处理二进制数据,需要的是另一种方法。

先对表的数据解析然后绑定到结构体中,然后对数据库进行INSERT DELETE 或者 UPDATE操作。

以上是关于基于sqlite数据库的主要内容,如果未能解决你的问题,请参考以下文章

使用 sqlite3 构建大规模 OR/AI(信息检索/人工智能)系统

SQLite-lab:基于SQLite设计的SQL注入实践靶场

使用 swiftyJSON 和 sqlite.swift 将基于 json 的数据添加到 sqlite 数据库中

ios中sqlite怎么存储数据的

如何从基于 sqlite 的日期约束中提取数据

在应用程序运行时切换 Core Data sqlite 文件?数据不显示