FMDB Swift版使用

FMDB是使用OC语言对原生的SQLite的包装库,可使用原生SQLite语句对其进行操作,且支持多线程从而保证线程安全性。

一:安装使用步骤

SPM: https://github.com/ccgus/fmdb

使用时导入包即可。例如 import FMDB

二:创建DBHelper类

创建单例工具类供访问。

import Foundation
import FMDB
 
class SQLiteManager: NSObject {
     
    // Create Single instance.
    private static let manger: SQLiteManager = SQLiteManager()
    class func getInstance() -> SQLiteManager {
        return manger
    }
     
    // Database's name.
    private let dbName = "table_basic.db"
     
    // URL
    lazy var dbURL: URL = {
        let fileURL = try! FileManager.default
            .url(for: .applicationSupportDirectory, in: .userDomainMask,
                 appropriateFor: nil, create: true)
            .appendingPathComponent(dbName)
        print("------>Database URL:", fileURL)
        return fileURL
    }()
     
    // FMDatabase Object.
    lazy var db: FMDatabase = {
        let database = FMDatabase(url: dbURL)
        return database
    }()
     
    // FMDatabaseQueue Object.
    lazy var dbQueue: FMDatabaseQueue? = {
        let databaseQueue = FMDatabaseQueue(url: dbURL)
        return databaseQueue
    }()
}

helper类里面存有数据表名称(sqlite对于用户而言无数据库,仅有数据表),每次使用时获取实例对象进行增删改查即可。

三:增删改查类型操作

1.初始化

    @IBAction func initListener(_ sender: Any) {
        let db = SQLiteManager.getInstance().db
        if db.open() {
            print("DATABASE INIT SUCCEED.")
        } else {
            print("ERROR AT: \(db.lastError())")
        }
    }

2.创建表

增删改查均可类使用Java中的预处理方式进行运行。例如,SQL语句已经拼写完成,只需将SQL语句中待定参数替换为?即可,在withArgumentsIn: []将变量对应传入即可。需要说明的是,在FMDB中,只有查询为executeQuery(),其余均为executeUpdate()

    @IBAction func createListener(_ sender: Any) {
        let sql = "create table if not exists table_basic (id integer primary key autoincrement, name text);"
        let db = SQLiteManager.getInstance().db
        if db.open(){
            try! db.executeUpdate(sql, withArgumentsIn: [])
        } else{
            print("ERROR AT \(db.lastError())")
        }
        db.close()
    }

3.增

    @IBAction func addListener(_ sender: Any) {
        let sql = "insert into table_basic (id, name) values(?,?);"
        let db = SQLiteManager.getInstance().db
        if db.open(){
            try! db.executeUpdate(sql, withArgumentsIn: [2007001, "jack"])
        } else{
            print("ERROR AT \(db.lastError())")
        }
        db.close()
    }

4.删

    @IBAction func delListener(_ sender: Any) {
        let sql = "delete from table_basic where id = 2007001;"
        let db = SQLiteManager.getInstance().db
        if db.open(){
            try! db.executeUpdate(sql, withArgumentsIn: [])
        } else{
            print("ERROR AT \(db.lastError())")
        }
        db.close()
    }

当然也可以写成如下预处理方式:

    @IBAction func delListener(_ sender: Any) {
        let sql = "delete from table_basic where id = ?;"
        let db = SQLiteManager.getInstance().db
        if db.open(){
            try! db.executeUpdate(sql, withArgumentsIn: [2007001])
        } else{
            print("ERROR AT \(db.lastError())")
        }
        db.close()
    }

5.改

    @IBAction func updateListener(_ sender: Any) {
        let sql = "update table_basic set name = "" where id = ?;"
        let db = SQLiteManager.getInstance().db
        if db.open(){
            try! db.executeUpdate(sql, withArgumentsIn: ["rose", 2007001])
        } else{
            print("ERROR AT \(db.lastError())")
        }
        db.close()
    }

6.查

这里的查询类似于JDBC的ResultSet,FMDB也存在类似于FMDBResultSet的查询结果集对象。需要说明的是,String类型的数据一般为optional类型,不要忘记解包。

    @IBAction func searchListener(_ sender: Any) {
        let sql = "select * from table_basic;"
        let db = SQLiteManager.getInstance().db
        if db.open() {
            if let resultSet = db.executeQuery(sql, withArgumentsIn: []) {
                while resultSet.next() {
                    print("DATA IS \(resultSet.int(forColumn: "id"))")
                    print("DATA IS \(resultSet.string(forColumn: "name")!)")
                }
            }
        } else {
            print("ERROR AT \(db.lastError())")
        }
        db.close()
    }

四:多数据类型存储

SQLite不仅可存储常见的date、varchar等类型数据,还可存储二进制类型数据,是为blob。下面将演示
将图片以二进制类型存储至数据库并读取后显示在视图上。
首先创建表的操作如下:

    @IBAction func createListener(_ sender: Any) {
        let sql = "create table if not exists table_bin (id integer primary key autoincrement, content_data blob);"
        let db = SQLiteManager.getInstance().db
        if db.open(){
            try! db.executeUpdate(sql, withArgumentsIn: [])
        } else{
            print("ERROR AT \(db.lastError())")
        }
        db.close()
    }

可以看到这里创建了一个名为content_data的blob类型的字段,其余并无特别之处。
接下来是存储的操作。这里需要说明的是,需要用预处理语句将图片存入(图片需要转换为Data类型)

@IBAction func alterListener(_ sender: Any) {
        let sql = "insert into table_bin (id, content_data) values(?,?);"
        let db = SQLiteManager.getInstance().db
        if db.open() {
            // Convert UIImage object into Data object.
            let image = UIImage(named: "testimg")
            let data = image?.pngData()
            // Store into preprocessing statement.
            try! db.executeUpdate(sql, withArgumentsIn: [2007003, data])
        } else{
            print("ERROR AT \(db.lastError())")
        }
        db.close()
    }

接下来就是读取了,读取时注意数据的解包即可。操作如下:

    @IBAction func searchListener(_ sender: Any) {
        let sql = "select * from table_bin;"
        let db = SQLiteManager.getInstance().db
        if db.open() {
            if let resultSet = db.executeQuery(sql, withArgumentsIn: []) {
                while resultSet.next() {
                    let imageData = resultSet.data(forColumn: "content_data")! as? Data
                    self.imageView.image = UIImage(data: imageData!)
                }
            }
        } else {
            print("ERROR AT \(db.lastError())")
        }
        
        db.close()
    }

五:事物类操作

FMDB同样支持事物类型的操作。事物具有四大特性(ACID)。这里演示的是事物队列进行批量操作,成功与失败的情况。

    @IBAction func searchListener(_ sender: Any) {
        if let queue = SQLiteManager.getInstance().dbQueue {
            queue.inTransaction { db, rollback in
                do {
                    for i in 0..<10 {
                        try db.executeUpdate("insert into table_basic (id, name) values (?,?);",
                                             values: [i, "jack"])
                    }
                } catch {
                    print("ERROR AND ROLLBACK.")
                    rollback.pointee = true
                }
            }
        }
    }

正常情况下上述内容会全部正常插入。下面是异常情况:

   @IBAction func searchListener(_ sender: Any) {
       if let queue = SQLiteManager.getInstance().dbQueue {
           queue.inTransaction { db, rollback in
               do {
                   for i in 0..<10 {
                       if i == 5 {
                           try db.executeUpdate("insert into table_error (id, name) values (?,?);",
                                                values: [i, "jack"])
                       }
                       try db.executeUpdate("insert into table_basic (id, name) values (?,?);",
                                            values: [i, "jack"])
                   }
               } catch {
                   print("ERROR AND ROLLBACK.")
                   rollback.pointee = true
               }
           }
       }
   }

由于设置了回滚,第五次执行的语句是不存在的数据表,正常情况下前面四组插入成功的数据也会被删除,是为回滚。

六:配合Model

这里做个简单的演示,顺便做个分层。
首先要展示的功能很简单,一张数据表用来存储id、name、major,再来个查询功能将数据表内数据查出显示。
那么dbhelper(dao)内应该是与这张数据表相关的内容,如下:

import Foundation
import FMDB
 
class SQLiteManager: NSObject {
     
    private static let manger: SQLiteManager = SQLiteManager()
    class func getInstance() -> SQLiteManager {
        return manger
    }
     
    private let dbName = "tast_user.db"
     
    lazy var dbURL: URL = {
        let fileURL = try! FileManager.default
            .url(for: .applicationSupportDirectory, in: .userDomainMask,
                 appropriateFor: nil, create: true)
            .appendingPathComponent(dbName)
        print("Database Address:", fileURL)
        return fileURL
    }()
     
    lazy var db: FMDatabase = {
        let database = FMDatabase(url: dbURL)
        return database
    }()
     
    lazy var dbQueue: FMDatabaseQueue? = {
        let databaseQueue = FMDatabaseQueue(url: dbURL)
        return databaseQueue
    }()
}

接着我们在model层定义一个model(类似于Java中的Bean),其作用是作为实体类进行操纵读取写入,内容如下:

import Foundation

class User: NSObject {
    var id: Int?
    var name: String?
    var major: String?
    
    init(id: Int, name: String, major: String) {
        self.id = id
        self.name = name
        self.major = major
    }
    
    override init() {
        
    }
}

然后我们在service(业务层)定义一个协议,其作用是规定返回数据类型及应该完成的功能,内容如下:

import Foundation

protocol SearchUserService {
    func findUser() -> Array<User>
}

既然定义了一个这样的协议,就肯定要遵循了,即应存在一个实现SearchService的一个类,内容如下:

import Foundation
import FMDB

class SearchServiceImpl: SearchUserService {
    
    init() {
        let db = SQLiteManager.getInstance().db
        if db.open() {
            print("DATABASE INIT SUCCEED.")
        } else {
            print("ERROR AT: \(db.lastError())")
        }
    }
    
    func findUser() -> Array<User> {
        var tempArray: Array<User> = [User]()
        let sql = "select * from test_user;"
        let db = SQLiteManager.getInstance().db
        if db.open() {
            if let resultSet = db.executeQuery(sql, withArgumentsIn: []) {
                while resultSet.next() {
                    tempArray.append(User(id: Int(resultSet.int(forColumn: "id")), name: resultSet.string(forColumn: "name")!, major: resultSet.string(forColumn: "major")!))
                }
            }
        } else {
            print("ERROR AT \(db.lastError())")
        }
        db.close()
        return tempArray
    }
}

在这个类里面我们首先初始化数据库来拿到数据库对象,然后在findUser()方法里面定义泛型集合,拼接查询条件,执行,取出数据到model内再放入泛型集合内,最后待迭代集迭代完成便返回这个泛型集合。
最后一步就是显示。这里是将结果直接打印,出于方便角度并没有直接显示在屏幕上,而是打印出来。内容如下:

        let searchServiceImpl = SearchServiceImpl()
        let myArray: Array<User> = searchServiceImpl.findUser()
        print("------> DATA CONTENT: id: \(myArray[0].id!) name: \(myArray[0].name!) major: \(myArray[0].major!)")

整体步骤类似于SpringBoot的操作。当然这不一定是完整、标准的,应该存在其它写法,欢迎讨论,不吝赐教。

七:模糊查询

SQLite支持like、glob语句。like不区分大小写,glob区分大小写。下面是两者的示例:

    @IBAction func searchListener(_ sender: Any) {
        let sql = "select * from table_basic where name like '%j%';"
        let db = SQLiteManager.getInstance().db
        if db.open() {
            if let resultSet = db.executeQuery(sql, withArgumentsIn: []) {
                while resultSet.next() {
                    print("DATA IS \(resultSet.int(forColumn: "id"))")
                    print("DATA IS \(resultSet.string(forColumn: "name")!)")
                }
            }
        } else {
            print("ERROR AT \(db.lastError())")
        }
        db.close()
    }

glob仅与上者语句不同:

let sql = "select * from table_basic where name glob '*j*';"

八:分组

let sql = "select name from table_basic group by name"

对name进行剔除重复的分组查询,结果中每个不同的name值仅有对应的唯一一条行记录,当然这个语句要看具体的业务逻辑。

九:排序

let sql = "select * from table_basic order by id asc"

对id进行以升序为条件的查询

十:触发器

触发器的创建使用是executeUpdate()语句。假设存在一个这样的业务逻辑:某张表记录用户每次的账号、密码、上次密码。上次密码的作用是防止用户待修改与上次密码重复。那么其创表语句如下:

let sql = "create table if not exists test_trigger(id integer primary key,password varchar(20),pass_password varchar(20))"

触发器语句如下:

    @IBAction func triggerListener(_ sender: Any) {
        let sql = "create trigger record_password before update on test_trigger begin insert into test_trigger(pass_password) values(old.password); end"
        let db = SQLiteManager.shareManger().db
        if db.open(){
            try! db.executeUpdate(sql, withArgumentsIn: [])
        } else{
            print("ERROR AT \(db.lastError())")
        }
        db.close()
    }

需要说明的是不要让触发语句的外部条件为for earch row。这里并不是说在ios的sqlite不支持for each row,而是该条件是可选的。因为大部分情况下触发语句是针对行操作,也就是说外部条件为for each row而内部为update等情况下会造成循环引用。

十一:索引

索引就比较简单了,同样也是executeUpdate(),语句如下:

let sql = "create index my_index on table_basic(id);"
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值