swift微博第25天(SQLite)

作者: IIronMan | 来源:发表于2018-05-05 19:54 被阅读31次
    一、数据库的简单介绍
    • 数据库(Database)是按照数据结构来组织、存储和管理数据的仓库
    • 数据库可以分为2大种类:关系型数据库(主流)对象型数据库
    • 常用关系型数据库
      • PC 端:Oracle(收费)、MySQL(国内免费)、SQL Server 、Access、DB2、Sybase
      • 嵌入式\移动客户端:SQLite
    二、SQLite的介绍
    • SQLite是一款轻型的嵌入式数据库
    • 它占用的资源非常的低,在嵌入式的设备中,可能只需要几百K的内存就够了
    • 它的处理速度比Mysql、PostgreSQL这两款著名的数据库都还快
    三、SQL和SQL语句的介绍以及SQL语句的特点
    • SQL(structured query language):结构化查询语言,它还是一种对关系型数据库中的数据进行定义和操作的语言,而且它的语言简洁,语法简单,好学好用。

    • SQL语句:使用SQL语言编写出来的句子\代码,就是SQL语句,在程序运行的过程中,要想操作(增删改查)数据库中的数据,必须使用SQL语句

    • SQL语句的特点

      • 不区分大小写(比如数据库的person和PerSon是一样的)
      • 每条语句必须以分号结尾(;)
    • SQL中的关键字有很多

      select、insert、update、delete、from、create、where、desc、order等等

    • 数据库不可以使用关键字来命名表、字段

    • SQLlite数据存储类型:integer(整型值),real(浮点值),text(文本字符串),blob(二进制数据,例如文件)

    四、SQL语句的种类(DDL,DML,DQL)下面的学习会涉及到一个数据库工具Navicat,请到QQ群:584599353的文件库常用软件下载,找群主要注册码
    • 1、数据定义语句(DDL: Data Definition Language),包括create和drop等操作,在数据库中建表或删除表(create table和drop table)

      • 具体的使用:

        • 1.1、创建表的语句

          • IF NOT EXISTS 判断创建的表是否存在,只有不存在才会创建
          • PRIMARY KEY 代表该字典是主键
          • AUTOINCERMENT 代表字段自动增长
          CREATE TABLE IF NOT EXISTS T_Person2(
                  id INTEGER PRIMARY KEY AUTOINCERMENT,
                  name TEXT,
                  age INTEGER
          );
          
        • 1.2、删除表( IF EXISTS 判断是否存在,只有存在才会删除)

           DROP TABLE IF EXISTS T_Person2;
          
      • 2、数据操作语句(DML: Data Manipulation Language),包括insert、update、delete等操作,上面的三种操作分别用于添加、修改,删除表中的数据

        • 2.1、添加(插入数据:insert)(注意:前后两个括号是对应的,数据库中的字符串要加单引号)

           INSERT INTO T_person (name,age) VALUES ('wc',26);
          
        • 2.2、更新数据(update)(下面的代码会把数据库中的所有名字和年龄都改了)

           UPDATE T_person SET name = 'jk';更改了所有人名字
           UPDATE T_person SET name = '王冲' WHERE age = 30; 更改了年龄等于30人的名字
          
        • 2.3、删除数据(下面的是不对的,会删除整个表的)

           DELETE FROM T_person; 删除表中所有的数据了
           DELETE FROM T_person WHERE age = 28; 删除了表中年龄等于28的人
          
        • 2.4、条件语句的常见格式

           WHERE 字段 = 某个值; 不可以用两个=
           WHERE 字段 is 某个值; is 相当于 =
           WHERE 字段 != 某个值
           WHERE 字段  is not 某个值  is not 相当于 !=
           WHERE 字段 > 某个值;
           WHERE 字段1 = 某个值 and 字段2 > 某个值  // and相当于oc里面的&& 
           WHERE 字段1 = 某个值 or 字段2 = 某个值  // and相当于oc里面的||
          
      • 3、数据查询语句(DQL: Data Query Langue),可以查询获得表中的数据,关键字select是DQL(也是左右SQL)用的最多的操作,其他DQL常用的关键字有where,order by,group by 和having

        • 3.1、查询整个表

          SELECT * FROM T_Person2;   //  T_Person2是表名 
          
        • 3.2、查询符合条件的表中数据(年龄大于13的人)

          SELECT name,age FROM T_Person2 WHERE age > 13;
          
        • 3.3、给字段起别名

           SELECT name as nameJ,age as ageK FROM T_Person2;
          
          给字段起别名
        • 3.4、给表起别名 (同时查询不同的表,里面包含相同的字段的时候)

          SELECT table1.name,table1.age,table2.name,table2.age FROM T_Person2 as table1,T_person as table2;
          
        • 3.5、计算记录的数量

          • SELECT count(*或者字段) FROM T_Person2(表名) : 查询表中某一个字段有多少条

            SELECT count(*) FROM T_Person2
            
          • 加约束条件的查询 (年龄大于20的条数)

            SELECT count(*) FROM T_Person2 WHERE age > 20 
            
        • 3.6、查询的结果用

          • 单个字段排序(默认是升序ASC,降序是DESC

            SELECT * FROM T_Person2 ORDER BY age DESC
            
          • 多个字段排序 (年龄按照降序排序,遇到年龄相同的,再按降序排序)

             SELECT * FROM T_Person2 ORDER BY age DESC , id DESC;
            
      • 3.6、LIMIT分页查询

        • LIMIT数值1,数值2

          • 数值1:代表跳过几条

          • 数值2:代表取几条

          • 如果只取一个数值代表取前几条

            取前3条
            SELECT * FROM T_Person2 LIMIT 3;
            跳过前3条取后面的4条
            SELECT * FROM T_Person2 LIMIT 3,4;
            
          • 如果按照上面的分页,下面可以用一个公式概括

            10 代表一页10条数据 n代表第几页
            SELECT * FROM T_Person2 LIMIT 10*(n-1),10;
            
        • 3.7、对建表的字段的简单约束(如下面的方式建立一个表)

          • NOT NULL : 规定字段的值不能为null

          • UNIQUE: 规定字段的值不能一样

          • DEFAULT: 指定的字段设置默认值

             CREATE TABLE IF NOT EXISTS T_Person5(
                 id INTEGER PRIMARY KEY AUTOINCREMENT,
                 name TEXT NOT NULL,
                 age INTEGER DEFAULT 20
             );
            
    五、SQLite在Xcode里面的运用
    • 5.1、下面会按照创建数据库、建表、插入数据、更新数据、删除数据、多条数据的插入、多条数据插入时间的优化(事件的添加)、预插入数据和事件的结合。

    • 5.2、创建一个单例类 JKSQLiteManger

          import UIKit
          class JKSQLiteManger: NSObject {
      
             private static let manger: JKSQLiteManger = JKSQLiteManger()
             // 单粒
             class func shareManger() -> JKSQLiteManger {
                return manger;
             }
          }
      
    • 5.3、创建数据库建表(在使用之前打开,一般可以放到AppDelegate里面),下面的 \n是为了打印换行方便看, +是为了把上下两句话连成一句话,数据的名字要定义为 JK.sqlite的格式,下面T_Person8是表名,name,age:字段名,id:主键名

      private var db: OpaquePointer? = nil
      // MARK: 打开数据库
      func openDB(SQLiteName: String) {
        
        // 0.拿到数据库的路径
        let path = String.cacheDir() + "/\(SQLiteName)"
        print(path)
        let cPath = path.cString(using: String.Encoding.utf8)!
        // 1.打开数据库
        /**
           1.1、cPath: 需要打开的数据库文件的路径,这里的路径是C语言字符串的路径
           1.2、打开之后的数据库对象(指针),以后所有的数据库操作,都必须拿到这个指针才能进行相关的操作
         */
        // open方法的特点:如果指定路径的数据库文件已经存在,就会直接打开,否则就会创建一个新的
        if sqlite3_open(cPath, &db) != SQLITE_OK{
            
            print("打开数据库失败")
            return
        }
        
        // 2.创建表
        if creatTable(){
            print("创建表成功")
        }else{
            print("创建表失败")
        }
      }
      
      //MARK: 创建表
      func creatTable() -> Bool {
        
        // 1、编写SQL语句
        /**
            建议:在开发中编写SQL语句,如果语句过长,不要写在一行
            技巧:在做数据库开发时候,如果遇到错误,可以先将SQL打印出来,拷贝到PC工具中验证之后再进行调试
         */
        let sql = "\n CREATE TABLE IF NOT EXISTS T_Person8( \n" +
        "id INTEGER PRIMARY KEY AUTOINCREMENT, \n" +
        "name TEXT, \n" +
        "age INTEGER \n" +
        "); \n"
        print(sql)
        
        // 2、执行SQL语句
        // 在SQLite3中,除了查询以外(创建/删除/新增/更新)都使用同一个函数
        return execSQL(sql: sql)
      }
      
    • 5.4、插入更新删除 一条数据

      /** 插入一条数据*/
      let sql = "INSERT INTO T_Person8 (name,age) VALUES ('\(name!)',\(age));"
      /** 更新一条数据*/
      let sql = "UPDATE T_Person8 SET name = '\(name)' WHERE age = \(self.age);"
      /** 删除一条数据*/
      let sql = "DELETE FROM T_Person8 WHERE age = \(self.age);"
      
       // MARK:执行除查询以外的SQL语句
       /**
          - Parameter sql: 要执行的SQL语句
          - Returns: 是否执行成功 true:执行成功 false:执行失败
        */
      func execSQL(sql: String) -> Bool {
        
        // 1.将Swift的字符串转换为C语言的字符串
        let cSQL = sql.cString(using: String.Encoding.utf8)!
        
        // 在SQLite3中,除了查询以外(创建/删除/新增/更新)都使用同一个函数
        /**
           1、已经打开的数据库对象
           2、需要执行的SQLite语句,C语言字符串
           3、执行SQLite语句后面的回调,一般传nil
           4、第三个参数的第一个参数,一般传nil
           5、错误信息一般传nil
         */
         if sqlite3_exec(db, cSQL, nil, nil, nil) != SQLITE_OK{
            
              return false
          }
          return true
       }
      
    • 5.5、查询数据

       let sql = "SELECT * FROM T_Person8;"
       let res = JKSQLiteManger.shareManger().execRecordSQL(sql: sql)
      
       // MARK:查询所有的数据
       func execRecordSQL(sql: String) ->[[String: AnyObject]] {
        
        // 0.将Swift字符串转换为C语言字符串
        let cSQL = sql.cString(using: String.Encoding.utf8)!
        
        // 1.准备数据
        // 准备: 理解为预编译SQL语句, 检测里面是否有错误等等, 它可以提供性能
        /*
         1.已经开打的数据库对象
         2.需要执行的SQL语句
         3.需要执行的SQL语句的长度, 传入-1系统自动计算
         4.预编译之后的句柄, 已经要想取出数据, 就需要这个句柄
         5. 一般传nil
         */
        var stmt: OpaquePointer? = nil
        if sqlite3_prepare_v2(db, cSQL, -1, &stmt, nil) != SQLITE_OK
        {
            print("准备失败")
        }
        
        // 准备成功
        var records = [[String: AnyObject]]()
        
        // 2.查询数据
        // sqlite3_step代表取出一条数据, 如果取到了数据就会返回SQLITE_ROW
        while sqlite3_step(stmt) == SQLITE_ROW
        {
            // 获取一条记录的数据
            let record = recordWithStmt(stmt: stmt!)
            // 将当前获取到的这一条记录添加到数组中
            records.append(record)
        }
        
        // 3.关闭STMT
        // 注意点: 只要用到了stmt, 一定要关闭
        sqlite3_finalize(stmt)
        
        // 返回查询到的数据
        return records
      }
      
       /**
         获取一条记录的值
         :param: stmt 预编译好的SQL语句
         :returns: 字典
       */
       private func recordWithStmt(stmt: OpaquePointer) ->[String: AnyObject]
       {
        // 2.1拿到当前这条数据所有的列
        let count = sqlite3_column_count(stmt)
        //            print(count)
        // 定义字典存储查询到的数据
        var record  = [String: AnyObject]()
        
        for index in 0..<count
        {
            // 2.2拿到每一列的名称
            let cName = sqlite3_column_name(stmt, index)
            //let name = String(CString: cName, encoding: NSUTF8StringEncoding)!
            let name = String(cString: cName!, encoding: String.Encoding.utf8)
            //                print(name)
            // 2.3拿到每一列的类型 SQLITE_INTEGER
            
            let type = sqlite3_column_type(stmt, index)
            //                print("name = \(name) , type = \(type)")
            
            switch type
            {
            case SQLITE_INTEGER:
                // 整形
                let num = sqlite3_column_int64(stmt, index)
                record[name!] = Int(num) as AnyObject
            case SQLITE_FLOAT:
                // 浮点型
                let double = sqlite3_column_double(stmt, index)
                record[name!] = Double(double) as AnyObject
            case SQLITE3_TEXT:
                // 文本类型
                let cText = UnsafePointer(sqlite3_column_text(stmt, index))
                let text =  String.init(cString: cText!)
                record[name!] = text as AnyObject
            case SQLITE_NULL:
                // 空类型
                record[name!] = NSNull()
            default:
                // 二进制类型 SQLITE_BLOB
                // 一般情况下, 不会往数据库中存储二进制数据
                print("")
            }
        }
        return record
      }
      
    • 5.6、大批量数据插入的优化问题(下面将使用事务异步串行的队列来快速的插入数据)

       // MARK: 事务相关
       // 1.开启事务
       func beginTransaction(){
          execSQL(sql: "BEGIN TRANSACTION")
       }
       // 2.提交事务
       func commitTransaction(){
          execSQL(sql: "COMMIT TRANSACTION")
       }
       // 3.回滚
       func rollbackTransaction(){
         execSQL(sql: "ROLLBACK TRANSACTION")
       }
      
      // MARK: 创建一个异步串行队列来执行数据的插入,防止界面卡顿
      private let dbQueue = DispatchQueue(label: "com.520it.lnj")
      func execQueueSQL(action:@escaping (_ manager: JKSQLiteManger)->())
      {
        // 1.开启一个子线程
        dbQueue.async {
            
            //print(Thread.current)
            // 2.执行闭包
            action(self)
         }
      }
      
    • 5.7、预编译优化数据库

      // MARK: 预编译优化数据库
      func batchExecSQL() {
       
       let start = CFAbsoluteTimeGetCurrent()
       let manager = JKSQLiteManger.shareManger()
       // 开启事务
       manager.beginTransaction()
       for i in 0..<10000
       {
           let sql = "INSERT INTO T_Person8" +
               "(name, age)" +
               "VALUES" +
           "(?, ?);"
           
           manager.batchExecSQL(sql: sql, args: "yy +\(i)", 1 + i)
       }
       // 提交事务
       manager.commitTransaction()
       print("耗时 = \(CFAbsoluteTimeGetCurrent() - start)")
      }
      
      // 自定义一个SQLITE_TRANSIENT, 覆盖系统的
      private let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
      
      // MARK: - 预编译优化数据库
      func batchExecSQL(sql:String, args: CVarArg...) -> Bool
      {
       
       // 1.将SQL语句转换为C语言
       let cSQL = sql.cString(using: String.Encoding.utf8)!
       
       // 2.预编译SQL语句
       var stmt: OpaquePointer? = nil
       if sqlite3_prepare_v2(db, cSQL, -1, &stmt, nil) != SQLITE_OK
       {
           print("预编译失败")
           sqlite3_finalize(stmt)
           return false
       }
       
       // 3.绑定数据
       var index:Int32 = 1
       for objc in args
       {
           if objc is Int
           {
               //print("通过int方法绑定数据 \(objc)")
               // 第二个参数就是SQL中('?', ?)的位置, 注意: 从1开始
               sqlite3_bind_int64(stmt, index, sqlite3_int64(objc as! Int))
           }else if objc is Double
           {
               //print("通过Double方法绑定数据 \(objc)")
               sqlite3_bind_double(stmt, index, objc as! Double)
           }else if objc is String
           {
               //                print("通过Text方法绑定数据 \(objc)")
               let text = objc as! String
               let cText = text.cString(using: String.Encoding.utf8)!
               // 第三个参数: 需要绑定的字符串, C语言
               // 第四个参数: 第三个参数的长度, 传入-1系统自动计算
               // 第五个参数: OC中直接传nil, 但是Swift传入nil会有大问题
               /*
                typedef void (*sqlite3_destructor_type)(void*);
                
                #define SQLITE_STATIC      ((sqlite3_destructor_type)0)
                #define SQLITE_TRANSIENT   ((sqlite3_destructor_type)-1)
                
                第五个参数如果传入SQLITE_STATIC/nil, 那么系统不会保存需要绑定的数据, 如果需要绑定的数据提前释放了, 那么系统就随便绑定一个值
                第五个参数如果传入SQLITE_TRANSIENT, 那么系统会对需要绑定的值进行一次copy, 直到绑定成功之后再释放
                */
               sqlite3_bind_text(stmt, index, cText, -1, SQLITE_TRANSIENT)
           }
           
           index = index + 1
       }
       
       // 4.执行SQL语句
       if sqlite3_step(stmt) != SQLITE_DONE
       {
           print("执行SQL语句失败")
           return false
       }
       
       // 5.重置STMT
       if sqlite3_reset(stmt) != SQLITE_OK
       {
           print("重置失败")
           return false
       }
       
       // 6.关闭STMT
       // 注意点: 只要用到了stmt, 一定要关闭
       sqlite3_finalize(stmt)
       
       return true
      }
      

    最后奉上练习的demo: SwiftSQLite

    相关文章

      网友评论

        本文标题:swift微博第25天(SQLite)

        本文链接:https://www.haomeiwen.com/subject/cucrxftx.html