FMDB是使用OC语言对原生的SQLite的包装库,可使用原生SQLite语句对其进行操作,且支持多线程从而保证线程安全性。
SPM: https://github.com/ccgus/fmdb
使用时导入包即可。例如 import FMDB
创建单例工具类供访问。
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对于用户而言无数据库,仅有数据表),每次使用时获取实例对象进行增删改查即可。
@IBAction func initListener(_ sender: Any) {
let db = SQLiteManager.getInstance().db
if db.open() {
print("DATABASE INIT SUCCEED.")
} else {
print("ERROR AT: \(db.lastError())")
}
}
增删改查均可类使用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()
}
@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()
}
@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()
}
@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()
}
这里的查询类似于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
}
}
}
}
由于设置了回滚,第五次执行的语句是不存在的数据表,正常情况下前面四组插入成功的数据也会被删除,是为回滚。
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);"