天天看點

FMDB的增删改查,事務和線程安全1.簡介2.核心類3.打開資料庫和c語言架構一樣,FMDB通過指定SQLite資料庫檔案路徑來建立FMDatabase對象,但FMDB更加容易了解,使用起來更容易,使用之前一樣需要導入sqlite3.dylib。打開資料庫,建立表(也叫更新)的方法如下:5.查詢6 :條件删除的方法7.線程安全

本文主要從以下幾個方面介紹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