本文主要從以下幾個方面介紹FMDB,保存,查詢,條件查詢,更新,條件刪除來介紹
本文演示代碼下載地址
本文生成的數據表使用Navicat打開
1.簡介
FMDB是iOS平臺的SQLite數據庫框架,它是以OC的方式封裝了SQLite的C語言API,它相對于cocoa自帶的C語言框架有如下的優點:
使用起來更加面向對象,省去了很多麻煩、冗余的C語言代碼
對比蘋果自帶的Core Data框架,更加輕量級和靈活
提供了多線程安全的數據庫操作方法,有效地防止數據混亂
2.核心類
FMDB有三個主要的類:
FMDatabase
一個FMDatabase對象就代表一個單獨的SQLite數據庫,用來執行SQL語句
FMResultSet
使用FMDatabase執行查詢后的結果集
FMDatabaseQueue
用于在多線程中執行多個查詢或更新,它是線程安全的
3.打開數據庫和c語言框架一樣,FMDB通過指定SQLite數據庫文件路徑來創建FMDatabase對象,但FMDB更加容易理解,使用起來更容易,使用之前一樣需要導入sqlite3.dylib。打開數據庫,創建表(也叫更新)的方法如下:
//獲取Document文件夾下的數據庫文件,沒有則創建
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *dbPath = [docPath stringByAppendingPathComponent:@"user.db"];
NSLog(@"dbPath = %@",dbPath);
//獲取數據庫并打開
FMDatabase *dataBase = [FMDatabase databaseWithPath:dbPath];
if (![dataBase open]) {
NSLog(@"打開數據庫失敗");
return ;
}
//創建表(FMDB中只有update和query操作,除了查詢其他都是update操作)
[dataBase executeUpdate:@"create table if not exists user(name text,gender text,age integer) "];
插入數據的操作:
//常用方法有以下3種:
/* 執行更新的SQL語句,字符串里面的"?",依次用后面的參數替代,必須是對象,不能是int等基本類型 */
- (BOOL)executeUpdate:(NSString *)sql,... ;
/* 執行更新的SQL語句,可以使用字符串的格式化進行構建SQL語句 */
- (BOOL)executeUpdateWithFormat:(NSString*)format,... ;
/* 執行更新的SQL語句,字符串中有"?",依次用arguments的元素替代 */
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;
/* 1. 直接使用完整的SQL更新語句 */
[database executeUpdate:@"insert into mytable(num,name,sex) values(0,'liuting','m');"];
NSString *sql = @"insert into mytable(num,name,sex) values(?,?,?);";
/* 2. 使用不完整的SQL更新語句,里面含有待定字符串"?",需要后面的參數進行替代 */
[database executeUpdate:sql,@0,@"liuting",@"m"];
/* 3. 使用不完整的SQL更新語句,里面含有待定字符串"?",需要數組參數里面的參數進行替代 */
[database executeUpdate:sql
withArgumentsInArray:@[@0,@"liuting",@"m"]];
/* 4. SQL語句字符串可以使用字符串格式化,這種我們應該比較熟悉 */
[database executeUpdateWithFormat:@"insert into mytable(num,name,sex) values(%d,%@,%@);",0,@"liuting","m"];
本demo采用第二種方法
//常用方法有以下3種:
// - (BOOL)executeUpdate:(NSString*)sql, ...
// - (BOOL)executeUpdateWithFormat:(NSString*)format, ...
// - (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments
//插入數據
BOOL inser = [dataBase executeUpdate:@"insert into user values(?,?,?)",_nameTextField.text,_sexTextField.text,_ageTextField.text];
if (inser) {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"提示" message:@"信息保存成功" delegate:self cancelButtonTitle:nil otherButtonTitles:@"確定", nil];
[alert show];
}
[dataBase close];
5.查詢
查詢方法也有3種,使用起來相當簡單:
// 全部查詢
- (FMResultSet *)executeQuery:(NSString*)sql, ...
// 條件查詢
- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments
// 演示如下:
//1.執行查詢
FMResultSet *result = [database executeQuery:@"SELECT * FROM t_person"];
//2.遍歷結果集
while ([result next]) {
NSString *name = [result stringForColumn:@"name"];
int age = [result intForColumn:@"age"];
}
/*
FMResultSet獲取不同數據格式的方法
/* 獲取下一個記錄 */
- (BOOL)next;
/* 獲取記錄有多少列 */
- (int)columnCount;
/* 通過列名得到列序號,通過列序號得到列名 */
- (int)columnIndexForName:(NSString *)columnName;
- (NSString *)columnNameForIndex:(int)columnIdx;
/* 獲取存儲的整形值 */
- (int)intForColumn:(NSString *)columnName;
- (int)intForColumnIndex:(int)columnIdx;
/* 獲取存儲的長整形值 */
- (long)longForColumn:(NSString *)columnName;
- (long)longForColumnIndex:(int)columnIdx;
/* 獲取存儲的布爾值 */
- (BOOL)boolForColumn:(NSString *)columnName;
- (BOOL)boolForColumnIndex:(int)columnIdx;
/* 獲取存儲的浮點值 */
- (double)doubleForColumn:(NSString *)columnName;
- (double)doubleForColumnIndex:(int)columnIdx;
/* 獲取存儲的字符串 */
- (NSString *)stringForColumn:(NSString *)columnName;
- (NSString *)stringForColumnIndex:(int)columnIdx;
/* 獲取存儲的日期數據 */
- (NSDate *)dateForColumn:(NSString *)columnName;
- (NSDate *)dateForColumnIndex:(int)columnIdx;
/* 獲取存儲的二進制數據 */
- (NSData *)dataForColumn:(NSString *)columnName;
- (NSData *)dataForColumnIndex:(int)columnIdx;
/* 獲取存儲的UTF8格式的C語言字符串 */
- (const unsigned cahr *)UTF8StringForColumnName:(NSString *)columnName;
- (const unsigned cahr *)UTF8StringForColumnIndex:(int)columnIdx;
/* 獲取存儲的對象,只能是NSNumber、NSString、NSData、NSNull */
- (id)objectForColumnName:(NSString *)columnName;
- (id)objectForColumnIndex:(int)columnIdx;
*/
本文demo中代碼演示:
//查詢全部
- (IBAction)query:(id)sender {
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *dbPath = [docPath stringByAppendingPathComponent:@"user.db"];
FMDatabase *dataBase = [FMDatabase databaseWithPath:dbPath];
if (![dataBase open]) {
NSLog(@"打開數據庫失敗");
return ;
}
FMResultSet *resultSet = [dataBase executeQuery:@"select * from user"];
while ([resultSet next]) {
NSString *name = [resultSet stringForColumn:@"name"];
NSString *genter = [resultSet stringForColumn:@"gender"];
int age = [resultSet intForColumn:@"age"];
NSLog(@"Name:%@,Gender:%@,Age:%d",name,genter,age);
}
[dataBase close];
}
//條件查詢
- (IBAction)queryByCondition:(id)sender {
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *dbPath = [docPath stringByAppendingPathComponent:@"user.db"];
FMDatabase *dataBase = [FMDatabase databaseWithPath:dbPath];
if (![dataBase open]) {
return ;
}
// FMResultSet *resultSet = [dataBase executeQuery:@"select *from user where name = ?",@"ZY"];
FMResultSet *resultSet = [dataBase executeQueryWithFormat:@"select * from user where name = %@",@"zy"];
while ([resultSet next]) {
NSString *name = [resultSet stringForColumnIndex:0];
NSString *gender = [resultSet stringForColumn:@"gender"];
int age = [resultSet intForColumn:@"age"];
NSLog(@"Name:%@,Gender:%@,Age:%d",name,gender,age);
}
[dataBase close];
}
6 :條件刪除的方法
- (IBAction)deleteByCondition:(id)sender
{
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *dbPath = [docPath stringByAppendingPathComponent:@"user.db"];
FMDatabase *dataBase = [FMDatabase databaseWithPath:dbPath];
if (![dataBase open]) {
return ;
}
BOOL delete = [dataBase executeUpdateWithFormat:@"delete from user where name = %@",@"zy"];
if (delete) {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"提示" message:@"信息刪除成功" delegate:self cancelButtonTitle:nil otherButtonTitles:@"確定", nil];
[alert show];
}
[dataBase close];
}
7.線程安全
產考使用FMDB事務批量更新數據庫速度問題里面的代碼進行使用
在多個線程中同時使用一個FMDatabase實例是不明智的。不要讓多個線程分享同一個FMDatabase實例,它無法在多個線程中同時使用。 如果在多個線程中同時使用一個FMDatabase實例,會造成數據混亂等問題。所以,請使用 FMDatabaseQueue,它是線程安全的。以下是使用方法:
1. 創建
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES).firstObject;
NSString *filePath = [path stringByAppendingPathComponent:@"FMDB.db"];
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:path];
2. 操作數據庫
[queue inDatabase:^(FMDatabase*db) {
//FMDatabase數據庫操作
}];
**3.本文的使用實例
- (IBAction)save:(id)sender {
//獲取Document文件夾下的數據庫文件,沒有則創建
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *dbPath = [docPath stringByAppendingPathComponent:@"user.db"];
NSLog(@"dbPath = %@",dbPath);
//獲取數據庫并打開
// FMDatabase *dataBase = [FMDatabase databaseWithPath:dbPath];
//多線程安全FMDatabaseQueue可以替代dataBase
FMDatabaseQueue *dataBasequeue = [FMDatabaseQueue databaseQueueWithPath:dbPath];
[dataBasequeue inDatabase:^(FMDatabase *db) {
if (![db open]) {
NSLog(@"打開數據庫失敗");
return ;
}
//創建表(FMDB中只有update和query操作,除了查詢其他都是update操作)
[db executeUpdate:@"create table if not exists user(name text,gender text,age integer) "];
//常用方法有以下3種:
// - (BOOL)executeUpdate:(NSString*)sql, ...
// - (BOOL)executeUpdateWithFormat:(NSString*)format, ...
// - (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments
//插入數據
BOOL inser = [db executeUpdate:@"insert into user values(?,?,?)",_nameTextField.text,_sexTextField.text,_ageTextField.text];
if (inser) {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"提示" message:@"信息保存成功" delegate:self cancelButtonTitle:nil otherButtonTitles:@"確定", nil];
[alert show];
}
[db close];
}];
}
7:而且可以輕松地把簡單任務包裝到事務里:
之所以將事務放到FMDB中去說并不是因為只有FMDB才支持事務,而是因為FMDB將其封裝成了幾個方法來調用,不用自己寫對應的SQL而已,假如你要對數據庫中的Stutent表插入新數據,那么該事務的具體過程是:開始新事物->插入數據->提交事務,那么當我們要往該表內插入500條數據,如果按常規操作處理就要執行500次“開始新事物->插入數據->提交事務”的過程。
好吧,今天的重點來了,舉個例子:假如北京的一家A工廠接了上海一家B公司的500件產品的訂單,思考一下:A工廠是生產完一件立即就送到B公司還是將500件產品全部生產完成后再送往B公司?答案肯定是后者,因為前者浪費了大量的時間、人力物力花費在往返于北京和上海之間。同樣這個道理也能用在我們的數據庫操作上,下面是我自己對使用事務和不使用事務的兩種測試:
SQLite進行事務的SQL語句:
只要在執行SQL語句前加上以下的SQL語句,就可以使用事務功能了:
開啟事務的SQL語句,"begin transaction;"
進行提交的SQL語句,"commit transaction;"
進行回滾的SQL語句,"rollback transaction;"
一: FMDatabase使用事務的方法:
-(void)transaction {
NSDate *date1 = [NSDate date];
// 創建表
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *dbPath = [docPath stringByAppendingPathComponent:@"mytable1.db"];
NSLog(@"dbPath = %@",dbPath);
FMDatabase *dataBase = [FMDatabase databaseWithPath:dbPath];
// 注意這里的判斷一步都不能少,特別是這里open的判斷
if (![dataBase open]) {
NSLog(@"打開數據庫失敗");
return ;
}
NSString *sqlStr = @"create table if not exists mytable1(num integer,name varchar(7),sex char(1),primary key(num));";
BOOL res = [dataBase executeUpdate:sqlStr];
if (!res) {
NSLog(@"error when creating mytable1");
[dataBase close];
}
// 開啟事務
[dataBase beginTransaction];
BOOL isRollBack = NO;
@try {
for (int i = 0; i<500; i++) {
NSNumber *num = @(i+1);
NSString *name = [[NSString alloc] initWithFormat:@"student_%d",i];
NSString *sex = (i%2==0)?@"f":@"m";
NSString *sql = @"insert into mytable1(num,name,sex) values(?,?,?);";
BOOL result = [dataBase executeUpdate:sql,num,name,sex];
if ( !result ) {
NSLog(@"插入失敗!");
return;
}
}
}
@catch (NSException *exception) {
isRollBack = YES;
// 事務回退
[dataBase rollback];
}
@finally {
if (!isRollBack) {
//事務提交
[dataBase commit];
}
}
[dataBase close];
NSDate *date2 = [NSDate date];
NSTimeInterval a = [date2 timeIntervalSince1970] - [date1 timeIntervalSince1970];
NSLog(@"FMDatabase使用事務插入500條數據用時%.3f秒",a);
}
二: FMDatabase不使用事務的方法:
//二: FMDatabase不使用事務的方法:
-(void)noTransaction {
NSDate *date1 = [NSDate date];
// 創建表
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *dbPath = [docPath stringByAppendingPathComponent:@"mytable3.db"];
NSLog(@"dbPath = %@",dbPath);
FMDatabase *dataBase = [FMDatabase databaseWithPath:dbPath];
// 注意這里的判斷一步都不能少,特別是這里open的判斷
if (![dataBase open]) {
NSLog(@"打開數據庫失敗");
return ;
}
NSString *sqlStr = @"create table if not exists mytable3(num integer,name varchar(7),sex char(1),primary key(num));";
BOOL res = [dataBase executeUpdate:sqlStr];
if (!res) {
NSLog(@"error when creating mytable1");
[dataBase close];
}
for (int i = 0; i<500; i++) {
NSNumber *num = @(i+1);
NSString *name = [[NSString alloc] initWithFormat:@"student_%d",i];
NSString *sex = (i%2==0)?@"f":@"m";
NSString *sql = @"insert into mytable3(num,name,sex) values(?,?,?);";
BOOL result = [dataBase executeUpdate:sql,num,name,sex];
if ( !result ) {
NSLog(@"插入失敗!");
return;
}
}
[dataBase close];
NSDate *date2 = [NSDate date];
NSTimeInterval a = [date2 timeIntervalSince1970] - [date1 timeIntervalSince1970];
NSLog(@"FMDatabase不使用事務插入500條數據用時%.3f秒",a);
}
是否使用事務的比較結果如下:
2017-01-18 00:28:57.455 Location[5319:300127] FMDatabase使用事務插入500條數據用時0.018秒
2017-01-18 00:28:58.509 Location[5319:300127] FMDatabase不使用事務插入500條數據用時1.054秒
三: FMDatabaseQueue使用事務的方法:
//多線程事務
- (void)transactionByQueue {
NSDate *date1 = [NSDate date];
// 創建表
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *dbPath = [docPath stringByAppendingPathComponent:@"mytable2.db"];
//多線程安全FMDatabaseQueue可以替代dataBase
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:dbPath];
//開啟事務
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
if(![db open]){
return NSLog(@"事務打開失敗");
}
NSString *sqlStr = @"create table mytable2(num integer,name varchar(7),sex char(1),primary key(num));";
BOOL res = [db executeUpdate:sqlStr];
if (!res) {
NSLog(@"error when creating mytable2 table");
[db close];
}
for (int i = 0; i<500; i++) {
NSNumber *num = @(i+1);
NSString *name = [[NSString alloc] initWithFormat:@"student_%d",i];
NSString *sex = (i%2==0)?@"f":@"m";
NSString *sql = @"insert into mytable2(num,name,sex) values(?,?,?);";
BOOL result = [db executeUpdate:sql,num,name,sex];
if ( !result ) {
//當最后*rollback的值為YES的時候,事務回退,如果最后*rollback為NO,事務提交
*rollback = YES;
return;
}
}
[db close];
}];
NSDate *date2 = [NSDate date];
NSTimeInterval a = [date2 timeIntervalSince1970] - [date1 timeIntervalSince1970];
NSLog(@"FMDatabaseQueue使用事務插入500條數據用時%.3f秒",a);
}