前言:
之前學習了資料儲存的NSUserDefaults,歸檔和解檔,沙盒檔案儲存,但是對於資料量比較大,需要頻繁查詢,刪除,更新等操作的時候無論從效率上還是效能上,上述三種明顯不能滿足我們的日常開發需要了。這個時候我們必須藉助資料庫,做為Android開發的都知道採用的是一種輕量級資料庫Sqlite。其實它廣泛用於包括瀏覽器、IOS,Android以及一些便攜需求的小型web應用系統。它具備佔用資源低,處理速度快等優點。接下來我們具體認識一下。
我們在專案開發中需要引入libsqlite3.dylib,那麼sqllite有哪些具體方法呢?
sqlite3 *db, 資料庫控制程式碼,跟檔案控制程式碼FILE很類似 sqlite3_stmt *stmt, 這個相當於ODBC的Command物件,用於儲存編譯好的SQL語句 sqlite3_open(), 開啟資料庫,沒有資料庫時建立。 sqlite3_exec(), 執行非查詢的sql語句 Sqlite3_step(), 在呼叫sqlite3_prepare後,使用這個函式在記錄集中移動。 Sqlite3_close(), 關閉資料庫檔案還有一系列的函式,用於從記錄集欄位中獲取資料,如 sqlite3_column_text(), 取text型別的資料。 sqlite3_column_blob(),取blob型別的資料 sqlite3_column_int(), 取int型別的資料
為了系統而且方面的學習sqlite 整理一個sqlite管理類DBManager,實現功能具體涵蓋了:資料庫的建立,開啟,關閉,升級,資料的增刪改查,以及事務的開啟和開啟事務的好處。
DBManager.h
#import <Foundation/Foundation.h> @interface DBManager : NSObject<NSCopying> //建立資料庫管理者單例 +(instancetype)shareManager; //開啟資料庫 -(void)openDb; //關閉資料庫 -(void)closeDb; //執行sql語句 -(void)execSql:(NSString *)sql; //建立資料庫表 -(void)creatTable; //刪除表結構 -(void)dropTable; //插入資料 -(void)insertData:(NSString*)tempName; //插入資料未開啟事務 -(void)insertDataByNomarl:(NSArray*)tempNames; //插入資料開啟事務 -(void)insertDataByTransaction:(NSArray*)tempNames; //刪除資料 -(void)deleteData:(NSString*)tempName; //刪除資料 -(void)deleteData; //修改資料 -(void)updateData:(NSString*)tempName; //查詢資料 -(void)queryData; @end
DBManager.m
#import "DBManager.h" #import <sqlite3.h> #define DBNAME @"myDb" //資料庫名字 #define TBNAME @"persons" //表名 #define DBVERSION 1 //資料庫版本 #define DBVERSIONKEY @"dbversion_key" //儲存資料庫版本key static DBManager *instance=nil; @implementation DBManager { //建立資料庫例項 sqlite3 *db; } -(instancetype)init { self=[super init]; if (self) { [self creatTable]; [self upgrade]; } return self; } //建立資料庫管理者單例 +(instancetype)shareManager { if(instance==nil){ @synchronized(self){ if(instance==nil){ instance =[[[self class]alloc]init]; } } } return instance; } -(id)copyWithZone:(NSZone *)zone { return instance; } +(id)allocWithZone:(struct _NSZone *)zone { if(instance==nil){ instance =[super allocWithZone:zone]; } return instance; } //開啟資料庫 -(void)openDb { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documents = [paths objectAtIndex:0]; NSString *database_path = [documents stringByAppendingPathComponent:DBNAME]; if (sqlite3_open([database_path UTF8String], &db) == SQLITE_OK) { NSLog(@"資料庫開啟成功"); }else{ [self closeDb]; NSLog(@"資料庫開啟失敗"); } } //關閉資料庫 -(void)closeDb { sqlite3_close(db); } //檢查資料庫是否需要升級 - (void)upgrade { //獲取儲存好的原版本號 NSInteger oldVersionNum = [[NSUserDefaults standardUserDefaults] integerForKey:DBVERSIONKEY]; if (DBVERSION <= oldVersionNum || oldVersionNum == 0) { return; } //升級 [self upgrade:oldVersionNum]; // 儲存新的版本號到庫中 -這裡大家可以使用NSUserDefault儲存 [[NSUserDefaults standardUserDefaults]setInteger:DBVERSION forKey:DBVERSIONKEY]; } //根據不同版本執行不同的升級邏輯 - (void)upgrade:(NSInteger)oldVersion { if (oldVersion >= DBVERSION) { return; } switch (oldVersion) { case 0: //執行相應的升級操作 break; case 1: //執行相應的升級操作 break; case 2: //執行相應的升級操作 break; default: break; } oldVersion ++; // 遞迴判斷是否需要升級 [self upgrade:oldVersion]; } //執行sql語句 -(void)execSql:(NSString *)sql { [self openDb]; char *err; if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) { NSLog(@"資料庫運算元據成功!"); }else{ sqlite3_free(err); NSLog(@"資料庫運算元據失敗!"); } sqlite3_close(db); } //建立資料庫表 -(void)creatTable { NSString *creatTableSql=[NSString stringWithFormat:@"create table if not exists %@(person_id integer primary key,name text)",TBNAME]; [self execSql:creatTableSql]; } //刪除資料庫表 -(void)dropTable { NSString *dropTableSql=[NSString stringWithFormat:@"drop table %@",TBNAME]; [self execSql:dropTableSql]; } //插入資料 -(void)insertData:(NSString*)tempName { NSString *insertSql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,tempName]; [self execSql:insertSql]; } //插入資料未開啟事務 -(void)insertDataByNomarl:(NSArray*)tempNames { [self openDb]; for(NSString *name in tempNames){ NSString *sql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name]; char *err; if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) { NSLog(@"資料庫運算元據成功!"); }else{ sqlite3_free(err); NSLog(@"資料庫運算元據失敗!"); } } [self closeDb]; } //插入資料開啟事務 -(void)insertDataByTransaction:(NSArray*)tempNames { @try{ char *errorMsg; [self openDb]; if (sqlite3_exec(db, "BEGIN", NULL, NULL, &errorMsg)==SQLITE_OK) { NSLog(@"啟動事務成功"); sqlite3_free(errorMsg); //執行真正的操作 for(NSString *name in tempNames){ NSString *sql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name]; char *err; if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) { NSLog(@"資料庫運算元據成功!"); }else{ sqlite3_free(err); NSLog(@"資料庫運算元據失敗!"); } } if (sqlite3_exec(db, "COMMIT", NULL, NULL, &errorMsg)==SQLITE_OK) { NSLog(@"提交事務成功"); } sqlite3_free(errorMsg); }else{ sqlite3_free(errorMsg); } } @catch(NSException *e){ char *errorMsg; if (sqlite3_exec(db, "ROLLBACK", NULL, NULL, &errorMsg)==SQLITE_OK) { NSLog(@"回滾事務成功"); } sqlite3_free(errorMsg); } [self closeDb]; } //刪除資料 -(void)deleteData:(NSString*)tempName { NSString *deleteSql=[NSString stringWithFormat:@"delete from %@ where name = '%@'",TBNAME,tempName]; [self execSql:deleteSql]; } //刪除資料 -(void)deleteData { NSString *deleteSql=[NSString stringWithFormat:@"delete from %@ ",TBNAME]; [self execSql:deleteSql]; } //修改資料 -(void)updateData:(NSString*)tempName { NSString *updateSql=[NSString stringWithFormat:@"update %@ set name ='test' where name = '%@'",TBNAME,tempName]; [self execSql:updateSql]; } //查詢資料 -(void)queryData { [self openDb]; NSString *querySql =[NSString stringWithFormat:@"select * from %@",TBNAME]; sqlite3_stmt *stmt; if (sqlite3_prepare_v2(db, [querySql UTF8String], -1, &stmt, nil) == SQLITE_OK) { while (sqlite3_step(stmt)==SQLITE_ROW) { char *name = (char *)sqlite3_column_text(stmt, 1); NSString *nameString = [[NSString alloc] initWithUTF8String:name]; NSLog(@"nameString---->%@",nameString); } sqlite3_finalize(stmt); } [self closeDb]; } @end
具體使用方法:
#import "DBManager.h" #import <sqlite3.h> #define DBNAME @"myDb" //資料庫名字 #define TBNAME @"persons" //表名 #define DBVERSION 1 //資料庫版本 #define DBVERSIONKEY @"dbversion_key" //儲存資料庫版本key static DBManager *shareManager=nil; @implementation DBManager { //建立資料庫例項 sqlite3 *db; } -(instancetype)init { self=[super init]; if (self) { [self creatTable]; [self upgrade]; } return self; } //建立資料庫管理者單例 +(instancetype)shareManager { if(shareManager==nil){ @synchronized(self){ if(shareManager==nil){ shareManager =[[[self class]alloc]init]; } } } return shareManager; } -(id)copyWithZone:(NSZone *)zone { return shareManager; } +(id)allocWithZone:(struct _NSZone *)zone { if(shareManager==nil){ shareManager =[super allocWithZone:zone]; } return shareManager; } //開啟資料庫 -(void)openDb { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documents = [paths objectAtIndex:0]; NSString *database_path = [documents stringByAppendingPathComponent:DBNAME]; if (sqlite3_open([database_path UTF8String], &db) == SQLITE_OK) { NSLog(@"資料庫開啟成功"); }else{ [self closeDb]; NSLog(@"資料庫開啟失敗"); } } //關閉資料庫 -(void)closeDb { sqlite3_close(db); } //刪除資料庫 -(void)dropDb { } //檢查資料庫是否需要升級 - (void)upgrade { //獲取儲存好的原版本號 NSInteger oldVersionNum = [[NSUserDefaults standardUserDefaults] integerForKey:DBVERSIONKEY]; if (DBVERSION <= oldVersionNum || oldVersionNum == 0) { return; } //升級 [self upgrade:oldVersionNum]; // 儲存新的版本號到庫中 -這裡大家可以使用NSUserDefault儲存 [[NSUserDefaults standardUserDefaults]setInteger:DBVERSION forKey:DBVERSIONKEY]; } //根據不同版本執行不同的升級邏輯 - (void)upgrade:(NSInteger)oldVersion { //對比資料庫版本 if (oldVersion >= DBVERSION) { return; } switch (oldVersion) { case 0: //執行相應的升級操作 break; case 1: //執行相應的升級操作 break; case 2: //執行相應的升級操作 break; default: break; } oldVersion ++; // 遞迴判斷是否需要升級 [self upgrade:oldVersion]; } //執行sql語句 -(void)execSql:(NSString *)sql { [self openDb]; char *err; if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) { NSLog(@"資料庫運算元據成功!"); }else{ sqlite3_free(err); NSLog(@"資料庫運算元據失敗!"); } sqlite3_close(db); } //建立資料庫表 -(void)creatTable { NSString *creatTableSql=[NSString stringWithFormat:@"create table if not exists %@(person_id integer primary key,name text)",TBNAME]; [self execSql:creatTableSql]; } //刪除資料庫表 -(void)dropTable { NSString *dropTableSql=[NSString stringWithFormat:@"drop table %@",TBNAME]; [self execSql:dropTableSql]; } //插入資料 -(void)insertData:(NSString*)tempName { NSString *insertSql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,tempName]; [self execSql:insertSql]; } //插入資料未開啟事務 -(void)insertDataByNomarl:(NSArray*)tempNames { [self openDb]; for(NSString *name in tempNames){ NSString *sql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name]; char *err; if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) { //NSLog(@"資料庫運算元據成功!"); }else{ sqlite3_free(err); //NSLog(@"資料庫運算元據失敗!"); } } [self closeDb]; } //插入資料開啟事務 -(void)insertDataByTransaction:(NSArray*)tempNames { @try{ char *errorMsg; [self openDb]; if (sqlite3_exec(db, "BEGIN", NULL, NULL, &errorMsg)==SQLITE_OK) { NSLog(@"啟動事務成功"); sqlite3_free(errorMsg); //執行真正的操作 for(NSString *name in tempNames){ NSString *sql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name]; char *err; if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) { //NSLog(@"資料庫運算元據成功!"); }else{ sqlite3_free(err); // NSLog(@"資料庫運算元據失敗!"); } } if (sqlite3_exec(db, "COMMIT", NULL, NULL, &errorMsg)==SQLITE_OK) { NSLog(@"提交事務成功"); } sqlite3_free(errorMsg); }else{ sqlite3_free(errorMsg); } } @catch(NSException *e){ char *errorMsg; if (sqlite3_exec(db, "ROLLBACK", NULL, NULL, &errorMsg)==SQLITE_OK) { NSLog(@"回滾事務成功"); } sqlite3_free(errorMsg); } [self closeDb]; } //刪除資料 -(void)deleteData:(NSString*)tempName { NSString *deleteSql=[NSString stringWithFormat:@"delete from %@ where name = '%@'",TBNAME,tempName]; [self execSql:deleteSql]; } //刪除資料 -(void)deleteData { NSString *deleteSql=[NSString stringWithFormat:@"delete from %@ ",TBNAME]; [self execSql:deleteSql]; } //修改資料 -(void)updateData:(NSString*)tempName { NSString *updateSql=[NSString stringWithFormat:@"update %@ set name ='test' where name = '%@'",TBNAME,tempName]; [self execSql:updateSql]; } //查詢資料 -(void)queryData { [self openDb]; NSString *querySql =[NSString stringWithFormat:@"select * from %@",TBNAME]; sqlite3_stmt *stmt; if (sqlite3_prepare_v2(db, [querySql UTF8String], -1, &stmt, nil) == SQLITE_OK) { while (sqlite3_step(stmt)==SQLITE_ROW) { char *name = (char *)sqlite3_column_text(stmt, 1); NSString *nameString = [[NSString alloc] initWithUTF8String:name]; NSLog(@"nameString---->%@",nameString); } sqlite3_finalize(stmt); } [self closeDb]; } @end
重點來了,曾經做個IM即時通訊方面,聊天資訊相對來說還是比較龐大一點,動不動就是成千上萬條聊天資訊,有時候執行一個訊息已讀狀態的更新都要耗時很長,那時候偶還沒有學習IOS開發,在Android平臺上我已經領略過開啟事務對效率的提升所帶來的喜悅了,那麼ios上面是怎麼開啟事務的呢?效率怎麼樣呢?讓我們一探究竟:
開啟事務:
//插入資料開啟事務 -(void)insertDataByTransaction:(NSArray*)tempNames { @try{ char *errorMsg; [self openDb]; if (sqlite3_exec(db, "BEGIN", NULL, NULL, &errorMsg)==SQLITE_OK) { NSLog(@"啟動事務成功"); sqlite3_free(errorMsg); //執行真正的操作 for(NSString *name in tempNames){ NSString *sql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name]; char *err; if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) { NSLog(@"資料庫運算元據成功!"); }else{ sqlite3_free(err); NSLog(@"資料庫運算元據失敗!"); } } if (sqlite3_exec(db, "COMMIT", NULL, NULL, &errorMsg)==SQLITE_OK) { NSLog(@"提交事務成功"); } sqlite3_free(errorMsg); }else{ sqlite3_free(errorMsg); } } @catch(NSException *e){ char *errorMsg; if (sqlite3_exec(db, "ROLLBACK", NULL, NULL, &errorMsg)==SQLITE_OK) { NSLog(@"回滾事務成功"); } sqlite3_free(errorMsg); } [self closeDb]; }
同時準備一個未開啟事務的:
//插入資料未開啟事務 -(void)insertDataByNomarl:(NSArray*)tempNames { [self openDb]; for(NSString *name in tempNames){ NSString *sql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name]; char *err; if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) { NSLog(@"資料庫運算元據成功!"); }else{ sqlite3_free(err); NSLog(@"資料庫運算元據失敗!"); } } [self closeDb]; }
測試程式:
//測試事務 NSMutableArray *testArray =[[NSMutableArray alloc]init]; int testMaxCount =10000; for(int i=0;i<testMaxCount;i++){ NSString *string = [[NSString alloc] initWithFormat:@"%d",i]; [testArray addObject:string]; } //未開啟事務插入 CFAbsoluteTime start = CFAbsoluteTimeGetCurrent(); [[DBManager shareManager]insertDataByNomarl:testArray]; CFAbsoluteTime end=CFAbsoluteTimeGetCurrent(); NSLog(@"普通插入 time cost: %0.3f", end - start); //刪除資料 [[DBManager shareManager]deleteData]; //開啟事務插入 start = CFAbsoluteTimeGetCurrent(); [[DBManager shareManager]insertDataByTransaction:testArray]; end=CFAbsoluteTimeGetCurrent(); NSLog(@"開啟事務插入 time cost: %0.3f", end - start); //刪除資料 [[DBManager shareManager]deleteData];
測試結果:測試資料10000條 單位(秒)
開啟事務耗時:0.049
未開啟事務耗時:5.614
看到上面的執行結果 是不是驚呆了。
關於資料庫升級:由於專案業務發展,資料庫有可能要考慮到升級,比如資料庫新增表或者已有表結構變化,這時候我們就要考慮到資料升級來做版本相容:
什麼時候檢查:
-(instancetype)init { self=[super init]; if (self) { [self creatTable]; [self upgrade]; } return self; }
怎麼實現版本升級:
//檢查資料庫是否需要升級 - (void)upgrade { //獲取儲存好的原版本號 NSInteger oldVersionNum = [[NSUserDefaults standardUserDefaults] integerForKey:DBVERSIONKEY]; if (DBVERSION <= oldVersionNum || oldVersionNum == 0) { return; } //升級 [self upgrade:oldVersionNum]; // 儲存新的版本號到庫中 -這裡大家可以使用NSUserDefault儲存 [[NSUserDefaults standardUserDefaults]setInteger:DBVERSION forKey:DBVERSIONKEY]; } //根據不同版本執行不同的升級邏輯 - (void)upgrade:(NSInteger)oldVersion { //對比資料庫版本 if (oldVersion >= DBVERSION) { return; } switch (oldVersion) { case 0: //執行相應的升級操作 break; case 1: //執行相應的升級操作 break; case 2: //執行相應的升級操作 break; default: break; } oldVersion ++; // 遞迴判斷是否需要升級 [self upgrade:oldVersion]; }
至此原生的Sqlite基礎使用就告一段落了,至於高階使用一般情況涉及到的多數是sql語句的使用,sql語句不善長的小夥伴可以去熟悉一下sql資料!這時就在想了IOS有沒有像Android一樣的第三方資料庫框架呢?也讓我等sql小白緩解一下壓力?特意查詢了一下,以下僅供參考:Sqlitepersistentobjects ,FMDB(這個在兩年前使用過)。