iOS之Sqlite3封装

一、代码下载

代码下载地址

二、实例效果展示

效果图一.png
效果图二.png
效果图三.png

三、实例项目简单介绍

这个实例主要是封装sqlite3数据库工具,并用这个工具实现记录、删除、改动、查询学生信息的功能。另外一个附属的功能就是依据学生的姓名首字母分组并加入索引。

对于数据库。我做了两层封装。

第一层是对数据库的基本操作进行抽象。第二层是对学生数据的操作进行抽象。这样的分层的思想不仅能使处于底层的功能得到多次重用,调高效率。并且这样做逻辑清晰,易于定位问题,故意于维护与跟新。

四、实例项目分析

1、首先抽象出数据库基本操作工具类,而数据库的基本操作有:打开、关闭、建表、增、删、改、查……基于这些操作,我封装出例如以下五个方法来提供数据库的操作:

 * 打开数据库 * * @param path 数据库路径 * @param success 打开成功的回调 * @param falure 打开失败的回调 */+ (void)openDBPath:(NSString *)path succesefulBlock:(void (^)(sqlite3 *db))success andFailureBlock:(void (^)(NSString *msg))failure{ sqlite3 *database = NULL; int result = sqlite3_open(path.UTF8String, &database); if (result == SQLITE_OK) { if (success) { success(database); } } else { if (failure) { const char *msg = sqlite3_errmsg(database); failure([NSString stringWithUTF8String:msg]); } if (database) { [self closeDB:database succesefulBlock:nil andFailureBlock:nil]; } }}/** * 关闭数据库 * *  @param database 数据库链接 *  @param succese 成功的回调 *  @param failure 失败的回调 */+ (void)closeDB:(sqlite3 *)database succesefulBlock:(void (^)())succese andFailureBlock:(void (^)(NSString *msg))failure{ int result = sqlite3_close(database); if (result == SQLITE_OK) { if (succese) { succese(); } } else { if (failure) { failure([NSString stringWithUTF8String:sqlite3_errmsg(database)]); } }}/** * 运行SQL语句(不适应查询语句和blob(NSData)二进制数据类型的操作) * *  @param sqStr SQL语句 *  @param database 数据库链接 *  @param succese 成功的回调 *  @param failure 失败的回调 */+ (void)executeSql:(NSString *)sqStr toDatabase:(sqlite3 *)database succesefulBlock:(void (^)())succese andFailureBlock:(void (^)(NSString *msg))failure{ DebugLog(@"%@", sqStr); char *msg = NULL; int result = sqlite3_exec(database, sqStr.UTF8String, NULL, NULL, &msg); if (result == SQLITE_OK) { if (succese) { succese(); } } else { if (failure) { failure([NSString stringWithUTF8String:msg]); } }}/** * 准备须要sqlite3_stmt结果集的SQL语句 * *  @param sqStr SQL语句 *  @param database 数据库连接 *  @param succese 成功的回调 *  @param failure 失败的回调 */+ (void)prepareSql:(NSString *)sqStr fromDatabase:(sqlite3 *)database succesefulBlock:(void (^)(sqlite3_stmt *stmt))succese andFailureBlock:(void (^)(NSString *msg))failure{ DebugLog(@"%@", sqStr); sqlite3_stmt *stmt = NULL; int result = sqlite3_prepare_v2(database, sqStr.UTF8String, -1, &stmt, NULL); if (result == SQLITE_OK) { if (succese) { succese(stmt); } } else { if (failure) { failure(@"SQL语句是非法的。"); } }}

2、接着就是抽象学生数据功能类了。依据须要我抽象出了例如以下五个方法

/** * 获取全部学生 * *  @param callBack 回调 */+ (void)getAllStudents:(void (^)(NSArray *students, NSString *msg))callBack;/** * 加入学生 * *  @param studentModel 学生模型 *  @param succese 加入成功回调 *  @param failure 加入失败回调 */+ (void)addStudent:(StudentModel *)studentModel succesefulBlock:(void (^)(StudentModel *studentModel))succese andFailureBlock:(void (^)(NSString *msg))failure;/** * 更新学生 * *  @param studentModel 学生模型 *  @param succese 更新成功回调 *  @param failure 更新失败回调 */+ (void)updateStudent:(StudentModel *)studentModel succesefulBlock:(void (^)(StudentModel *studentModel))succese andFailureBlock:(void (^)(NSString *msg))failure;/** * 按条件搜索学生 * *  @param condition 条件 *  @param callBack 搜索回调 */+ (void)searchStudents:(NSString *)condition andCallBack:(void (^)(NSArray *students, NSString *msg))callBack;/** * 删除学生 * *  @param studentModel 学生模型 *  @param succese 删除成功回调 *  @param failure 删除失败回调 */+ (void)deleteStudent:(StudentModel *)studentModel succesefulBlock:(void (^)(StudentModel *studentModel))succese andFailureBlock:(void (^)(NSString *msg))failure;

细节分析
- 因为该功能类都是使用类方法,所以不能以实例变量来存储数据,因此声明全局变量static sqlite3 *database来存储数据库的链接。并以static关键字修饰来避免其它源文件对其訪问。
- 类方法initialize是在手动调用累中不论什么方法前调用一次,所以在这种方法中打开数据库并创建学生表是很合适的。

/** * 在手动调用类里的不论什么方法前自己主动调用一次 */+ (void)initialize{ [SqliteDBAccess openDBPath:SqlitePathStr succesefulBlock:^(sqlite3 *db) { DebugLog(@"数据库打开成功。"); [SqliteDBAccess executeSql:[NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS %@ (identifier integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL, studentNumber text NOT NULL UNIQUE, photo blob, age integer NOT NULL, address text, describe text);", StudentTableName] toDatabase:db succesefulBlock:^{ database = db; DebugLog(@"学生表创建成功!"); } andFailureBlock:^(NSString *msg) { DebugLog(@"学生表创建失败。%@", msg); }]; } andFailureBlock:^(NSString *msg) { DebugLog(@"数据库打开失败,%@", msg); }];}
  • 在学生数据功能类的内部能够封装一个不公开的方法来从sqlite3_stmt结果集中获取学生模型对象
/** * 从sqlite3_stmt中获取学生数据 * * @param stmt sqlite3_stmt结果集 * * @return 学生数组 */+ (NSArray *)getStudentsFromStatement:(sqlite3_stmt *)stmt{ NSMutableArray *students = [NSMutableArray arrayWithCapacity:1]; StudentModel *studentModel; while (sqlite3_step(stmt) == SQLITE_ROW) { studentModel = [[StudentModel alloc] init]; studentModel.identifier = sqlite3_column_int(stmt, 0); char *name = (char *)sqlite3_column_text(stmt, 1); char *studentNumber = (char *)sqlite3_column_text(stmt, 2); void *photo = (void *)sqlite3_column_blob(stmt, 3); studentModel.name = name ? [NSString stringWithUTF8String:name] : nil; studentModel.studentNumber = studentNumber ?

[NSString stringWithUTF8String:studentNumber] : nil; studentModel.photo = photo ? [UIImage imageWithData:[NSData dataWithBytes:photo length:sqlite3_column_bytes(stmt, 3)]] : nil; studentModel.age = sqlite3_column_int(stmt, 4); char *address = (char *)sqlite3_column_text(stmt, 5); char *describe = (char *)sqlite3_column_text(stmt, 6); studentModel.address = address ?

[NSString stringWithUTF8String:address] : nil; studentModel.describe = describe ? [NSString stringWithUTF8String:describe] : nil; [students addObject:studentModel]; } return students; }

  • 因为表中存在二进制数据,所以插入跟新二进制数据时使用sqlite3_exec函数运行SQL语句是有问题的,须要使用sqlite3_step函数来运行SQL语句。

/** * 更新学生 * *  @param studentModel 学生模型 *  @param succese 更新成功回调 *  @param failure 更新失败回调 */+ (void)updateStudent:(StudentModel *)studentModel succesefulBlock:(void (^)(StudentModel *studentModel))succese andFailureBlock:(void (^)(NSString *msg))failure{ [SqliteDBAccess prepareSql:[NSString stringWithFormat:@"UPDATE %@ SET name = ?, studentNumber = ?, photo = ?, age = ?, address = ?, describe = ? WHERE identifier = ?;", StudentTableName] fromDatabase:database succesefulBlock:^(sqlite3_stmt *stmt) { NSData *data = UIImagePNGRepresentation(studentModel.photo); sqlite3_bind_text(stmt, 1, studentModel.name.UTF8String, -1, NULL); sqlite3_bind_text(stmt, 2, studentModel.studentNumber.UTF8String, -1, NULL); sqlite3_bind_blob(stmt, 3, [data bytes], (int)[data length], NULL); sqlite3_bind_int(stmt, 4, studentModel.age); sqlite3_bind_text(stmt, 5, studentModel.address.UTF8String, -1, NULL); sqlite3_bind_text(stmt, 6, studentModel.describe.UTF8String, -1, NULL); sqlite3_bind_int(stmt, 7, studentModel.identifier); //运行完毕 if (sqlite3_step(stmt) == SQLITE_DONE) { if (succese) { succese(studentModel); } } else { if (failure) { failure([NSString stringWithFormat:@"更新学生失败。%@", [NSString stringWithUTF8String:sqlite3_errmsg(database)]]); } } //在遍历完结果集后,调用sqlite3_finalize以释放和预编译的语句相关的资源。

sqlite3_finalize(stmt); } andFailureBlock:^(NSString *msg) { if (failure) { failure([NSString stringWithFormat:@"更新学生失败。%@", msg]); } }]; }

五、问题与补充

1.sqlite3事务:是并发控制的基本单位。所谓的事务。它是一个操作序列,这些操作要么都运行。要么都不运行,它是一个不可切割的工作单位。比如,银行转账工作:从一个账号扣款并使还有一个账号增款,这两个操作要么都运行,要么都不运行。所以,应该把它们看成一个事务。事务是数据库维护数据一致性的单位,在每一个事务结束时,都能保持数据一致性。 针对上面的描写叙述能够看出,事务的提出主要是为了解决并发情况下保持数据一致性的问题。

事务的语句:
- 開始事物:BEGIN TRANSACTION
- 提交事物:COMMIT TRANSACTION
- 回滚事务:ROLLBACK TRANSACTION

项目中加入学生数据的时候,并没有设置identifer主键。可是主键是实用的,在插入成功后须要查出来,因此加入学生就涉及两步操作:插入数据,查询数据。并且这两个操作必须要同一时候成功才干算是学生加入成功。所以在这里,我就开启一个事务。先插入学生数据,再查询此学生数据,仅仅要有一个步骤错误发生就回滚事务,全部成功则提交事务:

/** * 加入学生 * * @param studentModel 学生模型 * @param succese 加入成功回调 * @param failure 加入失败回调 */+ (void)addStudent:(StudentModel *)studentModel succesefulBlock:(void (^)(StudentModel *studentModel))succese andFailureBlock:(void (^)(NSString *msg))failure{ //开启事务 [SqliteDBAccess executeSql:@"BEGIN TRANSACTION" toDatabase:database succesefulBlock:^{ DebugLog(@"事务启动成功。"); [SqliteDBAccess prepareSql:[NSString stringWithFormat:@"INSERT INTO %@ (name, studentNumber, photo, age, address, describe) VALUES(?, ?

, ?

, ?, ?, ?);", StudentTableName] fromDatabase:database succesefulBlock:^(sqlite3_stmt *stmt) { NSData *data = UIImagePNGRepresentation(studentModel.photo); sqlite3_bind_text(stmt, 1, studentModel.name.UTF8String, -1, NULL); sqlite3_bind_text(stmt, 2, studentModel.studentNumber.UTF8String, -1, NULL); sqlite3_bind_blob(stmt, 3, [data bytes], (int)[data length], NULL); sqlite3_bind_int(stmt, 4, studentModel.age); sqlite3_bind_text(stmt, 5, studentModel.address.UTF8String, -1, NULL); sqlite3_bind_text(stmt, 6, studentModel.describe.UTF8String, -1, NULL); if (sqlite3_step(stmt) == SQLITE_DONE) { [SqliteDBAccess prepareSql:[NSString stringWithFormat:@"SELECT * FROM %@ WHERE studentNumber = ?

;", StudentTableName] fromDatabase:database succesefulBlock:^(sqlite3_stmt *stmt) { sqlite3_bind_text(stmt, 1, studentModel.studentNumber.UTF8String, -1, NULL); StudentModel *model = [[self getStudentsFromStatement:stmt] firstObject]; if (studentModel) { studentModel.identifier = model.identifier; if (succese) { succese(studentModel); } //提交事务 [SqliteDBAccess executeSql:@"COMMIT TRANSACTION" toDatabase:database succesefulBlock:^{ DebugLog(@"提交事务成功!"); } andFailureBlock:^(NSString *msg) { DebugLog(@"提交事务失败:%@", msg); }]; } else { //回滚事务 [SqliteDBAccess executeSql:@"ROLLBACK TRANSACTION" toDatabase:database succesefulBlock:^{ DebugLog(@"回滚成功。"); } andFailureBlock:^(NSString *msg) { DebugLog(@"回滚失败:%@", msg); }]; } //在遍历完结果集后,调用sqlite3_finalize以释放和预编译的语句相关的资源。 sqlite3_finalize(stmt); } andFailureBlock:^(NSString *msg) { if (failure) { failure(msg); //回滚事务 [SqliteDBAccess executeSql:@"ROLLBACK TRANSACTION" toDatabase:database succesefulBlock:^{ DebugLog(@"回滚成功。"); } andFailureBlock:^(NSString *msg) { DebugLog(@"回滚失败:%@", msg); }]; } }]; } else { if (failure) { failure([NSString stringWithFormat:@"加入学生失败,%@", [NSString stringWithUTF8String:sqlite3_errmsg(database)]]); } } //在遍历完结果集后,调用sqlite3_finalize以释放和预编译的语句相关的资源。 sqlite3_finalize(stmt); } andFailureBlock:^(NSString *msg) { if (failure) { failure([NSString stringWithFormat:@"加入学生失败。%@", msg]); } //回滚事务 [SqliteDBAccess executeSql:@"ROLLBACK TRANSACTION" toDatabase:database succesefulBlock:^{ DebugLog(@"回滚成功。"); } andFailureBlock:^(NSString *msg) { DebugLog(@"回滚失败:%@", msg); }]; }]; } andFailureBlock:^(NSString *msg) { DebugLog(@"事务启动失败:%@", msg); }]; }

2.sqlite3注入漏洞:
举例:登录功能
1.用户输入登录名和密码:userName = ‘zhangsan or 1 = 1 or ” = ”’ password = ‘123456’
select * from t_user where name = ‘zhangsan or 1 = 1 or ” = ”’ and password = ‘123456’;
如此条件永远成立
解决方式:參数化查询
用?替换掉须要查询的条件
select * from t_user where name = ?

and passsword = ?

;
检查查询语句合法后。再绑定參数
sqlite3_bind_text(stmt, 1, “张三”, NULL);

3.sqlite3模糊查询:确定给定的字符串是否与指定的模式匹配。模式能够包括常规字符和通配符字符。

模式匹配过程中,常规字符必须与字符串中指定的字符全然匹配。然而,可 使用字符串的随意片段匹配通配符。与使用 = 和 != 字符串比較运算符相比,使用通配符可使 LIKE 运算符更加灵活。
- %:包括零个或很多其它字符的随意字符串。WHERE title LIKE ‘%computer%’ 将查找处于书名任何位置的包括单词 computer 的全部书名。
- _(下划线):不论什么单个字符。

WHERE au_fname LIKE ‘_ean’ 将查找以 ean 结尾的全部 4 个字母的名字(Dean、Sean 等)。

- [ ]:指定范围 ([a-f]) 或集合 ([abcdef]) 中的不论什么单个字符。WHERE au_lname LIKE ‘[C-P]arsen’ 将查找以arsen 结尾且以介于 C 与 P 之间的不论什么单个字符開始的作者姓氏,比如,Carsen、Larsen、Karsen 等。
- [^]:不属于指定范围 ([a-f]) 或集合 ([abcdef]) 的不论什么单个字符。WHERE au_lname LIKE ‘de[^l]%’ 将查找以 de 開始且其后的字母不为 l 的全部作者的姓氏。

/** * 按条件搜索学生 * * @param condition 条件 * @param callBack 搜索回调 */+ (void)searchStudents:(NSString *)condition andCallBack:(void (^)(NSArray *students, NSString *msg))callBack{ int age = [condition intValue]; NSString *selectStr; if (age != 0 || [condition isEqualToString:@"0"]) { selectStr = [NSString stringWithFormat:@"SELECT * FROM %@ WHERE name LIKE ?

or studentNumber LIKE ? or age LIKE ?

;", StudentTableName]; } else { selectStr = [NSString stringWithFormat:@"SELECT * FROM %@ WHERE name LIKE ?

or studentNumber LIKE ?;", StudentTableName]; } [SqliteDBAccess prepareSql:selectStr fromDatabase:database succesefulBlock:^(sqlite3_stmt *stmt) { sqlite3_bind_text(stmt, 1, [NSString stringWithFormat:@"%%%@%%", condition].UTF8String, -1, NULL); sqlite3_bind_text(stmt, 2, [NSString stringWithFormat:@"%%%@%%", condition].UTF8String, -1, NULL); if (age != 0 || [condition isEqualToString:@"0"]) { sqlite3_bind_int(stmt, 3, age); } if (callBack) { callBack([self getStudentsFromStatement:stmt], @"搜索成功!"); } //在遍历完结果集后。调用sqlite3_finalize以释放和预编译的语句相关的资源。

sqlite3_finalize(stmt); } andFailureBlock:^(NSString *msg) { if (callBack) { callBack(nil, msg); } }]; }

3.UITableView索引:
- 对数据进行分组排序

/** * 按首字母分组排序 * *  @param sourceArr 目标数组 * *  @return 分好组的数组 */- (NSArray *)groupedByLetter:(NSArray *)sourceArr{ UILocalizedIndexedCollation *collation = [UILocalizedIndexedCollation currentCollation]; //依照索引创建相应的数组 NSMutableArray *sectionsArr = [NSMutableArray arrayWithCapacity:collation.sectionTitles.count]; for (int index = 0; index < collation.sectionTitles.count; index++) { NSMutableArray *arr = [NSMutableArray arrayWithCapacity:1]; [sectionsArr addObject:arr]; } //设置StudentModel模型的组号。并装进相应数组中 for (StudentModel *studentModel in sourceArr) { NSInteger section = [collation sectionForObject:studentModel collationStringSelector:@selector(name)]; studentModel.section = section; [sectionsArr[section] addObject:studentModel]; } //对每组数据进行按名称首字母排序 NSMutableArray *resultArr = [NSMutableArray arrayWithCapacity:1]; for (NSArray *arr in sectionsArr) { [resultArr addObject:[NSMutableArray arrayWithArray:[collation sortedArrayFromArray:arr collationStringSelector:@selector(name)]]]; } return resultArr;}
  • 在UITableView的代理方法中设置组的标题和索引数组
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{ if (tableView == self.tableView && [self.studentsArr[section] count] > 0) { return [UILocalizedIndexedCollation currentCollation].sectionTitles[section]; } return nil;}- (NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView{ if (tableView == self.tableView) { NSMutableArray *resultArr = [NSMutableArray arrayWithCapacity:1]; for (NSArray *arr in self.studentsArr) { if (arr.count > 0) { NSInteger index = [self.studentsArr indexOfObject:arr]; [resultArr addObject:[UILocalizedIndexedCollation currentCollation].sectionTitles[index]]; } } [resultArr insertObject:UITableViewIndexSearch atIndex:0]; return resultArr; } else { return nil; }}
  • 在UITableView的代理方法中将索引与cell的组联系起来
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index{ NSInteger result = -1; if (tableView == self.tableView) { if (index == 0) { [tableView scrollRectToVisible:tableView.tableHeaderView.frame animated:NO]; } else { result = [[UILocalizedIndexedCollation currentCollation].sectionTitles indexOfObject:title]; } } return result;}

$(function () {
$(‘pre.prettyprint code‘).each(function () {
var lines = $(this).text().split(‘\n‘).length;
var $numbering = $(‘

    ‘).addClass(‘pre-numbering‘).hide();
    $(this).addClass(‘has-numbering‘).parent().append($numbering);
    for (i = 1; i <= lines; i++) {
    $numbering.append($(‘

  • ‘).text(i));
    };
    $numbering.fadeIn(1700);
    });
    });

相关文章