参考:iOS基础进阶班合辑-大神推荐
本文是上面课程的摘要,只用于自己快速浏览.
目录
29 数据库
【录播】1.(了解)数据库简介(13分钟)
iOS中数据存取的方式
Plist(NSArray\NSDictionary)
Preference(偏好设置\NSUserDefaults)
NSCoding(NSKeyedArchiver\NSkeyedUnarchiver)
必须整存整取,比如数组内存放三条微博数据,归档后写入文件
当存放第四条时,需要将前三条取出解档,添加第四条再归档
SQLite3
嵌入式轻量化数据库
Core Data
本质是对SQLite3的包装,使用更面向对象化
什么是数据库
数据库(Database)是按照数据结构来组织、存储和管理数据的仓库
数据库可以分为2大种类
关系型数据库(主流)
对象型数据库
常用关系型数据库
PC端:Oracle、MySQL、SQL Server、Access、DB2、Sybase
嵌入式\移动客户端:SQLite
【录播】2.(了解)Navicat安装(6分钟)
Navicat是一款著名的数据库管理软件,支持大部分主流数据库(包括SQLite)
利用Navicat建立数据库连接
【录播】3.(掌握)Navicat基本使用(11分钟)
数据库文件:xxx.sqlite
表1
主键key(自增) 字段1 字段2 字段3
val val val val
表2:t_Student
id name age score
0 mj 18 99
1 lc 19 100
表3
SQLite将数据划分为以下几种存储类型:
integer : 整型值
real : 浮点值
text : 文本字符串
blob : 二进制数据(比如文件)
null : 空类型
【录播】4.(熟悉)SQL语言简介(8分钟)
什么是SQL
SQL(structured query language):结构化查询语言
SQL是一种对关系型数据库中的数据进行定义和操作的语言
SQL语言简洁,语法简单,好学好用
什么是SQL语句
使用SQL语言编写出来的句子\代码,就是SQL语句
在程序运行过程中,要想操作(增删改查,CRUD)数据库中的数据,必须使用SQL语句
SQL语句的特点
不区分大小写(比如数据库认为user和UsEr是一样的)
每条语句都必须以分号 ; 结尾
SQL语句的种类
数据定义语句(DDL:Data Definition Language)
包括create和drop等操作
在数据库中创建新表或删除表(create table或 drop table)
数据操作语句(DML:Data Manipulation Language)
包括insert、update、delete等操作
上面的3种操作分别用于添加、修改、删除表中的数据
数据查询语句(DQL:Data Query Language)
可以用于查询获得表中的数据
关键字select是DQL(也是所有SQL)用得最多的操作
其他DQL常用的关键字有where,order by,group by和having
【录播】5.(掌握)DDL语句(11分钟)
创建表
create table 表名 (字段名1 字段类型1, 字段名2 字段类型2, …) ;
create table if not exists 表名 (字段名1 字段类型1, 字段名2 字段类型2, …) ;
create table t_student (id integer, name text, age inetger, score real) ;
create table t_student(name, age);
删除表
drop table 表名 ;
drop table if exists 表名 ;
drop table t_student ;
修改表名
ALTER TABLE 旧表名 RENAME TO 新表名
alter t_student rename to t_stu
新增属性
ALTER TABLE 表名 ADD COLUMN 列名 数据类型 限定符
alter t_student add column birthday
【录播】6.(掌握)DML语句(14分钟)
不能为空
not null :规定字段的值不能为null
不能重复
unique :规定字段的值必须唯一
默认值
default :指定字段的默认值
主键
保证数据库数据的唯一
create table t_student (id integer, name text not null unique, age integer not null default 1) ;
create table t_student (id integer primary key autoincrement, name text, age integer) ;
primary key 默认包含了 not null unique
【录播】7.(掌握)DML语句(7分钟)
insert into t_student (name, age) values (‘sz’, 10) ;
update t_student set name = ‘wex’, age = 20 ;
delete from t_student ;
30 数据库 2
【录播】8.(掌握)条件语句(10分钟)
update t_student set age = 5 where age > 10 and name != ‘wex’ ;
delete from t_student where age <= 10 or age > 30 ;
update t_student set score = age where name = ‘wex’ ;
【录播】9.(掌握)DQL-查询相关语句(17分钟)
查询
select name, age from t_student ;
select * from t_student ;
select * from t_student where age > 10 ; // 条件查询
统计
select count(*) from t_student
select count(age) from t_student
select avg(score) from t_student
avg(X)
sum(X)
min(X)
max(X)
排序
select * from t_student order by age ;
select * from t_student order by age desc ; //降序
select * from t_student order by age asc ; // 升序(默认)
select * from t_student order by age asc, height desc ;
limit分页
select * from t_student limit 4, 8 ;
select * from t_student limit 7 ;
相当于select * from t_student limit 0, 7 ;
分页
第1页:limit 0, 5
第2页:limit 5, 5
第3页:limit 10, 5
第n页:limit 5*(n-1), 5
【录播】10.(掌握)多表查询(13分钟)
多表查询
select 字段1, 字段2, … from 表名1, 表名2 ;
多表连接查询
select 字段1, 字段2, … from 表名1, 表名2 where 表名1.关联id = 表名2.关联id;
【录播】11.(掌握)代码实现SQLite-DDL(18分钟)
创建并打开数据库文件
var db: OpaquePointer? = nil
override func viewDidLoad() {
super.viewDidLoad()
let docPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory,
FileManager.SearchPathDomainMask.userDomainMask,
true)[0]
print("path = ",docPath)
let fileName = docPath + "/demo.sqlite"
/**
* sqlite3_open 使用这个函数打开一个数据库
* 参数一: 需要打开的数据库文件路径
* 参数二: 一个指向SQlite3数据结构的指针, 到时候操作数据库都需要使用这个对象
* 功能作用: 如果需要打开数据库文件路径不存在, 就会创建该文件;如果存在, 就直接打开; 可通过返回值, 查看是否打开成功
*/
if sqlite3_open(fileName, &db) != SQLITE_OK {
print("打开失败")
}else{
print("打开成功")
}
}
创建表
func createTable() {
// 创建SQL语句
let sql = "CREATE TABLE IF NOT EXISTS t_student (name TEXT, age INTEGER, score text, id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT)"
// 执行SQL语句
// 参数一: 数据库
// 参数二: 需要执行的SQL语句
// 参数三: 回调结果, 执行完毕之后的回调函数, 如果不需要置为NULL
// 参数四: 参数三的第一个参数, 刻意通过这个传值给回调函数 如果不需要置为NULL
// 参数五: 错误信息, 通过传递一个地址, 赋值给外界, 如果不需要置为NULL
if sqlite3_exec(db, sql, nil, nil, nil) != SQLITE_OK{
print("创建表失败")
}else{
print("创建表成功")
}
}
删除表
func dropTable() {
let sql = "DROP TABLE IF EXISTS t_student"
if sqlite3_exec(db, sql, nil, nil, nil) != SQLITE_OK{
print("删除表失败")
}else{
print("删除表成功")
}
}
【录播】12.(掌握)代码实现SQLite-DDL-封装(8分钟)
封装成一个单例对象
SQLTool.shareInstance
【录播】13.(掌握)代码实现DML语句-Insert(7分钟)
func insert(tableName: String, columnNameArray: [String], valueArray: [String]) -> Bool {
// 1. 创建SQL语句
let tempColumnNameArray = columnNameArray as NSArray
let columnNames = tempColumnNameArray.componentsJoined(by: ",")
let tempValueArray = valueArray as NSArray
let values = "\'" + tempValueArray.componentsJoined(by: "\',\'") + "\'"
let sql = "INSERT INTO \(tableName)(\(columnNames)) values (\(values))"
print("sql = ",sql)
// 2. 执行SQL语句
return (sqlite3_exec(db, sql, nil, nil, nil) == SQLITE_OK)
}
【录播】14(了解)代码实现DML语句-Insert绑定参数(22分钟)
extension SQLTool {
/// 预处理语句,绑定参数(未进行拆分,效率低)
/// - Parameters:
/// - tableName: 表名称
/// - columnNameArray: key 数组
/// - values: values 数组
/// - Returns: 返回是否执行成功
@discardableResult
func insertBind(tableName: String, columnNameArray: [String], values: [Any]) -> Bool {
let tempColumnNameArray = columnNameArray as NSArray
let columnNames = tempColumnNameArray.componentsJoined(by: ",")
// 1. 创建需要预编译的SQL语句
var tempValues: Array = [String]()
// 创建 ? 的占位个数
for _ in 0 ..< values.count{
tempValues.append("?")
}
let valuesStr = (tempValues as NSArray).componentsJoined(by: ",")
let prepareSql = "INSERT INTO \(tableName)(\(columnNames)) values (\(valuesStr))"
//print("prepareSql = ",prepareSql)
//预处理SQL语句, 并生成 "语句句柄" , 后续会使用这样的语句句柄绑定数值, 并执行
var stmt: OpaquePointer? = nil
if sqlite3_prepare_v2(db, prepareSql, -1, &stmt, nil) != SQLITE_OK{
print("预处理失败")
// 释放语句资源
sqlite3_finalize(stmt)
return false
}
var index: Int32 = 1
for obj in values{
if obj is Int {
let temp: sqlite_int64 = obj as! sqlite_int64
sqlite3_bind_int64(stmt, index, temp)
} else if obj is Double {
sqlite3_bind_double(stmt, index, obj as! Double)
} else if obj is String {
/**
第5个参数
此参数有两个常数,SQLITE_STATIC告诉sqlite3_bind_text函数字符串为常量,可以放心使用;
而SQLITE_TRANSIENT会使得sqlite3_bind_text函数对字符串做一份拷贝。
一般使用这两个常量参数来调用sqlite3_bind_text。
*/
sqlite3_bind_text(stmt, index, obj as! String, -1, SQLITE_TRANSIENT)
} else {
continue
}
index+=1
}
var result: Bool = false
if sqlite3_step(stmt) == SQLITE_DONE {
//print("插入成功")
result = true
} else {
//print("插入失败")
result = false
}
// 将语句复位
if sqlite3_reset(stmt) != SQLITE_OK {
//print("复位失败")
result = false
}
// 释放语句
sqlite3_finalize(stmt)
return result
}
}
31 数据库 3
【录播】15.(掌握代码实现)DML语句-Insert数据优化(20分钟)
extension SQLTool {
/// 获取预处理语句,预处理语句不需要每次都创建,获取一次可以多次绑定值
/// - Parameters:
/// - tableName: 表名
/// - columnNameArray: key 数组
/// - Returns: 预处理语句
func getPrepareStmt(tableName: String, columnNameArray: [String]) -> OpaquePointer! {
let tempColumnNameArray = columnNameArray as NSArray
let columnNames = tempColumnNameArray.componentsJoined(by: ",")
// 1. 创建需要预编译的SQL语句
var tempValues: Array = [String]()
// 创建 ? 的占位个数
for _ in 0 ..< columnNameArray.count{
tempValues.append("?")
}
let valuesStr = (tempValues as NSArray).componentsJoined(by: ",")
let prepareSql = "INSERT INTO \(tableName)(\(columnNames)) values (\(valuesStr))"
var stmt: OpaquePointer? = nil
// 预处理SQL语句, 并生成 "语句句柄" , 后续会使用这样的语句句柄绑定数值, 并执行
if sqlite3_prepare_v2(db, prepareSql, -1, &stmt, nil) != SQLITE_OK {
print("预处理失败")
// 释放语句资源
sqlite3_finalize(stmt)
return nil
}
return stmt
}
/// 绑定值到预处理语句,并执行
/// - Parameters:
/// - stmt: 预处理语句
/// - values: values 数组
/// - Returns: true执行成功,false执行失败
@discardableResult
func insertBind(stmt: OpaquePointer!, values: [Any]) -> Bool {
//一 绑定值
var index: Int32 = 1
for obj in values{
if obj is Int{
let temp: sqlite_int64 = obj as! sqlite_int64
sqlite3_bind_int64(stmt, index, temp)
} else if obj is Double {
sqlite3_bind_double(stmt, index, obj as! Double)
}else if obj is String {
/**
第5个参数
此参数有两个常数,SQLITE_STATIC告诉sqlite3_bind_text函数字符串为常量,可以放心使用;
而SQLITE_TRANSIENT会使得sqlite3_bind_text函数对字符串做一份拷贝。
一般使用这两个常量参数来调用sqlite3_bind_text。
*/
sqlite3_bind_text(stmt, index, obj as! String, -1, SQLITE_TRANSIENT)
}else {
continue
}
index+=1
}
//二 执行
var result: Bool = false
if sqlite3_step(stmt) == SQLITE_DONE{
// print("插入成功")
result = true
}else{
//print("插入失败")
result = false
}
return result
}
/// 重置预处理语句
/// - Parameter stmt: 预处理语句
/// - Returns: 是否成功
@discardableResult
func resetStmt(stmt: OpaquePointer!) -> Bool {
// 将语句复位
return sqlite3_reset(stmt) == SQLITE_OK
}
/// 释放预处理语句
/// - Parameter stmt: 预处理语句
func releaseStmt(stmt: OpaquePointer!) {
// 释放语句
sqlite3_finalize(stmt)
}
}
【录播】16.(掌握)代码实现-事务(19分钟)
XMGSQLTool.shareInstance.beginTransaction()
let result1 = Student.updateStudent("score = score - 10", condition: "name = 'zs'")
let result2 = Student.updateStudent("score1 = score + 10", condition: "name = 'wex'")
// 如果都执行成功再提交, 如果都不成功, 那就回滚
if result1 && result2 {
XMGSQLTool.shareInstance.commitTransaction()
} else {
XMGSQLTool.shareInstance.rollBackTransaction()
}
【录播】17代码实现DQL语句-方式1- sqlite3_exec(13分钟)
let block = { (parameter: UnsafeMutablePointer<Void>,
columnCount: Int32,
values: UnsafeMutablePointer<UnsafeMutablePointer<Int8>>,
columnNames: UnsafeMutablePointer<UnsafeMutablePointer<Int8>>) -> Int32 in
let count = Int(columnCount)
for i: Int in 0..<count
{
let column = columnNames[i]
let columnStr = String(CString: column, encoding: NSUTF8StringEncoding)
let value = values[i]
let valueStr = String(CString: value, encoding: NSUTF8StringEncoding)
print(columnStr! + "= " + valueStr!)
}
return 0
}
sqlite3_exec(db, selectSql,block , nil, nil)
【录播】18.(掌握)代码实现DQL语句-准备语句(18分钟)
方式1: sqlite3_exec
作用: 可以通过回调来获取结果, 步骤相对来说简单, 结果数据类型没有特定类型(id)
let selectSql = "select * from t_student"
// 方式1:
// 参数1: 一个打开的数据库
// 参数2: 需要执行的SQL语句
// 参数3: 查询结果回调(执行0次或多次)
// 参数1: 参数4的值
// 参数2: 列的个数
// 参数3: 结果值的数组
// 参数4: 所有列的名称数组
// 返回值: 0代表继续执行一致到结束, 1代表执行一次
// 参数4: 回调函数的第一个值
// 参数5: 错误信息
sqlite3_exec(db, selectSql, { (parameter: UnsafeMutablePointer<Void>, columnCount: Int32, values: UnsafeMutablePointer<UnsafeMutablePointer<Int8>>, columnNames: UnsafeMutablePointer<UnsafeMutablePointer<Int8>>) -> Int32 in
let count = Int(columnCount)
for i: Int in 0..<count {
let column = columnNames[i]
let columnStr = String(CString: column, encoding: NSUTF8StringEncoding)
let value = values[i]
let valueStr = String(CString: value, encoding: NSUTF8StringEncoding)
print(columnStr! + "= " + valueStr!)
}
return 0
}, nil, nil)
方式2: 通过"准备语句"
作用: 可以处理不同特定类型, 步骤相对来说复杂
var stmt: COpaquePointer = nil
if sqlite3_prepare(db, selectSql, -1, &stmt, nil) != SQLITE_OK {
print("预处理失败")
return
}
// 因为查询语句中没有占位?, 所以, 可以省略"绑定步骤"
// 执行语句
// sqlite3_step, 当使用这个方法时, 执行完毕后, 会自动跳到结果集的下一行, 如果依然有记录就返回SQLITE_ROW,
while sqlite3_step(stmt) == SQLITE_ROW {
let columnCount = sqlite3_column_count(stmt)
for i in 0..<columnCount {
// 获取列的类型
let type = sqlite3_column_type(stmt, i)
if type == SQLITE_TEXT {
let text = UnsafePointer<Int8>(sqlite3_column_text(stmt, i))
let str = String(CString: text, encoding: NSUTF8StringEncoding)
print(str)
} else if xxx {
}
}
}
sqlite3_finalize(stmt)
【录播】19.(掌握)FMDB基本使用(19分钟)
1. 什么是FMDB?
FMDB是iOS平台的SQLite数据库框架
FMDB以OC的方式封装了SQLite的C语言API
2. FMDB有什么优势?
使用起来更加面向对象,省去了很多麻烦、冗余的C语言代码
提供了多线程安全的数据库操作方法,有效地防止数据混乱
3. 安装方式
Cocoapods
use_frameworks!
使用dynamic frameworks的方式集成
pod 'FMDB'
手动集成(swift)
1. 导入FMDB文件
2. 导入系统依赖库sqlite3.0.tbd
3. 建立桥接文件, 并导入需要的头文件
4. 核心类
FMDatabase
一个FMDatabase对象就代表一个单独的SQLite数据库
用来执行SQL语句
FMResultSet
使用FMDatabase执行查询后的结果集
FMDatabaseQueue
用于在多线程中执行多个查询或更新,它是线程安全的
5. 使用步骤
打开数据库
通过指定SQLite数据库文件路径来创建FMDatabase对象
文件路径有三种情况
具体文件路径
如果不存在会自动创建
空字符串@""
会在临时目录创建一个空的数据库
当FMDatabase连接关闭时,数据库文件也被删除
nil
会创建一个内存中临时数据库,
当FMDatabase连接关闭时,数据库会被销毁
FMDatabase *db = [FMDatabase databaseWithPath:path];
if ([db open]) { NSLog(@"打开成功!"); }
执行更新
在FMDB中,除查询以外的所有操作,都称为“更新”
create、drop、insert、update、delete等
使用executeUpdate:方法执行更新
- (BOOL)executeUpdate:(NSString*)sql, ...
- (BOOL)executeUpdateWithFormat:(NSString*)format, ...
- (BOOL)executeUpdate:(NSString)sql withArgumentsInArray:(NSArray )arguments
示例
[db executeUpdate:@"UPDATE t_student SET age = ? WHERE name = ?;", @20, @"Jack"]
执行查询
查询方法
- (FMResultSet )executeQuery:(NSString)sql, ...
- (FMResultSet )executeQueryWithFormat:(NSString)format, ...
- (FMResultSet )executeQuery:(NSString )sql withArgumentsInArray:(NSArray *)arguments
demo
// 查询数据
FMResultSet *rs = [db executeQuery:@"SELECT * FROM t_student"];
// 遍历结果集
while ([rs next]) {
NSString *name = [rs stringForColumn:@"name"];
int age = [rs intForColumn:@"age"];
double score = [rs doubleForColumn:@"score"];
}
关闭数据库
database.close()
【录播】20.(掌握)FMDabaseQueue(9分钟)
FMDatabaseQueue介绍
FMDatabase这个类是线程不安全的,如果在多个线程中同时使用一个FMDatabase实例,会造成数据混乱等问题
为了保证线程安全,FMDB提供方便快捷的FMDatabaseQueue类
FMDatabaseQueue的创建
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:path];
简单使用
[queue inDatabase:^(FMDatabase *db) {
[db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jack"];
// 查询
FMResultSet *rs = [db executeQuery:@"select * from t_student"];
}];
使用事务
[queue inTransaction:^(FMDatabase db, BOOL rollback) {
[db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jack"];
//查询
FMResultSet *rs = [db executeQuery:@"select * from t_student"];
}];
【录播】21.- 总结(18分钟)
行者常至,为者常成!