http://www.sqlite.org/
嵌入式
的SQLite
是一个进程内的库,本质上就是一个文件
,是一个 SQL 数据库引擎,具有:
而服务端使用的数据库,如:Orcal
, SQL Server
, MySQL
...则需要独立的服务器,安装,配置,维护……
字段(COL)
存储一个值,类似于对象的一个属性行(ROW)
存储一条记录,类似于一个对象表(TABLE)
存储一系列数据,类似于对象数组表
之间存在一定 关系
,类似于对象之间的关系,例如:一条微博数据中包含用户记录Field/Col
):一个字段存储一个值,可以存储 INTEGER
, REAL
, TEXT
, BLOB
, NULL
五种类型的数据
Primary Key
,唯一
标示一条记录的字段,具有以下特点:
Foreign Key
,对应其他关系表的标示,利用外键
可以和另外一个表
建立起"关系"
SQL 命令
实现增/删/查/改,并在 UI 中显示命令 | 描述 |
---|---|
CREATE | 创建一个新的表,一个表的视图,或者数据库中的其他对象 |
ALTER | 修改数据库中的某个已有的数据库对象,比如一个表 |
DROP | 删除整个表,或者表的视图,或者数据库中的其他对象 |
命令 | 描述 |
---|---|
INSERT | 新增 |
UPDATE | 修改 |
DELETE | 删除 |
命令 | 描述 |
---|---|
SELECT | 查询 |
/*
创建数据表
CREATE TABLE '表名' (
'字段名' 类型(INTEGER, REAL, TEXT, BLOB)
NOT NULL 不允许为空
PRIMARY KEY 主键
AUTOINCREMENT 自增长,
'字段名2' 类型,
...
)
注意:在开发中,如果是从 Navicat 粘贴的 SQL,需要自己添加一个指令
IF NOT EXISTS 加在表名前,如果数据表已经存在,就什么也不做
*/
CREATE TABLE IF NOT EXISTS "T_Person" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" TEXT,
"age" INTEGER,
"heigth" REAL
)
/* 简单约束 */
CREATE TABLE IF NOT EXISTS t_student
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
age INTEGER
);
CREATE TABLE IF NOT EXISTS t_student
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE,
age INTEGER
);
/* 添加主键 */
CREATE TABLE IF NOT EXISTS t_student
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
age INTEGER,
score REAL
);
/* 添加主键 */
CREATE TABLE IF NOT EXISTS t_student
(
id INTEGER,
name TEXT,
age INTEGER,
score REAL,
PRIMARY KEY(id)
);
INSERT INTO t_student
(age, score, name)
VALUES
('28', 100, 'zhangsan');
INSERT INTO t_student
(name, age)
VALUES
('lisi', '28');
INSERT INTO t_student
(score)
VALUES
(100);
UPDATE t_student
SET name = 'MM'
WHERE age = 10;
UPDATE t_student
SET name = 'WW'
WHERE age is 7;
UPDATE t_student
SET name = 'XXOO'
WHERE age < 20;
UPDATE t_student
SET name = 'NNMM'
WHERE age < 50 and score > 10;
/*更新记录的name*/
UPDATE t_student SET name = 'zhangsan';
DELETE FROM t_student;
DELETE FROM t_student WHERE age < 50;
/* 分页 */
SELECT * FROM t_student
ORDER BY id ASC LIMIT 30, 10;
/* 排序 */
SELECT * FROM t_student
WHERE score > 50
ORDER BY age DESC;
SELECT * FROM t_student
WHERE score < 50
ORDER BY age ASC , score DESC;
/* 计量 */
SELECT COUNT(*)
FROM t_student
WHERE age > 50;
/* 别名 */
SELECT name as myName, age as myAge, score as myScore
FROM t_student;
SELECT name myName, age myAge, score myScore
FROM t_student;
SELECT s.name myName, s.age myAge, s.score myScore
FROM t_student s
WHERE s.age > 50;
/* 查询 */
SELECT name, age, score FROM t_student;
SELECT * FROM t_student;
/*删除表*/
DROP TABLE IF EXISTS t_student;
database_connection
sqlite3_open
函数创建并返回SQLite
接口函数之前,必须先获得 database_connnection
对象prepared_statement
sqlite3_open
database_connection
对象是其他 SQLite APIs
的句柄参数sqlite3_prepare
SQL
文本转换为 prepared_statement
对象执行
指定的 SQL
语句SQL
文本初始化为待
执行的状态sqlite3_step
sqlite3_prepare
函数返回的 prepared_statement
对象prepared_statement
对象的内部指针将指向其返回结果集的第一行INSERT
、UPDATE
和 DELETE
等 DML
语句,执行一次即可完成sqlite3_column
sqlite3_column_blob
sqlite3_column_bytes
sqlite3_column_bytes16
sqlite3_column_double
sqlite3_column_int
sqlite3_column_int64
sqlite3_column_text
sqlite3_column_text16
sqlite3_column_type
sqlite3_column_value
sqlite3_column_count
sqlite3_finalize
prepared_statement
对象,否则会造成内存泄露sqlite3_close
database_connection
对象prepared_statements
对象都必须在此之前被销毁libsqlite3.tbd
SQLite-Bridge.h
SQLite3
框架是一套 C
语言的框架,因此需要添加桥接文件项目
-TARGETS
-Build Settings
,搜索 Bridg
Objective-C Bridging Header
中输入 项目名/SQLite-Bridge.h
编译测试
与网络接口的独立类似,数据库的底层操作,也应该有一个独立的对象单独负责
SQLiteManager
单例SQLiteManager.swift
,并且实现以下代码:/// SQLite 管理器
class SQLiteManager {
/// 单例
static let sharedManager = SQLiteManager()
}
SQL 命令
实现增/删/查/改,并在 UI 中显示/// 数据库句柄
private var db: COpaquePointer = nil
/// 打开数据库
///
/// - parameter dbname: 数据库文件名
func openDB(dbname: String) {
let path = (NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).last! as NSString).stringByAppendingPathComponent(dbname)
print(path)
if sqlite3_open(path, &db) != SQLITE_OK {
print("打开数据库失败")
return
}
print("打开数据库成功")
}
sqlite3_open
函数会打开数据库,如果数据库不存在,会新建一个空的数据库
,并且返回数据库指针(句柄)数据库句柄
进行AppDelegate
中添加以下代码func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
SQLiteManager.sharedManager.openDB("my.db")
return true
}
SQLite
数据库是直接保存在沙盒中的一个文件,只有当前应用程序可以使用持久式
连接方式使用的持久式连接
指的是只做一次 打开数据库
的操作,永远不做 关闭
数据库的操作,从而可以提高数据库的访问效率创表
操作注意:创表操作本质上是通过执行
SQL
语句实现的
SQL
语句函数/// 执行 SQL
///
/// - parameter sql: SQL
///
/// - returns: 是否成功
func execSQL(sql: String) -> Bool {
/**
参数
1. 数据库句柄
2. 要执行的 SQL 语句
3. 执行完成后的回调,通常为 nil
4. 回调函数第一个参数的地址,通常为 nil
5. 错误信息地址,通常为 nil
*/
return sqlite3_exec(db, sql, nil, nil, nil) == SQLITE_OK
}
/// 创建数据表
///
/// - returns: 是否成功
private func createTable() -> Bool {
let sql = "CREATE TABLE IF NOT EXISTS T_Person \n" +
"('id' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \n" +
"'name' TEXT, \n" +
"'age' INTEGER);"
print(sql)
return execSQL(sql)
}
openDB
函数if createTable() {
print("创表成功")
} else {
print("创表失败")
db = nil
}
SQL
可以从 Navicat
中粘贴,然后做一些处理
"
替换成 '
\n
防止字符串拼接因为缺少空格造成 SQL
语句错误表名
前添加 IF NOT EXISTS
防止因为数据表存在出现错误Person
模型class Person: NSObject {
/// id
var id: Int = 0
/// 姓名
var name: String?
/// 年龄
var age: Int = 0
/// 使用字典创建 Person 对象
init(dict: [String: AnyObject]) {
super.init()
setValuesForKeysWithDictionary(dict)
}
}
/// 将当前对象插入到数据库
///
/// - returns: 是否成功
func insertPerson() -> Bool {
assert(name != nil, "姓名不能为空")
let sql = "INSERT INTO T_Person (name, age) VALUES ('\(name!)', \(age));"
return SQLiteManager.sharedManager.execSQL(sql)
}
/// 测试插入数据
func demoInsert() {
print(Person(dict: ["name": "zhangsan", "age": 18]).insertPerson())
}
/// 更新当前对象在数据库中的记录
///
/// - returns: 是否成功
func updatePerson() -> Bool {
assert(name != nil, "姓名不能为空")
assert(id > 0, "ID 不正确")
let sql = "UPDATE T_Person SET name = '\(name!)', age = \(age) WHERE id = \(id);"
return SQLiteManager.sharedManager.execSQL(sql)
}
/// 测试更新记录
func demoUpdate() {
print(Person(dict: ["id": 1, "name": "lisi", "age": 20]).updatePerson())
}
/// 删除当前对象在数据库中的记录
///
/// - returns: 是否成功
func deletePerson() -> Bool {
assert(id > 0, "ID 不正确")
let sql = "DELETE FROM T_Person WHERE ID = \(id);"
return SQLiteManager.sharedManager.execSQL(sql)
}
/// 测试删除记录
func demoDelete() {
print(Person(dict: ["id": 1, "name": "lisi", "age": 20]).deletePerson())
}
/// 测试批量插入数据
func insertManyPerson() {
print("开始")
let start = CFAbsoluteTimeGetCurrent()
for i in 0..<100000 {
Person(dict: ["name": "lisi-\(i)", "age": Int(arc4random_uniform(10)) + 20]).insertPerson()
}
print(CFAbsoluteTimeGetCurrent() - start)
}
非常耗时,大概需要1分钟左右
/// 加载 Person 对象数组
class func loadPersons() -> [Person]? {
// 1. 从数据库获取字典数组
SQLiteManager.sharedManager.execRecordSet("SELECT id, name, age FROM T_Person;")
// 2. 遍历数组,字典转模型
return nil
}
SQLiteManager
中添加查询语句,准备结果集/// 执行 SQL 返回结果集
///
/// - parameter sql: SQL
///
/// - returns: 字典数组
func execRecordSet(sql: String) -> [[String: AnyObject]]? {
// 1. 准备(预编译) SQL
var stmt: COpaquePointer = nil
/**
1. 已经打开的数据库句柄
2. 要执行的 SQL
3. 以字节为单位的 SQL 最大长度,传入 -1 会自动计算
4. SQL 语句地址
5. 未使用的指针地址,通常传入 nil
*/
if sqlite3_prepare_v2(db, sql, -1, &stmt, nil) != SQLITE_OK {
print("准备 SQL 失败")
return nil
}
print("OK")
// 释放语句
sqlite3_finalize(stmt)
return nil
}
代码小结
sqlite3_step
执行 SQL,每执行一次,获取一条记录while
循环直至执行完毕单步执行
// 2. 单步执行获取结果集内容
var index = 0
while sqlite3_step(stmt) == SQLITE_ROW {
print(index++)
}
stmt
中的列数
以及每列的列名
& 数据类型
// 2. 单步执行获取结果集内容
while sqlite3_step(stmt) == SQLITE_ROW {
// 1> 结果集列数
let colCount = sqlite3_column_count(stmt)
// 2> 遍历每一列
for col in 0..let cName = sqlite3_column_name(stmt, col)
let name = String(CString: cName, encoding: NSUTF8StringEncoding)!
print(name + "\t", appendNewline: false)
}
print("\n", appendNewline: false)
}
数据类型
获取数据for col in 0..// 1) 字段名
let cName = sqlite3_column_name(stmt, col)
let name = String(CString: cName, encoding: NSUTF8StringEncoding)!
// 2) 字段类型
let type = sqlite3_column_type(stmt, col)
// 3) 根据类型获取字段内容
var v: AnyObject? = nil
switch type {
case SQLITE_INTEGER:
v = Int(sqlite3_column_int64(stmt, col))
case SQLITE_FLOAT:
v = sqlite3_column_double(stmt, col)
case SQLITE3_TEXT:
let cText = UnsafePointer<Int8>(sqlite3_column_text(stmt, col))
v = String(CString: cText, encoding: NSUTF8StringEncoding)
case SQLITE_NULL:
v = NSNull()
default:
print("不支持的格式")
}
print(name + "\t" + String(type) + "\t \(v) \t", appendNewline: false)
}
print("\n", appendNewline: false)
/// 从 stmt 获取记录字典
///
/// - parameter stmt: stmt
///
/// - returns: 返回记录集字典
private func recordDict(stmt: COpaquePointer) -> [String: AnyObject] {
// 1> 结果集列数
let colCount = sqlite3_column_count(stmt)
// 2> 遍历每一列 - 创建字典
var record = [String: AnyObject]()
for col in 0..(sqlite3_column_text(stmt, col))
v = String(CString: cText, encoding: NSUTF8StringEncoding)
case SQLITE_NULL:
v = NSNull()
default:
print("不支持的格式")
}
record[name] = v
}
return record
}
/// 执行 SQL 返回结果集
///
/// - parameter sql: SQL
///
/// - returns: 字典数组
func execRecordSet(sql: String) -> [[String: AnyObject]]? {
// 1. 准备(预编译) SQL
var stmt: COpaquePointer = nil
if sqlite3_prepare_v2(db, sql, -1, &stmt, nil) != SQLITE_OK {
print("准备 SQL 失败")
return nil
}
// 2. 单步执行获取结果集内容
// 2.1 结果集
var recordset = [[String: AnyObject]]()
// 2.2 遍历结果集
while sqlite3_step(stmt) == SQLITE_ROW {
recordset.append(recordDict(stmt))
}
// 3. 释放语句
sqlite3_finalize(stmt)
return recordset
}
Person
模型中加载 Person
列表/// 加载 Person 对象数组
class func loadPersons() -> [Person]? {
// 1. 从数据库获取字典数组
guard let array = SQLiteManager.sharedManager.execRecordSet("SELECT id, name, age FROM T_Person;") else {
return nil
}
// 2. 遍历数组,字典转模型
var persons = [Person]()
for dict in array {
persons.append(Person(dict: dict))
}
return persons
}
在 SQLite 中如果要批量插入数据,通常需要引入
事务的概念
大规模数据操作前
,首先开启一个事务,保存操作前的数据库的状态提交
事务,让数据库更新到数据操作后的状态回滚
事务,让数据库还原到操作前的状态/// 开启事务
func beginTransaction() -> Bool {
return execSQL("BEGIN TRANSACTION;")
}
/// 提交事务
func commitTransaction() -> Bool {
return execSQL("COMMIT TRANSACTION;")
}
/// 回滚事务
func rollBackTransaction() -> Bool {
return execSQL("ROLLBACK TRANSACTION;")
}
/// 插入许多人
private func insertManyPerson() {
print("开始")
let start = CFAbsoluteTimeGetCurrent()
SQLiteManager.sharedSQLiteManager.beginTransaction()
for i in 0..<100000 {
let person = Person(dict: ["name": "lisi-" + String(i), "age": 18, "height": 1.8])
person.insertPerson()
}
SQLiteManager.sharedSQLiteManager.commitTransaction()
print("结束 " + String(CFAbsoluteTimeGetCurrent() - start))
}
测试结果不到 4s
/// 插入许多人
private func insertManyPerson() {
print("开始")
let start = CFAbsoluteTimeGetCurrent()
SQLiteManager.sharedSQLiteManager.beginTransaction()
for i in 0..<100000 {
let person = Person(dict: ["name": "lisi-" + String(i), "age": 18, "height": 1.8])
person.insertPerson()
if i == 10000 {
SQLiteManager.sharedSQLiteManager.rollBackTransaction()
break
}
}
SQLiteManager.sharedSQLiteManager.commitTransaction()
print("结束 " + String(CFAbsoluteTimeGetCurrent() - start))
}
func batchUpdate(sql: String, params: CVarArgType...) -> Bool {
let cSQL = sql.cStringUsingEncoding(NSUTF8StringEncoding)!
var stmt: COpaquePointer = nil
if sqlite3_prepare_v2(db, cSQL, -1, &stmt, nil) == SQLITE_OK {
// 绑定参数
var col: Int32 = 1
for arg in params {
if arg is Int {
sqlite3_bind_int64(stmt, col, sqlite3_int64(arg as! Int))
} else if arg is Double {
sqlite3_bind_double(stmt, col, (arg as! Double))
} else if arg is String {
let cStr = (arg as! String).cStringUsingEncoding(NSUTF8StringEncoding)
sqlite3_bind_text(stmt, col, cStr!, -1, SQLITE_TRANSIENT)
} else if arg is NSNull {
sqlite3_bind_null(stmt, col)
}
col++
}
}
sqlite3_finalize(stmt)
return true
}
NULL
或者 SQLITE_STATIC
常量,SQlite 会假定这块 buffer
是静态内存,或者客户应用程序会小心的管理和释放这块 buffer
,所以SQlite放手不管如果第5个参数传递的是 SQLITE_TRANSIENT
常量,则SQlite会在内部复制这块buffer的内容。这就允许客户应用程序在调用完 bind
函数之后,立刻释放这块 buffer
(或者是一块栈上的 buffer
在离开作用域之后自动销毁)。SQlite会自动在合适的时机释放它内部复制的这块 buffer
由于在 SQLite.h 中 SQLITE_TRANSIENT
是以宏的形式定义的,而在 swift 中无法直接利用宏传递函数指针,因此需要使用以下代码转换一下
private let SQLITE_TRANSIENT = sqlite3_destructor_type(COpaquePointer(bitPattern: -1))
private let SQLITE_TRANSIENT = unsafeBitCast(-1, sqlite3_destructor_type.self)
NULL
,是因为 OC
中以 @"" 定义的函数都是保存在静态区的)sqlite3_bind_text(stmt, index, cStr, -1, SQLITE_TRANSIENT)
var result = true
if sqlite3_step(stmt) != SQLITE_DONE {
print("插入错误")
result = false
}
// 语句复位
if sqlite3_reset(stmt) != SQLITE_OK {
print("语句复位错误")
result = false
}
sqlite3_finalize(stmt)
return result
注意:执行结束后,一定要对语句进行复位,以便后续查询语句能够继续执行
SQLITE_DONE
reset
操作/// 批量插入
private func batchInsert() {
print("开始")
let start = CFAbsoluteTimeGetCurrent()
let manager = SQLiteManager.sharedSQLiteManager
let sql = "INSERT INTO T_Person (name, age, height) VALUES (?, ?, ?);"
// 开启事务
manager.beginTransaction()
for _ in 0..<10000 {
if !manager.batchUpdate(sql, params: "zhangsan", 18, 1.8) {
manager.rollBackTransaction()
break
}
}
manager.commitTransaction()
print("结束 " + String(CFAbsoluteTimeGetCurrent() - start))
}
运行测试,执行结果只需要 0.1s
/// 操作队列
private let queue = dispatch_queue_create("com.itheima.sqlite", DISPATCH_QUEUE_SERIAL)
/// 队列更新
///
/// :param: action 在后台执行的任务
func queueUpdate(action: (manager: SQLiteManager) -> ()) {
dispatch_async(queue) { [unowned self] in
// 1. 开启事务
self.beginTransaction()
action(manager: self)
// 2. 提交事务
self.commitTransaction()
}
}
private func queueUpdate() {
print("开始")
let start = CFAbsoluteTimeGetCurrent()
SQLiteManager.sharedSQLiteManager.queueUpdate { (manager) -> () in
let sql = "INSERT INTO T_Person (name, age, height) VALUES (?, ?, ?);"
for i in 0..<10000 {
if !manager.batchUpdate(sql, params: "zhangsan", 18, 1.8) {
manager.rollBackTransaction()
break
}
if i == 1000 {
manager.rollBackTransaction()
break
}
}
print(NSThread.currentThread())
print("结束 " + String(CFAbsoluteTimeGetCurrent() - start))
}
}
注意:SQLite 数据库不允许同时并发写入输入,如果用多线程,也必须使用串行队列进行操作
FMDB
使用框架
官网地址
https://github.com/ccgus/fmdb
直接拖拽
- 将 fmdb 文件夹拖入项目
- 建立桥接文件
- 将 Swift extensions 拖入项目
Podfile
- 不推荐
use_frameworks! pod 'FMDB', :git => 'https://github.com/robertmryan/fmdb.git'
代码演练
- 除了查询都使用
executeUpdate
- 查询使用
executeQuery
let documentsFolder = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String let path = documentsFolder.stringByAppendingPathComponent("test.sqlite") let database = FMDatabase(path: path) if !database.open() { println("Unable to open database") return } if !database.executeUpdate("create table test(x text, y text, z text)", withArgumentsInArray: nil) { println("create table failed: \(database.lastErrorMessage())") } if !database.executeUpdate("insert into test (x, y, z) values (?, ?, ?)", withArgumentsInArray: ["a", "b", "c"]) { println("insert 1 table failed: \(database.lastErrorMessage())") } if !database.executeUpdate("insert into test (x, y, z) values (?, ?, ?)", withArgumentsInArray: ["e", "f", "g"]) { println("insert 2 table failed: \(database.lastErrorMessage())") } if let rs = database.executeQuery("select x, y, z from test", withArgumentsInArray: nil) { while rs.next() { let x = rs.stringForColumn("x") let y = rs.stringForColumn("y") let z = rs.stringForColumn("z") println("x = \(x); y = \(y); z = \(z)") } } else { println("select failed: \(database.lastErrorMessage())") } database.close()
- 队列演练
let queue = FMDatabaseQueue(path: "/Users/liufan/Desktop/my.db") let sql = "insert into t_person (name, age) VALUES (?, ?);" queue.inTransaction { (db, rollBack) -> Void in db.executeUpdate(sql, "lisi", 28) db.executeUpdate(sql, "wangwu", 48) rollBack.memory = true } queue.inDatabase { (db) -> Void in if let result = db.executeQuery("select * from t_person") { while result.next() { let name = result.objectForColumnName("name") let age = result.intForColumn("age") print("\(name) \(age)") } } }
要设置 rollBack 可以使用
rollBack.memory = true