前面写了一篇博文讲如何在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];
怎么样,经过本文的介绍您是否对iOS开发中的sqlite数据库有了一定的理解和掌握?