iOS開(kāi)發(fā)中的sqlite數(shù)據(jù)庫(kù)
前面寫(xiě)了一篇博文講如何在C#中使用ADO訪問(wèn)各種數(shù)據(jù)庫(kù),在移動(dòng)開(kāi)發(fā)和嵌入式領(lǐng)域也有一個(gè)輕量級(jí)的開(kāi)源關(guān)系型數(shù)據(jù)庫(kù)-sqlite。它的特點(diǎn)是零配置(無(wú)需服務(wù)器),單磁盤(pán)文件存儲(chǔ)數(shù)據(jù)(就像 fopen一樣),平臺(tái)無(wú)關(guān)性,使用起來(lái)簡(jiǎn)單又高效。這些特點(diǎn)讓其非常適合移動(dòng)開(kāi)發(fā)和嵌入式領(lǐng)域。當(dāng)然,sqlite也因其力求簡(jiǎn)單高效,也就限制了它對(duì)并發(fā),海量數(shù)據(jù)的處理。下面,我就再接再厲,來(lái)講講如何在iOS開(kāi)發(fā)中使用sqlite數(shù)據(jù)庫(kù)和第三方封裝庫(kù)FMDB,以及介紹一個(gè)MAC下開(kāi)源的可視化sqlite瀏覽器。
本文源碼:https://github.com/kesalin/iOSSnippet/tree/master/SQLiteDemo
一,在 iOS 中的使用
在 iOS 中 sqlite3 庫(kù)是一套純 C 的接口,因此很方便地就可以在 obj-c 源碼中無(wú)痕使用它,而且其使用方式與用 ADO 方式操作數(shù)據(jù)庫(kù)大同小異-少了創(chuàng)建數(shù)據(jù)庫(kù)鏈接一環(huán)而已(因?yàn)?sqlite 沒(méi)有服務(wù)器的概念也就無(wú)需鏈接了)。
首先,需要引入 libsqlite3.0.dylib:
然后包含頭文件:
#import "/usr/include/sqlite3.h"
打開(kāi)或創(chuàng)建數(shù)據(jù)庫(kù)
- 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);
- }
關(guān)閉數(shù)據(jù)庫(kù)
- SQLITE_API int sqlite3_close(sqlite3 *);
上面這個(gè)接口將關(guān)閉數(shù)據(jù)庫(kù),如果當(dāng)前還有事務(wù)沒(méi)有提交,會(huì)先執(zhí)行 rollback 操作,然后再關(guān)閉數(shù)據(jù)庫(kù)。
執(zhí)行 sql 語(yǔ)句
- 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 */
- );
這個(gè)接口是最常用到的,幾乎除了查詢之外的 sql 命令都可以用它來(lái)操作,比如創(chuàng)建表,插入/更新/刪除記錄,創(chuàng)建/提交/回滾事務(wù)等。注意:如果 errmsg 不為 null,那么當(dāng)錯(cuò)誤發(fā)生時(shí), sqlite 就會(huì)為錯(cuò)誤消息分配內(nèi)存,返回給調(diào)用者,調(diào)用者有責(zé)任調(diào)用 sqlite3_free 來(lái)釋放這部分內(nèi)存。為了方便使用,我封裝了一個(gè)簡(jiǎn)單的 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);
- }
下面是創(chuàng)建表以及事務(wù)操作的使用示例:
- - (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;"];
- }
很簡(jiǎn)單,不是么?至于插入,更新,刪除示例,請(qǐng)參考如下 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];
查詢操作
查詢操作稍微負(fù)責(zé)一點(diǎn),需要?jiǎng)?chuàng)建查詢描述(sqlite3_stmt),然后調(diào)用如下接口編譯成字節(jié)程序:
- 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 僅僅是為了向后兼容而保留著。
然后使用如下接口來(lái)評(píng)估的查詢結(jié)果:
- 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);
最后,需要通過(guò)如下接口釋放先前創(chuàng)建的查詢描述。通常,為了提高查詢效率,可以把常用的查詢描述緩存起來(lái)。
- SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt);
下面就來(lái)看一個(gè)具體的使用示例:
- - (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 下有一款不錯(cuò)的開(kāi)源可視化 sqlite db 瀏覽器:SQLite Database Browser,你可以從以下鏈接獲取:
http://sourceforge.net/projects/sqlitebrowser/
該軟件運(yùn)行界面如下:
三,封裝 sqlite 的第三方庫(kù) FMDB
在 iOS 中直接使用 sqlite 原生 C 接口還是不那么方便,因此催生了第三方的 iOS 版封裝庫(kù),其中使用比較廣泛又輕量級(jí)的就是 FMDB(https://github.com/ccgus/fmdb),目前該庫(kù)只有六個(gè)文件,不超過(guò)2000行代碼。
使用也是非常簡(jiǎn)單,在工程中包含這六個(gè)文件:
然后包含頭文件:
- #import "FMDatabase.h"
- #import "FMResultSet.h"
- #import "FMDatabaseAdditions.h"
就可以使用該庫(kù)了:
- // 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];
怎么樣,經(jīng)過(guò)本文的介紹您是否對(duì)iOS開(kāi)發(fā)中的sqlite數(shù)據(jù)庫(kù)有了一定的理解和掌握?