【reference】http://blog.csdn.net/mad1989/article/details/9322307
原生数据库:SQlite3
一、必备条件
在ios项目中使用sqlite需要添加 libsqlite3.dylib 库
二、简单介绍常用方法
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类型的数据
三、创建(或打开)数据库
3-1首先自定义一个方法,返回我们当前应用程序沙盒目录(也就是说希望数据库保存在哪里)
- -(NSString *) dataFilePath{
- NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
- NSString *document = [path objectAtIndex:0];
- return [document stringByAppendingPathComponent:TABLENAME];//'persion.sqlite'
- }
SQLITE_OK是sqlite3的一个常量,代表操作执行成功
- //SQLite3
- sqlite3 *database;
- if (sqlite3_open([[self dataFilePath] UTF8String], &database)!=SQLITE_OK) {
- sqlite3_close(database);
- NSAssert(0, @"open database faid!");
- NSLog(@"数据库创建失败!");
- }
上图便是我们创建的数据库在app中的指定位置。
四、创建一张表
- NSString *ceateSQL = @"CREATE TABLE IF NOT EXISTS PERSIONINFO(ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT, AGE INTEGER, SEX TEXT, WEIGHT INTEGER, ADDRESS TEXT)";
- char *ERROR;
- if (sqlite3_exec(database, [ceateSQL UTF8String], NULL, NULL, &ERROR)!=SQLITE_OK){
- sqlite3_close(database);
- NSAssert(0, @"ceate table faild!");
- NSLog(@"表创建失败");
- }
五、查询表数据
- NSString *quary = @"SELECT * FROM PERSIONINFO";//SELECT ROW,FIELD_DATA FROM FIELDS ORDER BY ROW
- sqlite3_stmt *stmt;
- if (sqlite3_prepare_v2(database, [quary 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];
- self.nameTextField.text = nameString;
- [nameString release];
- int age = sqlite3_column_int(stmt, 2);
- self.ageTextField.text = [NSString stringWithFormat:@"%d",age];
- char *sex = (char *)sqlite3_column_text(stmt, 3);
- NSString *sexString = [[NSString alloc] initWithUTF8String:sex];
- self.sexTextField.text = sexString;
- [sexString release];
- int weight = sqlite3_column_int(stmt, 4);
- self.weightTextField.text = [NSString stringWithFormat:@"%d",weight];
- char *address = (char *)sqlite3_column_text(stmt, 5);
- NSString *addressString = [[NSString alloc] initWithUTF8String:address];
- self.addressTextField.text = addressString;
- [addressString release];
- }
- sqlite3_finalize(stmt);
- }
- //用完了一定记得关闭,释放内存
- sqlite3_close(database);
sqlite_3_column_text可以读取字符串类型的数据,参数二为column号,sqlite_3column_int读取int类型数据,其它的就不举例了,大家可以尝试。
六、保存,插入数据
- char *update = "INSERT OR REPLACE INTO PERSIONINFO(NAME,AGE,SEX,WEIGHT,ADDRESS)""VALUES(?,?,?,?,?);";
- //上边的update也可以这样写:
- //NSString *insert = [NSString stringWithFormat:@"INSERT OR REPLACE INTO PERSIONINFO('%@','%@','%@','%@','%@')VALUES(?,?,?,?,?)",NAME,AGE,SEX,WEIGHT,ADDRESS];
- char *errorMsg = NULL;
- sqlite3_stmt *stmt;
- if (sqlite3_prepare_v2(database, update, -1, &stmt, nil) == SQLITE_OK) {
- //【插入数据】在这里我们使用绑定数据的方法,参数一:sqlite3_stmt,参数二:插入列号,参数三:插入的数据,参数四:数据长度(-1代表全部),参数五:是否需要回调
- sqlite3_bind_text(stmt, 1, [self.nameTextField.text UTF8String], -1, NULL);
- sqlite3_bind_int(stmt, 2, [self.ageTextField.text intValue]);
- sqlite3_bind_text(stmt, 3, [self.sexTextField.text UTF8String], -1, NULL);
- sqlite3_bind_int(stmt, 4, [self.weightTextField.text integerValue]);
- sqlite3_bind_text(stmt, 5, [self.addressTextField.text UTF8String], -1, NULL);
- }
- if (sqlite3_step(stmt) != SQLITE_DONE)
- NSLog(@"数据更新失败");
- NSAssert(0, @"error updating :%s",errorMsg);
- sqlite3_finalize(stmt);
- sqlite3_close(database);
- NSString *insert = [NSString stringWithFormat:@"INSERT OR REPLACE INTO PERSIONINFO('%@','%@','%@','%@','%@')VALUES('%@','%d','%@','%d','%@')",NAME,AGE,SEX,WEIGHT,ADDRESS,@"小杨",23,@"man",65,@"中国北京,haidian,shangdi,xinxiRoad,100014"];
- //执行语句
- if (sqlite3_exec(database, [insert UTF8String], NULL, NULL, &errorMsg) != SQLITE_OK) {
- sqlite3_close(database);
- }
七:运行效果:
demo中我在每回打开APP时,在viewdidload里,读取数据库,如果有数据,赋值到相应的属性,当用户按下home键后,及时保存当前数据。也就是说,模拟一个游戏,当我们来电话时或是按下home键做别的事情时,一定要为用户保存当前进度和数据,不然下次打开游戏,又归零了?
demo地址:
http://download.csdn.net/detail/mad1989/5752207
【reference】http://blog.devtang.com/2012/04/22/use-fmdb/
SQLite () 是一个轻量级的关系数据库。iOS SDK 很早就支持了 SQLite,在使用时,只需要加入 libsqlite3.dylib 依赖以及引入 sqlite3.h 头文件即可。但是,原生的 SQLite API 在使用上相当不友好,在使用时,非常不便。于是,开源社区中就出现了一系列将 SQLite API 进行封装的库,而 FMDB () 则是开源社区中的优秀者。
FMDB 在使用上相当方便。以下是一个简单的例子:
NSString* docsdir = [NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; NSString* dbpath = [docsdir stringByAppendingPathComponent:@"user.sqlite"]; FMDatabase* db = [FMDatabase databaseWithPath:dbpath]; [db open]; FMResultSet *rs = [db executeQuery:@"select * from people"]; while ([rs next]) { NSLog(@"%@ %@", [rs stringForColumn:@"firstname"], [rs stringForColumn:@"lastname"]); } [db close]; |
可以看到,使用 FMDB 后的数据库代码清晰明了,比原生的 API 优雅多了。另外,FMDB 同时兼容 ARC 和非 ARC 工程,会自动根据工程配置来调整相关的内存管理代码。
使用说明
该使用说明主要翻译自 fmdb 的 github 项目说明文档:
引入相关文件
首先将 FMDB 从 github 上 clone 下来,然后将以下文件 copy 到你的工程中:
FMDatabase.hFMDatabase.mFMDatabaseAdditions.hFMDatabaseAdditions.mFMDatabasePool.hFMDatabasePool.mFMDatabaseQueue.hFMDatabaseQueue.mFMResultSet.hFMResultSet.m |
建立数据库
建立数据库只需要如下一行即可 , 当该文件不存在时,fmdb 会自己创建一个。如果你传入的参数是空串:@”” ,则 fmdb 会在临时文件目录下创建这个数据库,如果你传入的参数是 NULL,则它会建立一个在内存中的数据库。
FMDatabase *db = [FMDatabase databaseWithPath:@"/tmp/tmp.db"]; |
打开数据库
使用如下语句,如果打开失败,可能是权限不足或者资源不足。通常打开完操作操作后,需要调用 close 方法来关闭数据库。
if (![db open]) { // error return; } // some operation // ... [db close]; |
执行更新操作
除了 Select 操作之外,其它的都是更新操作。更新操作使用如下方法,如果有错误,可以用 error 参数中获得。
-[FMDatabase executeUpdate:error:withArgumentsInArray:orVAList:] |
执行查询操作
查询操作示例如下。注意:即使操作结果只有一行,也需要先调用 FMResultSet 的 next 方法。
FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"]; while ([s next]) { //retrieve values for each record } FMResultSet *s = [db executeQuery:@"SELECT COUNT(*) FROM myTable"]; if ([s next]) { int totalCount = [s intForColumnIndex:0]; } |
FMDB 提供如下多个方法来获取不同类型的数据:
intForColumn: longForColumn: longLongIntForColumn: boolForColumn: doubleForColumn: stringForColumn: dateForColumn: dataForColumn: dataNoCopyForColumn: UTF8StringForColumnIndex: objectForColumn: |
通常情况下,你并不需要关闭 FMResultSet,因为相关的数据库关闭时,FMResultSet 也会被自动关闭。
数据参数
通常情况下,你可以按照标准的 SQL 语句,用 ? 表示执行语句的参数,如:
INSERT INTO myTable VALUES (?, ?, ?) |
然后,可以我们可以调用 executeUpdate 方法来将 ? 所指代的具体参数传入,通常是用变长参数来传递进去的,如下:
NSString *sql = @"insert into User (name, password) values (?, ?)";[db executeUpdate:sql, user.name, user.password]; |
这里需要注意的是,参数必须是 NSObject 的子类,所以象 int,double,bool 这种基本类型,需要封装成对应的包装类才行,如下所示:
// 错误,42 不能作为参数[db executeUpdate:@"INSERT INTO myTable VALUES (?)", 42];// 正确,将 42 封装成 NSNumber 类[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:42]]; |
线程安全
如果我们的 app 需要多线程操作数据库,那么就需要使用 FMDatabaseQueue 来保证线程安全了。
切记不能在多个线程中共同一个 FMDatabase 对象并且在多个线程中同时使用,这个类本身不是线程安全的,这样使用会造成数据混乱等问题。使用 FMDatabaseQueue 很简单,首先用一个数据库文件地址来初使化 FMDatabaseQueue,然后就可以将一个闭包 (block) 传入 inDatabase 方法中。
在闭包中操作数据库,而不直接参与 FMDatabase 的管理。 // 创建,最好放在一个单例的类中 FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath]; // 使用 [queue inDatabase:^(FMDatabase *db) { [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]]; [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]]; [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]]; FMResultSet *rs = [db executeQuery:@"select * from foo"]; while ([rs next]) { // … } }]; // 如果要支持事务 [queue inTransaction:^(FMDatabase *db, BOOL *rollback) { [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]]; [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]]; [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]]; if (whoopsSomethingWrongHappened) { *rollback = YES; return; } // etc… [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:4]]; }]; |
工具
为了查看 Sqlite 中的数据,一个好的图形化界面的数据库管理程序是必不可少的。mysql 有 phpMyAdmin,那么 sqlite 呢?
我主要使用的是 Firefox 的一个名为 SQLite Manager 的插件,安装此插件后,可以直接打开后缀名为 sqlite 的数据库文件。SQLite Manager 提供一个图形化的界面来执行数据查询或更改操作。如下图所示:
总结
FMDB 将 SQLite API 进行了很友好的封装,使用上非常方便,对于那些使用纯 Sqlite API 来进行数据库操作的 app,可以考虑将其迁移到基于 FMDB 上,这对于以后数据库相关功能的开发维护,可以提高不少效率。
我在学习 fmdb 的时候做了一个小工程用于练习,我把它放到 github 上了。感兴趣的可以自行下载: