我們都知道, 不管使用任何一種高級(jí)編程語言, 都會(huì)遇到需要做本地緩存的情況. 當(dāng)然, 本地緩存方式可能有多種方式. 但如果需要對(duì)本地緩存做靈活的操作, 基于sql 的數(shù)據(jù)庫(kù)依然是首選. 本文將從基礎(chǔ)的sql語句出發(fā), 衍申到 iOS 開發(fā)中對(duì)數(shù)據(jù)庫(kù)的應(yīng)用.
一. SQL基礎(chǔ)
SQL(結(jié)構(gòu)化查詢語言)給我們提供了數(shù)據(jù)庫(kù)的創(chuàng)表,刪表,數(shù)據(jù)的增刪改查等基本操作. 接下來將依次舉例演示這些操作.
創(chuàng)表
判斷如果不存在 StudentTable 則創(chuàng)建. 將 id 設(shè)為主鍵, 以分號(hào)結(jié)尾.
create table if not exists StudentTable (id integer primary key, name text not null, score integer not null);
如果主鍵不需要外界輸入, 而是通過默認(rèn)的累加方式, 可以將主鍵設(shè)為自增模式.
create table if not exists StudentTable (id integer primary key autoincrement, name text not null, score integer not null);
主鍵有默認(rèn)的唯一性, 如果需要將除主鍵意外的其他字段設(shè)置唯一性屬性, 需要用unique 來修飾該字段.
create table if not exists StudentTable (id integer primary key autoincrement, name text unique not null, score integer not null);刪表
刪除數(shù)據(jù)庫(kù)表StudentTable
delete from StudentTable;
- 增
insert into StudentTable(id,name,score) values ('1','小明','99');
- 刪
delete from StudentTable where id='1' and name='小明';
- 改
修改Student表中 id = 1 的這條數(shù)據(jù), 同時(shí)修改兩個(gè)字段. 修改name = '大明', 修改score='90' . 同時(shí)修改多個(gè)字段值時(shí), 中間用逗號(hào)隔開.
update StudentTable set name='大明',score='90' where id='1';
- 查
查詢 id = 1 的這條數(shù)據(jù)的所有字段值.
select * from StudentTable where id='1';
查詢id = 1 的這條數(shù)據(jù)對(duì)應(yīng)的name 的字段值.
select name from StudentTable where id = '1';
查詢score = 90 的這條數(shù)據(jù)的name 的字段值, 并按照id 的升序排列. (如果需要降序排列, 將asc 替換成 desc 即可)
select name from StudentTable where score = '90' order by id asc;
二. FMDB中SQL的使用
FMDB 是在iOS開發(fā)中常用的第三方數(shù)據(jù)庫(kù)操作框架, 其底層實(shí)現(xiàn)是對(duì)sqlite 的封裝. 下文中用到的self.dataBase 是FMDatabase 的單例.
1. FMDatabase 調(diào)用 sql 不需要返回?cái)?shù)據(jù)
FMDatabase 在調(diào)用sql 時(shí), 不需要返回?cái)?shù)據(jù)的情況有 增, 刪, 改. 在這種情況下, FMDatabase 提供了兩種調(diào)用方式.
- 通過
executeUpdate直接調(diào)用組裝好的sql, 不需要傳入?yún)?shù). (以增加一條記錄為例)
NSString *sqlString = [NSString stringWithFormat:@"insert into StudentTable(id,name,score) values ('%@','%@','%@');", @1, @"小明", @99];
BOOL result = [self.dataBase executeUpdate:sqlString];
- 通過
executeUpdateWithFormat調(diào)用sql. FMDataBase自帶有格式化輸入占位符 '?' ;
BOOL result = [self.dataBase executeUpdateWithFormat:@"insert into StudentTable(id,name,score) values (?,?,?)", @1, @"小明", @99];
2. FMDatabase 調(diào)用 sql 語句需要返回?cái)?shù)據(jù)
FMDatabase 在調(diào)用sql 時(shí),需要返回?cái)?shù)據(jù)的操作是 查詢. 查詢操作也有以上兩種調(diào)用方式
- 通過
executeQuery直接調(diào)用組裝好的sql, 不需要傳入?yún)?shù); - 通過
executeQueryWithFormat調(diào)用sql. FMDataBase自帶有格式化輸入占位符 '?' ;
查詢操作返回?cái)?shù)據(jù)類型是FMResultSet, 需要對(duì)這個(gè)類型的數(shù)據(jù)進(jìn)行解析, 轉(zhuǎn)成模型數(shù)據(jù).
- (NSArray *)selectStudentByID:(NSNumber *)ID {
NSMutableArray *resultArray = [NSMutableArray array];
FMResultSet *set = [self.dataBase executeQueryWithFormat:@"select * from StudentTable where id = ?;", ID];
while (set.next) {
Student *stu = [[Student alloc] init];
stu.ID = [set objectForColumnName:@"id"];
stu.name = [set objectForColumnName:@"name"];
stu.score = [set objectForColumnName:@"score"];
[resultArray addObject:key];
}
return resultArray;
}
三. 異步線程的FMDB
在項(xiàng)目中曾經(jīng)遇到這么一種情況, 快速切換頁(yè)面, 大量網(wǎng)絡(luò)數(shù)據(jù)需要更新本地?cái)?shù)據(jù)庫(kù)的緩存. 操作的特點(diǎn)是: 數(shù)據(jù)量大, 對(duì)數(shù)據(jù)庫(kù)的操作頻繁, 更有甚者,多個(gè)網(wǎng)絡(luò)數(shù)據(jù)流同時(shí)操作了同一張數(shù)據(jù)表. 那么這樣的操作必然不能放到主線程中執(zhí)行, 整個(gè)app 會(huì)卡頓的無法使用, 用戶體驗(yàn)相當(dāng)不好.
這時(shí), 我考慮使用異步線程, 將這些耗時(shí)操作放到子線程中去執(zhí)行. 事實(shí)上我也這么嘗試了, app 卡頓的現(xiàn)象真的解決了. 在測(cè)試過程中, 新的問題又出現(xiàn)了. 問題就是: 多個(gè)動(dòng)作同時(shí)操作數(shù)據(jù)庫(kù)時(shí), 未必所有動(dòng)作都會(huì)被執(zhí)行. 因?yàn)槟硰垟?shù)據(jù)表正在被操作, 其他動(dòng)作則無法操作而被跳過了. 這樣, 每次從本地緩存中讀取的數(shù)據(jù)就會(huì)和預(yù)期的有較大出入.
要解決以上問題, FMDB為我們提供了基于 NSOperationQueue 的多線程操作. 將所有操作放入隊(duì)列中, iOS系統(tǒng)會(huì)自動(dòng)分配子線程, 確保每個(gè)動(dòng)作都會(huì)被執(zhí)行, 不會(huì)因?yàn)閿?shù)據(jù)庫(kù)正在被操作而丟棄其他操作.
下文中用到的self.dataBaseQueue 是FMDatabaseQueue的單例.
- (void)insertStudentsArray:(NSArray *)studentsArray {
[self.dataBaseQueue inDatabase:^(FMDatabase *db) {
for (Student *stu in studentsArray) {
[self insertStu: stu];
}
}];
}
注意: FMDB提供的隊(duì)列操作是不能嵌套的, 不能隊(duì)列中再調(diào)用同一隊(duì)列. 在上面的例子中, -(void)insertStu:(Student*)stu; 這個(gè)方法內(nèi)部就不能再調(diào)用[self.dataBaseQueue inDatabase:^(FMDatabase *db) { }]; 這個(gè)方法了, 否則程序會(huì)崩潰.