IOS开发源码阅读篇--FMDB源码分析2(FMDatabase+FMDatabaseAdditions)
来源:互联网 发布:sql select 多表查询 编辑:程序博客网 时间:2024/06/05 20:41
一、前言
如上一章所讲,FMDB源码主要有以下几个文件组成:
FMResultSet : 表示FMDatabase执行查询之后的结果集。
FMDatabase : 表示一个单独的SQLite数据库操作实例,通过它可以对数据库进行增删改查等等操作。
FMDatabaseAdditions : 扩展FMDatabase类,新增对查询结果只返回单个值的方法进行简化,对表、列是否存在,版本号,校验SQL等等功能。
FMDatabaseQueue : 使用串行队列 ,对多线程的操作进行了支持。
FMDatabasePool : 使用任务池的形式,对多线程的操作提供支持。(不过官方对这种方式并不推荐使用,优先选择FMDatabaseQueue的方式:ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD)
这一篇我们就来讲讲FMDatabase和FMDatabaseAdditions的实现思路。
二、FMDatabase源码分析
2.1:打开数据库连接
-(BOOL)open;其实是对sqlite3_open()函数的封装。
- (BOOL)open { if (_db) { return YES; } int err = sqlite3_open([self sqlitePath], (sqlite3**)&_db ); if(err != SQLITE_OK) { NSLog(@"error opening!: %d", err); return NO; } //当执行这段代码的时候,数据库正在被其他线程访问,那我们就需要给他设置一个重试时间,默认为2秒。 if (_maxBusyRetryTimeInterval > 0.0) { // set the handler [self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval]; } return YES;}- (void)setMaxBusyRetryTimeInterval:(NSTimeInterval)timeout { _maxBusyRetryTimeInterval = timeout; if (!_db) { return; } if (timeout > 0) { sqlite3_busy_handler(_db, &FMDBDatabaseBusyHandler, (__bridge void *)(self)); } else { // turn it off otherwise sqlite3_busy_handler(_db, nil, nil); }}
setMaxBusyRetryTimeInterval设置重试时间,其实调用的是
int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*);
该函数的第一个参数为:需要告知哪一个数据库需要设置busy handler。
第二个参数是需要回调的busy handler,当你调用该回调函数的时候,需要传递给它一个void*的参数拷贝,也就是sqlite3_busy_handler的第三个参数。
另一个需要传给回调函数的int参数表示这次锁事件,该回调函数被调用的次数。
如果回调函数返回0时,将不再尝试再次访问数据库而返回SQLITE_BUSY或者SQLITE_IOERR_BLOCKED.如果回调函数返回非0,将会不断尝试操作数据困。
程序运行过程中,如果有其他进程或者线程在读写数据库,那么sqlite3_busy_handler会不断调用该回调函数,直到其他线程或者进程释放锁。获得锁之后,不会再调用该回调函数,从而继续向下执行下去,进行数据库操作。该函数是在获取不到锁的时候,以执行回调函数的次数来进行延时,等待其他进程或者线程操作数据库结束,从而获得锁进行操作数据库。
2.2:查询数据库
executeQuery系列函数从根本上看其实调用的都是
-(FMResultSet )executeQuery:(NSString )sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args。
参数sql : 需要查询的sql语句。
参数arrayArgs : 数组类型的参数。应于于
FMResultSet *resultSet = [_db executeQuery:@"SELECT * FROM t_student WHERE age > ?" withArgumentsInArray:@[@25]];
参数dictionaryArgs:字典类型的参数。应用于
FMResultSet *resultSet = [_db executeQuery:@"SELECT * FROM t_student WHERE age > :age" withParameterDictionary:@{@"age":@25}];
参数args:可变参数类型。应用于
FMResultSet *resultSet = [_db executeQuery:@"SELECT * FROM t_student WHERE age > ?",@(20)];
下面来看一下该函数的大致实现过程,主要就是对sqlite3_prepare_v2的封装。
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args { if (![self databaseExists]) {//判断数据库是否存在 return 0x00; } if (_isExecutingStatement) {//判断数据库是否已经在使用当中 [self warnInUse]; return 0x00; } _isExecutingStatement = YES; int rc = 0x00; sqlite3_stmt *pStmt = 0x00; FMStatement *statement = 0x00; FMResultSet *rs = 0x00; if (_traceExecution && sql) {//打印sql语句 NSLog(@"%@ executeQuery: %@", self, sql); } if (_shouldCacheStatements) {//获取缓存数据 statement = [self cachedStatementForQuery:sql]; pStmt = statement ? [statement statement] : 0x00; [statement reset]; } if (!pStmt) {//没有缓存数据,直接查询数据库 rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);//对sql语句进行预处理,生成预处理过的“sql语句”pStmt。 if (SQLITE_OK != rc) {//出错处理 if (_logsErrors) { NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]); NSLog(@"DB Query: %@", sql); NSLog(@"DB Path: %@", _databasePath); } if (_crashOnErrors) { NSAssert(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]); abort(); } sqlite3_finalize(pStmt); _isExecutingStatement = NO; return nil; } } id obj; int idx = 0; int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!)//queryCount获取参数个数,“?”或者“:age”都算。 // If dictionaryArgs is passed in, that means we are using sqlite's named parameter support if (dictionaryArgs) {//对dictionaryArgs参数的处理,类似于":age"参数形式 for (NSString *dictionaryKey in [dictionaryArgs allKeys]) { // Prefix the key with a colon. NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey]; if (_traceExecution) { NSLog(@"%@ = %@", parameterName, [dictionaryArgs objectForKey:dictionaryKey]); } // Get the index for the parameter name. int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]); FMDBRelease(parameterName); if (namedIdx > 0) { // Standard binding from here. // * 将通配符?:age按照索引 赋值为obj。 //* SELECT * FROM t_student WHERE age > :age --> SELECT * FROM t_student WHERE age > obj [self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt]; // increment the binding count, so our check below works out idx++; } else { NSLog(@"Could not find index for %@", dictionaryKey); } } } else {//对于arrayArgs参数和不定参数的处理,类似于"?"参数形式 while (idx < queryCount) { if (arrayArgs && idx < (int)[arrayArgs count]) {//数组形式 obj = [arrayArgs objectAtIndex:(NSUInteger)idx]; } else if (args) {//不定参数形式 obj = va_arg(args, id); } else { //We ran out of arguments break; } if (_traceExecution) { if ([obj isKindOfClass:[NSData class]]) { NSLog(@"data: %ld bytes", (unsigned long)[(NSData*)obj length]); } else { NSLog(@"obj: %@", obj); } } idx++; // * 将通配符?:age按照索引 赋值为obj。 //* SELECT * FROM t_student WHERE age > ? --> SELECT * FROM t_student WHERE age > obj [self bindObject:obj toColumn:idx inStatement:pStmt];//绑定参数值 } } if (idx != queryCount) {//如果绑定的参数数目不对,则进行出错处理 NSLog(@"Error: the bind count is not correct for the # of variables (executeQuery)"); sqlite3_finalize(pStmt); _isExecutingStatement = NO; return nil; } FMDBRetain(statement); // to balance the release below if (!statement) {//生成FMStatement对象 statement = [[FMStatement alloc] init]; [statement setStatement:pStmt]; if (_shouldCacheStatements && sql) {//缓存处理,key为sql语句,值为statement [self setCachedStatement:statement forQuery:sql]; } } // the statement gets closed in rs's dealloc or [rs close]; rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];//生成FMResultSet结果集对象 [rs setQuery:sql]; NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs]; [_openResultSets addObject:openResultSet]; [statement setUseCount:[statement useCount] + 1]; FMDBRelease(statement); _isExecutingStatement = NO; return rs;}
上面有一个关键的函数就是- (void)bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt进行参数绑定,把通配符形式的参数改成obj实际参数。
将通配符?:age按照索引 赋值为obj。比如:SELECT * FROM t_student WHERE age > :age –> SELECT * FROM t_student WHERE age > obj 或者 SELECT * FROM t_student WHERE age > ? –> SELECT * FROM t_student WHERE age > obj
2.3 更新数据库操作
这里的更新,并不只是单单的更新数据。而是对数据库有更改的操作,增删改都算。FMDB调用的都是executeUpdate系列函数。这个函数基本上跟executeQuery系列函数的实现基本差不多。只是它生成statement对象后,直接调用rc = sqlite3_step(pStmt);更新执行,而没有像executeQuery延迟到FMResultSet中的next函数中执行。
2.4 WithFormat形式的数据查询和更新
比如:FMResultSet resultSet = [_db executeQueryWithFormat:@”SELECT FROM t_student WHERE age > %d”,20];替换原来?通配符。
- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ... { va_list args; va_start(args, format); NSMutableString *sql = [NSMutableString stringWithCapacity:[format length]]; NSMutableArray *arguments = [NSMutableArray array]; [self extractSQL:format argumentsList:args intoString:sql arguments:arguments]; va_end(args); return [self executeQuery:sql withArgumentsInArray:arguments];}
从代码中可以看出,这里其实是使用
/** * 将format的sql语句 改为 可用的sql语句 * SELECT * FROM t_student WHERE age > %d --> SELECT * FROM t_student WHERE age > ? * * @param sql SELECT * FROM t_student WHERE age > %d * @param args 可变参数,c语言的格式化参数 * @param cleanedSQL SELECT * FROM t_student WHERE age > ? * @param arguments oc数组 */- (void)extractSQL:(NSString *)sql argumentsList:(va_list)args intoString:(NSMutableString *)cleanedSQL arguments:(NSMutableArray *)arguments;
2.5 一次性执行多条sql语句。
使用executeStatements函数可以一次性执行多条sql语句,实例如下:
/** * 将多个SQL执行语句写入一个字符串中,并执行 */- (void)executeStatementsTest{ NSString *sql = @"CREATE TABLE IF NOT EXISTS t_student_tmp (id integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL, age integer NOT NULL);" "INSERT INTO t_student_tmp (name, age) VALUES ('yixiang', 10);" "INSERT INTO t_student_tmp (name, age) VALUES ('yixiangXX', 20);"; BOOL success = [_db executeStatements:sql]; if (success) { NSLog(@"执行成功"); }else{ NSLog(@"执行失败"); } sql = @"SELECT * FROM t_student;" "SELECT * FROM t_student_tmp;"; success = [_db executeStatements:sql withResultBlock:^int(NSDictionary *resultsDictionary) { NSLog(@"%@",resultsDictionary);//查询结果都在resultsDictionary return 0; }]; if (success) { NSLog(@"查询成功"); }else{ NSLog(@"查询失败"); }}
它又是如何实现的呢?他其实就是对sqlite3_exec函数的封装。
- (BOOL)executeStatements:(NSString *)sql withResultBlock:(FMDBExecuteStatementsCallbackBlock)block { int rc; char *errmsg = nil; rc = sqlite3_exec([self sqliteHandle], [sql UTF8String], block ? FMDBExecuteBulkSQLCallback : nil, (__bridge void *)(block), &errmsg); if (errmsg && [self logsErrors]) { NSLog(@"Error inserting batch: %s", errmsg); sqlite3_free(errmsg); } return (rc == SQLITE_OK);}
2.6:FMDB的加解密
FMDB中使用- [FMDatabase setKey:]和- [FMDatabase setKeyWithData:]输入数据库密码以求验证用户身份,使用- [FMDatabase rekey:]和- [FMDatabase rekeyWithData:]来给数据库设置密码或者清除密码。这两类函数分别对sqlite3_key和sqlite3_rekey函数进行了封装。
三、FMDatabaseAdditions源码分析
扩展FMDatabase类,新增对查询结果只返回单个值的方法进行简化,对表、列是否存在,版本号,校验SQL等等功能。
3.1:XXXForQuery系列函数
对查询结果只有一个值得情况进行优化,有多个值也只取第一个值。用法实例:
/** * 使用FMDatabaseAdditions中的intForQuery函数查找数据,如果返回结果有多个数据只取第一条数据 */- (void)queryForIntForQuery{ int idx = [_db intForQuery:@"SELECT id FROM t_student WHERE age = ?",@(26)]; NSLog(@"%zi",idx);}
3.2: 数据库的一些概要信息
-(BOOL)tableExists:(NSString*)tableName;数据库表是否存在。
-(BOOL)columnExists:(NSString*)columnName inTableWithName:(NSString*)tableName;在tableName表中columnName是否存在。
-(FMResultSet*)getSchema;数据库的一些概要信息。实例如下:
/** *schema概要信息 */- (void)querySchema{ //查询数据库schema(所有表的一些信息) //*对于表来说, tbl_name 则仍然是表名。 但sqlite_master 中不仅是表记录,还有其它的对象,比如索引,对于索引来说 name 是索引的名字,而tbl_name 是索引所属的表名。 FMResultSet *resultSet = [_db getSchema]; while ([resultSet next]) { NSString *type = [resultSet stringForColumn:@"type"]; NSString *name = [resultSet stringForColumn:@"name"]; NSString *tbl_name = [resultSet stringForColumn:@"tbl_name"]; int rootpage = [resultSet intForColumn:@"rootpage"]; NSString *sql = [resultSet stringForColumn:@"sql"]; NSLog(@"\n %@ \n %@ \n %@ \n %zi \n %@",type,name,tbl_name,rootpage,sql); } /** * 每一张表具体的概要信息(也就是每一列的信息) * get table schema: result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER] */ resultSet = [_db getTableSchema:@"t_student"]; while ([resultSet next]) { int cid = [resultSet intForColumn:@"cid"]; NSString *name = [resultSet stringForColumn:@"name"]; NSString *type = [resultSet stringForColumn:@"type"]; int notnull = [resultSet intForColumn:@"notnull"]; int pk = [resultSet intForColumn:@"pk"]; NSLog(@"\n %zi \n %@ \n %@ \n %zi \n %zi",cid,name,type,notnull,pk); }}
3.3:校验sql语句是否合法
-(BOOL)validateSQL:(NSString*)sql error:(NSError**)error;
四、联系方式
微博:新浪微博
博客:http://blog.csdn.net/yixiangboy
github:https://github.com/yixiangboy
- IOS开发源码阅读篇--FMDB源码分析2(FMDatabase+FMDatabaseAdditions)
- IOS开发源码阅读篇--FMDB源码分析1(FMResultSet)
- IOS开发源码阅读篇--FMDB源码分析3(FMDatabaseQueue+FMDatabasePool)
- FMDB源码阅读
- FMDB源码分析
- iOS开发 - FMDatabase 使用
- 源码阅读分析
- HASHMAP源码阅读分析
- 源码阅读分析
- zabbix源码阅读分析
- [iOS]MJExtension 源码阅读
- RDVTabBarController【iOS源码阅读】
- redis源码阅读(2)---- adlist分析
- spark源码分析(2)-源码阅读环境准备
- fmdb(FMDatabase) 数据库总结
- Tomcat源码阅读之StandarWrapper源码分析
- (源码阅读)源码分析之AsyncTask
- 阅读《STL源码分析》有感
- 深入浅出UML类图
- VB.net2008实例 编写文字加解密程序
- 关于manacher算法求字符串中最长字符串和其长度
- 学习感悟
- linux下的ss+privoxy代理配置
- IOS开发源码阅读篇--FMDB源码分析2(FMDatabase+FMDatabaseAdditions)
- ListView中的每个条目如何跳转到另外的Activity?
- git 错误 fatal: Not a valid object name: 'master'.
- IOS开发源码阅读篇--FMDB源码分析3(FMDatabaseQueue+FMDatabasePool)
- 层次遍历二叉树
- android源码分析之JNI调用与回调
- 【UNET自学日志】Part14 射击那些僵尸
- ubuntu opencv 安装编译问题
- spark rdd 去括号