对于FMDB的介绍我想就不用了吧,毕竟大家能用到也就说明大家有一定的了解了。
FMDB github 下载地址: https://github.com/ccgus/fmdb
本篇博客主要介绍一下当我们项目中用到数据库操作时候,且进行大量的数据插入修改操作时候,对执行速度和内存的优化。
1.使用iOS系统原生sqlite进行操作
/*
如果我们需要往表 A里面插入大量数据,则我们会把其对应的字段与值封装到字典里面
如其中一个字典的键值对为:
{
"name" = "小白"
"age" = 18
"job" = "死神"
}
*/
-(void)save:(NSString *)tableName dataArray:(NSMutableArray *)dataArray
{
NSMutableString *strSql = [NSMutableStringstring];
//我们在sql语句前拼接上该语句,即为其加上了事务机制,大大提高了sql语句的执行效率
[strSql appendString:@"BEGIN TRANSACTION;"];
for (int i=0; i < [dataArraycount]; i++)
{
NSDictionary *dic = dataArray[i];
NSMutableString *strName = [NSMutableStringstring];
NSMutableString *strValue = [NSMutableStringstring];
if (dic ==nil || [diccount] ==0) {
continue;
}
for (NSString *keyin [dicallKeys])
{
//循环字典中键值对,对字段及对应的值进行拼接
[strNameappendFormat:@"%@,",key];
if ([[dicobjectForKey:key]isKindOfClass:[NSStringclass]]) {
[strValueappendFormat:@"'%@',",[dicobjectForKey:key]];
}else {
[strValueappendFormat:@"%@,",[dicobjectForKey:key]];
}
}
NSString *strName2 = [strNamesubstringToIndex:(strName.length-1)];
NSString *strValue2 = [strValuesubstringToIndex:(strValue.length-1)];
[strSqlappendFormat:@"INSERT OR REPLACE INTO %@ (%@) VALUES (%@);", tableName, strName2, strValue2];
}
[strSql appendString:@"COMMIT TRANSACTION;"];
[self ExecuteSql:strSql];
}
- (BOOL)ExecuteSql:(NSString *)sql
{
NSArray *paths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *dbPath = [[pathsobjectAtIndex:0]stringByAppendingPathComponent:@"test.db"];
sqlite3 *sqlitedatabase;
char *errorMsg;
if (sqlite3_open([dbPathUTF8String], &sqlitedatabase) !=SQLITE_OK)
{
sqlite3_close(sqlitedatabase);
returnNO;
}
if (sqlite3_exec (sqlitedatabase, [sql UTF8String],NULL,NULL, &errorMsg) !=SQLITE_OK) {
sqlite3_close(sqlitedatabase);
NSLog(@"errorMsg = %@", [NSStringstringWithUTF8String:errorMsg]);
returnNO;
}
sqlite3_close(sqlitedatabase);
returnYES;
}
//使用系统sqlite执行sql操作。 (实验证明,这个速度是灰常快的,如果不加事务机制,将会很慢。 但该操作并没有使sql语句参数化,其存在一定的漏洞)
举个例子,如果我们的数据中有一个字段如"name" 其对应的值为 "小白'" (注意小白后面的单引号)
这时候我们的SQL语句可能就会是insert into STU (name, age, job) values ('小白'', '18', '死神') 这样就会因为数据的极大随意性导致SQL语句的执行失败。
2.这时候我们便可以使用FMDB的强大功能,
使用该三方库时候,我们需要导入系统框架libsqlite3.dylib框架。
至于简单的插入操作,在此不做太多介绍, 在此主要介绍一次多次 开启事务多次插入数据的方法
(1) 需要导入的头文件:
#import"FMDBOperator.h"
#import "FMDatabaseQueue.h"
初始化 FMDatabaseQueue 对象
_queue = [FMDatabaseQueuedatabaseQueueWithPath:dbPath];
-(void)save:(NSString *)tableName dataArray:(NSMutableArray *)dataArray
{
[_queueinTransaction:^(FMDatabase *db,BOOL *rollback) {
if (![dbopen]) {
return;
}
for (int i=0; i<[arrayLines count]; i++)
{
NSMutableString *strSql = [NSMutableStringstring];
NSDictionary *dic = arrayLines[i];
NSMutableString *strName = [NSMutableStringstring];
NSMutableString *strValue = [NSMutableStringstring];
NSMutableArray *valuesArray = [NSMutableArrayarray];
if (dic ==nil || [diccount] ==0) {
continue;
}
for (NSString *keyin [dicallKeys])
{
[strNameappendFormat:@"%@,",key];
[strValueappendString:@"?,"];
[valuesArrayaddObject:dic[key]];
}
NSString *strName2 = [strNamesubstringToIndex:(strName.length-1)];
NSString *strValue2 = [strValuesubstringToIndex:(strValue.length-1)];
[strSqlappendFormat:@"insert or replace into %@ (%@) values (%@)", tableName, strName2, strValue2];
BOOL result = [dbexecuteUpdate:strSqlwithArgumentsInArray:valuesArray];
if (!result) {
NSLog(@"数据下载失败:%@, valueArray:%@", tableName, valuesArray);
}
}
}];
}
//使用第二种方法则有效地避免了第一种
在数据不稳定的情况下出现的SQL数据执行错误
下面在随便说一下自己再用到FMDB时候遇到的坑
由于之前一直用的老版本的FMDB三方库,在进行数据检索时候 检索出来的空数据(即该字段的value为空)都被FMDB给过滤掉了,代码如下
NSMutableArray *array = [[NSMutableArrayalloc]init];
FMResultSet *resultSet = [databaseexecuteQuery:sql];
while ([resultSetnext])
{
NSDictionary *resultDict = [resultSetresultDictionary];
[arrayaddObject:resultDict];
}
[databaseclose];
举个例子,如果我们数据库中 存在一条数据 (表字段为 id, name, age, job) 其job字段对应的value为空,那么我们执行上面的sql语句检索出来的数据字典格式为
{
"id" = 1,
"name" = "xiaoming",
"age" = 22,
}
其job字段的值FMDB会自动为我们过滤掉
而最新的FMDB中检索出来的数据会是
{
"id" = 1,
"name" = "xiaoming",
"age" = 22,
"job" = "<null>"
}
其会自动把值为空的字段为我们添加一个NSNull的对象进去。 这样的话之前项目里面如果存在 判断
if (dict[@"job"] != nil) 才执行的语句现在就可能会无效,因为它永远不能为nil,而是一个属于NSNull的对象。
对于我这种不想动之前项目的懒人来说,只能考虑稍微动下库里的东西了
(1)打开 FMResultSet.m 文件,找到
- (id)objectForColumnIndex:(int)columnIdx 方法,做如下修改
把红框内的内容加以注释,
(2)在同一文件中找到
- (NSDictionary*)resultDictionary 方法内把for循环替换为
for (columnIdx =0; columnIdx < columnCount; columnIdx++) {
NSString *columnName = [NSStringstringWithUTF8String:sqlite3_column_name([_statementstatement], columnIdx)];
id objectValue = [selfobjectForColumnIndex:columnIdx];
//在该处加判断,保证不添加空值nil
if (objectValue) {
[dictsetObject:objectValueforKey:columnName];
}
}
修改之后,在检索数据时候,为空的字段便不会被添加上NSNull对象了