[深入淺出iOS庫]之數據庫 sqlite
羅朝輝 (http://blog.csdn.net/kesalin/)
本文遵循“署名-非商業用途-保持一致”創作公用協議
一,sqlite 簡介
前面寫了一篇博文講如何在 C# 中使用 ADO 訪問各種數據庫,在移動開發和嵌入式領域也有一個輕量級的開源關係型數據庫-sqlite。它的特點是零配置(無需服務器),單磁盤文件存儲數據(就像fopen一樣),平臺無關性,使用起來簡單又高效。這些特點讓其非常適合移動開發和嵌入式領域。當然,sqlite 也因其力求簡單高效,也就限制了它對併發,海量數據的處理。下面,我就再接再厲,來講講如何在 iOS 中使用 sqlite 庫和第三方封裝庫 FMDB,以及介紹一個 MAC 下開源的可視化 sqlite 瀏覽器。
本文源碼:https://github.com/kesalin/iOSSnippet/tree/master/SQLiteDemo
二,在 iOS 中的使用
在 iOS 中 sqlite3 庫是一套純 C 的接口,因此很方便地就可以在 obj-c 源碼中無痕使用它,而且其使用方式與用 ADO 方式操作數據庫大同小異-少了創建數據庫鏈接一環而已(因爲 sqlite 沒有服務器的概念也就無需鏈接了)。
- 首先,需要引入 libsqlite3.0.dylib:
然後包含頭文件:
#import "/usr/include/sqlite3.h"
- 打開或創建數據庫
SQLITE_API int sqlite3_open( const char *filename, /* Database filename (UTF-8) */ sqlite3 **ppDb /* OUT: SQLite db handle */ );
使用示例:(dbPath 爲 NSString *)
// open database // int state = sqlite3_open([dbPath UTF8String], &database); if (state == SQLITE_OK) { DLOG(@" >> Succeed to open database. %@", dbPath); } else { DLOG(@" >> Failed to open database. %@", dbPath); }
- 關閉數據庫
SQLITE_API int sqlite3_close(sqlite3 *);
上面這個接口將關閉數據庫,如果當前還有事務沒有提交,會先執行 rollback 操作,然後再關閉數據庫。
- 執行 sql 語句
SQLITE_API int sqlite3_exec( sqlite3*, /* An open database */ const char *sql, /* SQL to be evaluated */ int (*callback)(void*,int,char**,char**), /* Callback function */ void *, /* 1st argument to callback */ char **errmsg /* Error msg written here */ );
這個接口是最常用到的,幾乎除了查詢之外的 sql 命令都可以用它來操作,比如創建表,插入/更新/刪除記錄,創建/提交/回滾事務等。注意:如果 errmsg 不爲 null,那麼當錯誤發生時, sqlite 就會爲錯誤消息分配內存,返回給調用者,調用者有責任調用 sqlite3_free 來釋放這部分內存。爲了方便使用,我封裝了一個簡單的 obj-c 方法:
- (BOOL)excuteSQLWithCString:(const char *)sqlCmd { char * errorMsg; int state = sqlite3_exec(database, sqlCmd, NULL, NULL, &errorMsg); if (state == SQLITE_OK) { DLOG(@" >> Succeed to %@", [NSString stringWithCString:sqlCmd encoding:NSUTF8StringEncoding]); } else { DLOG(@" >> Failed to %@. Error: %@", [NSString stringWithCString:sqlCmd encoding:NSUTF8StringEncoding], [NSString stringWithCString:errorMsg encoding:NSUTF8StringEncoding]); sqlite3_free(errorMsg); } return (state == SQLITE_OK); }
下面是創建表以及事務操作的使用示例:
- (void)createTable { if (database == NULL) { DLOG(@" >> Database does not open yet."); return; } const char * sqlCmd = "create table if not exists customer (id integer primary key autoincrement, name text not null, address text, age integer)"; [self excuteSQLWithCString:sqlCmd]; } - (BOOL)beginTransaction { return [self excuteSQLWithCString:"BEGIN EXCLUSIVE TRANSACTION;"]; } - (BOOL)commit { return [self excuteSQLWithCString:"COMMIT TRANSACTION;"]; } - (BOOL)rollback { return [self excuteSQLWithCString:"ROLLBACK TRANSACTION;"]; }
很簡單,不是麼?至於插入,更新,刪除示例,請參考如下 sqlCmd:
// insert NSString * sqlCmd = [NSString stringWithFormat:@"insert into customer (name, address, age) values ('%@', '%@', %d)", customer.name, customer.address, customer.age]; // update NSString * sqlCmd = [NSString stringWithFormat:@"update customer set address='%@',age=%d where name='%@'", newValue.address, newValue.age, oldValue.name]; // delete NSString * sqlCmd = [NSString stringWithFormat:@"delete from customer where name='%@'", customer.name];
- 查詢操作
查詢操作稍微負責一點,需要創建查詢描述(sqlite3_stmt),然後調用如下接口編譯成字節程序:
SQLITE_API int sqlite3_prepare_v2( sqlite3 *db, /* Database handle */ const char *zSql, /* SQL statement, UTF-8 encoded */ int nByte, /* Maximum length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const char **pzTail /* OUT: Pointer to unused portion of zSql */ );
注意:這裏使用的是 v2 - version 2,這是 sqlite 推薦使用的版本,version 1 僅僅是爲了向後兼容而保留着。
然後使用如下接口來評估的查詢結果:
SQLITE_API int sqlite3_step(sqlite3_stmt*);
如果該接口返回 SQLITE_ROW,表面查詢到了一行記錄,我們就可以以下接口從查詢描述中獲取我們想要的值:
SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol); SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol); SQLITE_API int sqlite3_column_int(sqlite3_stmt*, int iCol); SQLITE_API sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol); SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt*, int iCol); SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol); SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol);
最後,需要通過如下接口釋放先前創建的查詢描述。通常,爲了提高查詢效率,可以把常用的查詢描述緩存起來。
SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt);
下面就來看一個具體的使用示例:
- (NSArray *)queryAllCustomers { NSMutableArray * array = [[NSMutableArray alloc] init]; const char * sqlCmd = "select name, address, age from customer"; sqlite3_stmt * statement; int state = sqlite3_prepare_v2(database, sqlCmd, -1, &statement, nil); if (state == SQLITE_OK) { DLOG(@" >> Succeed to prepare statement. %@", [NSString stringWithCString:sqlCmd encoding:NSUTF8StringEncoding]); } NSInteger index = 0; while (sqlite3_step(statement) == SQLITE_ROW) { // get raw data from statement // char * cstrName = (char *)sqlite3_column_text(statement, 0); char * cstrAddress = (char *)sqlite3_column_text(statement, 1); int age = sqlite3_column_int(statement, 2); NSString * name = [NSString stringWithCString:cstrName encoding:NSUTF8StringEncoding]; NSString * address = [NSString stringWithCString:cstrAddress encoding:NSUTF8StringEncoding]; KSCustomer * customer = [[KSCustomer alloc] initWith:name address:address age:age]; [array addObject:customer]; DLOG(@" >> Record %d : %@ %@ %d", index++, name, address, age); } sqlite3_finalize(statement); DLOG(@" >> Query %d records.", [array count]); return array; }
三,MAC 下查看 sqlite db 文件的工具
MAC 下有一款不錯的開源可視化 sqlite db 瀏覽器:SQLite Database Browser,你可以從以下鏈接獲取:
http://sourceforge.net/projects/sqlitebrowser/
該軟件運行界面如下:
四,封裝 sqlite 的第三方庫 FMDB
在 iOS 中直接使用 sqlite 原生 C 接口還是不那麼方便,因此催生了第三方的 iOS 版封裝庫,其中使用比較廣泛又輕量級的就是 FMDB(https://github.com/ccgus/fmdb),目前該庫只有六個文件,不超過2000行代碼。
使用也是非常簡單,在工程中包含這六個文件:
然後包含頭文件:
#import "FMDatabase.h" #import "FMResultSet.h" #import "FMDatabaseAdditions.h"
就可以使用該庫了:
// Create database // NSString * path = [UIHUtilities configPathFor:kDatabaseFile]; FMDatabase db = [[FMDatabase databaseWithPath:path] retain]; if (![db open]) { DLog(@" >> Error: Failed to open database at %@", path); } #if DEBUG db.traceExecution = TRUE; #endif // Create tables // [db executeUpdate:@"CREATE TABLE Image (studyUid text, patientId text, seriesUid text, SOPUid text, contentDate text, modality text, patientPosition text, filepath text, thumbnailPath text)"]; // insert // BOOL retValue = [db executeUpdate:@"INSERT INTO Image (studyUid, patientId, seriesUid, SOPUid, contentDate, patientPosition, modality, filepath, thumbnailPath) VALUES (?,?,?,?,?,?,?,?,?)", image.studyUid, image.patientId, image.seriesUid, image.SOPUid, image.contentDate, image.patientPosition, image.modality, image.filepath, image.thumbnailPath]; if (!retValue) DLog(@" >> Error: Database failed to insert image %@", image); // query // FMResultSet *rs = [db executeQuery:@"SELECT * FROM Image WHERE SOPUid = ?", SOPUid]; if ([rs next]) { .... } // query count // NSInteger count = 0; FMResultSet *rs = [db executeQuery:@"SELECT COUNT(*) FROM Image WHERE seriesUid = ?", seriesUid]; if ([rs next]) { count = [rs intForColumnIndex:0]; } // delete // retValue = [db executeUpdate:@"DELETE FROM Image WHERE seriesUid = ?", seriesUid]; if (!retValue) DLog(@" >> Error: Database failed to delete image by seriesUid %@", seriesUid); // release database // [db release];