本文主要從以下幾個方面介紹FMDB,儲存,查詢,條件查詢,更新,條件删除來介紹
本文示範代碼下載下傳位址
螢幕快照 2017-01-11 下午11.30.22.png
本文生成的資料表使用Navicat打開
螢幕快照 2017-01-12 上午12.19.46.png
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)[email protected]"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)[email protected]"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)[email protected]"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);
}
連結:https://www.jianshu.com/p/71ed016cb1fe