IOS数据存储之Sqlite数据库

时间:2022-01-12 21:57:55

前言:

之前学习了数据存储的NSUserDefaults,归档和解档,沙盒文件存储,但是对于数据量比较大,需要频繁查询,删除,更新等操作的时候无论从效率上还是性能上,上述三种明显不能满足我们的日常开发需要了。这个时候我们必须借助数据库,做为Android开发的都知道采用的是一种轻量级数据库Sqlite。其实它广泛用于包括浏览器、IOS,Android以及一些便携需求的小型web应用系统。它具备占用资源低,处理速度快等优点。接下来我们具体认识一下。

我们在项目开发中需要引入libsqlite3.dylib,那么sqllite有哪些具体方法呢?

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类型的数据

为了系统而且方面的学习sqlite 整理一个sqlite管理类DBManager,实现功能具体涵盖了:数据库的创建,打开,关闭,升级,数据的增删改查,以及事务的开启和开启事务的好处。

DBManager.h

#import <Foundation/Foundation.h>

@interface DBManager : NSObject<NSCopying>

//创建数据库管理者单例
+(instancetype)shareManager; //打开数据库
-(void)openDb; //关闭数据库
-(void)closeDb; //执行sql语句
-(void)execSql:(NSString *)sql; //创建数据库表
-(void)creatTable; //删除表结构
-(void)dropTable; //插入数据
-(void)insertData:(NSString*)tempName; //插入数据未开启事务
-(void)insertDataByNomarl:(NSArray*)tempNames; //插入数据开启事务
-(void)insertDataByTransaction:(NSArray*)tempNames; //删除数据
-(void)deleteData:(NSString*)tempName; //删除数据
-(void)deleteData; //修改数据
-(void)updateData:(NSString*)tempName; //查询数据
-(void)queryData; @end

DBManager.m

#import "DBManager.h"
#import <sqlite3.h> #define DBNAME @"myDb" //数据库名字
#define TBNAME @"persons" //表名
#define DBVERSION 1 //数据库版本
#define DBVERSIONKEY @"dbversion_key" //存储数据库版本key static DBManager *instance=nil; @implementation DBManager
{
//创建数据库实例
sqlite3 *db;
} -(instancetype)init
{
self=[super init];
if (self) {
[self creatTable];
[self upgrade];
}
return self;
} //创建数据库管理者单例
+(instancetype)shareManager
{
if(instance==nil){
@synchronized(self){
if(instance==nil){
instance =[[[self class]alloc]init];
}
}
}
return instance;
} -(id)copyWithZone:(NSZone *)zone
{ return instance;
} +(id)allocWithZone:(struct _NSZone *)zone
{
if(instance==nil){
instance =[super allocWithZone:zone];
}
return instance;
} //打开数据库
-(void)openDb
{ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documents = [paths objectAtIndex:];
NSString *database_path = [documents stringByAppendingPathComponent:DBNAME]; if (sqlite3_open([database_path UTF8String], &db) == SQLITE_OK) {
NSLog(@"数据库打开成功");
}else{
[self closeDb];
NSLog(@"数据库打开失败");
} } //关闭数据库
-(void)closeDb
{
sqlite3_close(db);
} //检查数据库是否需要升级
- (void)upgrade {
//获取存储好的原版本号
NSInteger oldVersionNum = [[NSUserDefaults standardUserDefaults] integerForKey:DBVERSIONKEY];
if (DBVERSION <= oldVersionNum || oldVersionNum == ) {
return;
} //升级
[self upgrade:oldVersionNum]; // 保存新的版本号到库中 -这里大家可以使用NSUserDefault存储
[[NSUserDefaults standardUserDefaults]setInteger:DBVERSION forKey:DBVERSIONKEY];
} //根据不同版本执行不同的升级逻辑
- (void)upgrade:(NSInteger)oldVersion {
if (oldVersion >= DBVERSION) {
return;
}
switch (oldVersion) {
case :
//执行相应的升级操作
break;
case :
//执行相应的升级操作
break;
case :
//执行相应的升级操作
break;
default:
break;
}
oldVersion ++;
// 递归判断是否需要升级
[self upgrade:oldVersion];
} //执行sql语句
-(void)execSql:(NSString *)sql
{
[self openDb];
char *err;
if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) {
NSLog(@"数据库操作数据成功!");
}else{
sqlite3_free(err);
NSLog(@"数据库操作数据失败!");
}
sqlite3_close(db);
} //创建数据库表
-(void)creatTable
{
NSString *creatTableSql=[NSString stringWithFormat:@"create table if not exists %@(person_id integer primary key,name text)",TBNAME];
[self execSql:creatTableSql];
} //删除数据库表
-(void)dropTable
{
NSString *dropTableSql=[NSString stringWithFormat:@"drop table %@",TBNAME];
[self execSql:dropTableSql];
} //插入数据
-(void)insertData:(NSString*)tempName
{
NSString *insertSql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,tempName];
[self execSql:insertSql];
} //插入数据未开启事务
-(void)insertDataByNomarl:(NSArray*)tempNames
{
[self openDb];
for(NSString *name in tempNames){
NSString *sql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name];
char *err;
if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) {
NSLog(@"数据库操作数据成功!");
}else{
sqlite3_free(err);
NSLog(@"数据库操作数据失败!");
}
}
[self closeDb];
} //插入数据开启事务
-(void)insertDataByTransaction:(NSArray*)tempNames
{
@try{
char *errorMsg;
[self openDb];
if (sqlite3_exec(db, "BEGIN", NULL, NULL, &errorMsg)==SQLITE_OK) { NSLog(@"启动事务成功"); sqlite3_free(errorMsg); //执行真正的操作
for(NSString *name in tempNames){
NSString *sql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name];
char *err;
if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) {
NSLog(@"数据库操作数据成功!");
}else{
sqlite3_free(err);
NSLog(@"数据库操作数据失败!");
}
} if (sqlite3_exec(db, "COMMIT", NULL, NULL, &errorMsg)==SQLITE_OK) { NSLog(@"提交事务成功");
} sqlite3_free(errorMsg); }else{
sqlite3_free(errorMsg);
} } @catch(NSException *e){ char *errorMsg;
if (sqlite3_exec(db, "ROLLBACK", NULL, NULL, &errorMsg)==SQLITE_OK) {
NSLog(@"回滚事务成功");
}
sqlite3_free(errorMsg); }
[self closeDb];
} //删除数据
-(void)deleteData:(NSString*)tempName
{
NSString *deleteSql=[NSString stringWithFormat:@"delete from %@ where name = '%@'",TBNAME,tempName];
[self execSql:deleteSql]; } //删除数据
-(void)deleteData
{
NSString *deleteSql=[NSString stringWithFormat:@"delete from %@ ",TBNAME];
[self execSql:deleteSql];
} //修改数据
-(void)updateData:(NSString*)tempName
{
NSString *updateSql=[NSString stringWithFormat:@"update %@ set name ='test' where name = '%@'",TBNAME,tempName];
[self execSql:updateSql]; } //查询数据
-(void)queryData
{
[self openDb];
NSString *querySql =[NSString stringWithFormat:@"select * from %@",TBNAME];
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(db, [querySql UTF8String], -, &stmt, nil) == SQLITE_OK) { while (sqlite3_step(stmt)==SQLITE_ROW) { char *name = (char *)sqlite3_column_text(stmt, );
NSString *nameString = [[NSString alloc] initWithUTF8String:name];
NSLog(@"nameString---->%@",nameString); } sqlite3_finalize(stmt);
}
[self closeDb]; }
@end

具体使用方法:

#import "DBManager.h"
#import <sqlite3.h> #define DBNAME @"myDb" //数据库名字
#define TBNAME @"persons" //表名
#define DBVERSION 1 //数据库版本
#define DBVERSIONKEY @"dbversion_key" //存储数据库版本key static DBManager *shareManager=nil; @implementation DBManager
{
//创建数据库实例
sqlite3 *db;
} -(instancetype)init
{
self=[super init];
if (self) {
[self creatTable];
[self upgrade];
}
return self;
} //创建数据库管理者单例
+(instancetype)shareManager
{
if(shareManager==nil){
@synchronized(self){
if(shareManager==nil){
shareManager =[[[self class]alloc]init];
}
}
}
return shareManager;
} -(id)copyWithZone:(NSZone *)zone
{ return shareManager;
} +(id)allocWithZone:(struct _NSZone *)zone
{
if(shareManager==nil){
shareManager =[super allocWithZone:zone];
}
return shareManager;
} //打开数据库
-(void)openDb
{ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documents = [paths objectAtIndex:];
NSString *database_path = [documents stringByAppendingPathComponent:DBNAME]; if (sqlite3_open([database_path UTF8String], &db) == SQLITE_OK) {
NSLog(@"数据库打开成功");
}else{
[self closeDb];
NSLog(@"数据库打开失败");
} } //关闭数据库
-(void)closeDb
{
sqlite3_close(db);
} //删除数据库
-(void)dropDb
{ } //检查数据库是否需要升级
- (void)upgrade {
//获取存储好的原版本号
NSInteger oldVersionNum = [[NSUserDefaults standardUserDefaults] integerForKey:DBVERSIONKEY];
if (DBVERSION <= oldVersionNum || oldVersionNum == ) {
return;
} //升级
[self upgrade:oldVersionNum]; // 保存新的版本号到库中 -这里大家可以使用NSUserDefault存储
[[NSUserDefaults standardUserDefaults]setInteger:DBVERSION forKey:DBVERSIONKEY];
} //根据不同版本执行不同的升级逻辑
- (void)upgrade:(NSInteger)oldVersion {
//对比数据库版本
if (oldVersion >= DBVERSION) {
return;
}
switch (oldVersion) {
case :
//执行相应的升级操作
break;
case :
//执行相应的升级操作
break;
case :
//执行相应的升级操作
break;
default:
break;
}
oldVersion ++;
// 递归判断是否需要升级
[self upgrade:oldVersion];
} //执行sql语句
-(void)execSql:(NSString *)sql
{
[self openDb];
char *err;
if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) {
NSLog(@"数据库操作数据成功!");
}else{
sqlite3_free(err);
NSLog(@"数据库操作数据失败!");
}
sqlite3_close(db);
} //创建数据库表
-(void)creatTable
{
NSString *creatTableSql=[NSString stringWithFormat:@"create table if not exists %@(person_id integer primary key,name text)",TBNAME];
[self execSql:creatTableSql];
} //删除数据库表
-(void)dropTable
{
NSString *dropTableSql=[NSString stringWithFormat:@"drop table %@",TBNAME];
[self execSql:dropTableSql];
} //插入数据
-(void)insertData:(NSString*)tempName
{
NSString *insertSql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,tempName];
[self execSql:insertSql];
} //插入数据未开启事务
-(void)insertDataByNomarl:(NSArray*)tempNames
{
[self openDb];
for(NSString *name in tempNames){
NSString *sql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name];
char *err;
if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) {
//NSLog(@"数据库操作数据成功!");
}else{
sqlite3_free(err);
//NSLog(@"数据库操作数据失败!");
}
}
[self closeDb];
} //插入数据开启事务
-(void)insertDataByTransaction:(NSArray*)tempNames
{
@try{
char *errorMsg;
[self openDb];
if (sqlite3_exec(db, "BEGIN", NULL, NULL, &errorMsg)==SQLITE_OK) { NSLog(@"启动事务成功"); sqlite3_free(errorMsg); //执行真正的操作
for(NSString *name in tempNames){
NSString *sql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name];
char *err;
if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) {
//NSLog(@"数据库操作数据成功!");
}else{
sqlite3_free(err);
// NSLog(@"数据库操作数据失败!");
}
} if (sqlite3_exec(db, "COMMIT", NULL, NULL, &errorMsg)==SQLITE_OK) { NSLog(@"提交事务成功");
} sqlite3_free(errorMsg); }else{
sqlite3_free(errorMsg);
} } @catch(NSException *e){ char *errorMsg;
if (sqlite3_exec(db, "ROLLBACK", NULL, NULL, &errorMsg)==SQLITE_OK) {
NSLog(@"回滚事务成功");
}
sqlite3_free(errorMsg); }
[self closeDb];
} //删除数据
-(void)deleteData:(NSString*)tempName
{
NSString *deleteSql=[NSString stringWithFormat:@"delete from %@ where name = '%@'",TBNAME,tempName];
[self execSql:deleteSql]; } //删除数据
-(void)deleteData
{
NSString *deleteSql=[NSString stringWithFormat:@"delete from %@ ",TBNAME];
[self execSql:deleteSql];
} //修改数据
-(void)updateData:(NSString*)tempName
{
NSString *updateSql=[NSString stringWithFormat:@"update %@ set name ='test' where name = '%@'",TBNAME,tempName];
[self execSql:updateSql]; } //查询数据
-(void)queryData
{
[self openDb];
NSString *querySql =[NSString stringWithFormat:@"select * from %@",TBNAME];
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(db, [querySql UTF8String], -, &stmt, nil) == SQLITE_OK) { while (sqlite3_step(stmt)==SQLITE_ROW) { char *name = (char *)sqlite3_column_text(stmt, );
NSString *nameString = [[NSString alloc] initWithUTF8String:name];
NSLog(@"nameString---->%@",nameString); } sqlite3_finalize(stmt);
}
[self closeDb]; } @end

重点来了,曾经做个IM即时通讯方面,聊天信息相对来说还是比较庞大一点,动不动就是成千上万条聊天信息,有时候执行一个消息已读状态的更新都要耗时很长,那时候偶还没有学习IOS开发,在Android平台上我已经领略过开启事务对效率的提升所带来的喜悦了,那么ios上面是怎么开启事务的呢?效率怎么样呢?让我们一探究竟:

开启事务:

//插入数据开启事务
-(void)insertDataByTransaction:(NSArray*)tempNames
{
@try{
char *errorMsg;
[self openDb];
if (sqlite3_exec(db, "BEGIN", NULL, NULL, &errorMsg)==SQLITE_OK) { NSLog(@"启动事务成功"); sqlite3_free(errorMsg); //执行真正的操作
for(NSString *name in tempNames){
NSString *sql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name];
char *err;
if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) {
NSLog(@"数据库操作数据成功!");
}else{
sqlite3_free(err);
NSLog(@"数据库操作数据失败!");
}
} if (sqlite3_exec(db, "COMMIT", NULL, NULL, &errorMsg)==SQLITE_OK) { NSLog(@"提交事务成功");
} sqlite3_free(errorMsg); }else{
sqlite3_free(errorMsg);
}
} @catch(NSException *e){ char *errorMsg;
if (sqlite3_exec(db, "ROLLBACK", NULL, NULL, &errorMsg)==SQLITE_OK) {
NSLog(@"回滚事务成功");
}
sqlite3_free(errorMsg);
}
[self closeDb];
}

同时准备一个未开启事务的:

//插入数据未开启事务
-(void)insertDataByNomarl:(NSArray*)tempNames
{
[self openDb];
for(NSString *name in tempNames){
NSString *sql = [NSString stringWithFormat:@"insert into %@ (name) values ('%@')",TBNAME,name];
char *err;
if (sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) == SQLITE_OK) {
NSLog(@"数据库操作数据成功!");
}else{
sqlite3_free(err);
NSLog(@"数据库操作数据失败!");
}
}
[self closeDb];
}

测试程序:

        //测试事务
NSMutableArray *testArray =[[NSMutableArray alloc]init];
int testMaxCount =;
for(int i=;i<testMaxCount;i++){
NSString *string = [[NSString alloc] initWithFormat:@"%d",i];
[testArray addObject:string];
} //未开启事务插入
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent(); [[DBManager shareManager]insertDataByNomarl:testArray];
CFAbsoluteTime end=CFAbsoluteTimeGetCurrent();
NSLog(@"普通插入 time cost: %0.3f", end - start); //删除数据
[[DBManager shareManager]deleteData]; //开启事务插入
start = CFAbsoluteTimeGetCurrent(); [[DBManager shareManager]insertDataByTransaction:testArray]; end=CFAbsoluteTimeGetCurrent();
NSLog(@"开启事务插入 time cost: %0.3f", end - start); //删除数据
[[DBManager shareManager]deleteData];

测试结果:测试数据10000条 单位(秒)

开启事务耗时:0.049

未开启事务耗时:5.614

看到上面的执行结果 是不是惊呆了。

关于数据库升级:由于项目业务发展,数据库有可能要考虑到升级,比如数据库新增表或者已有表结构变化,这时候我们就要考虑到数据升级来做版本兼容:

什么时候检查:

-(instancetype)init
{
self=[super init];
if (self) {
[self creatTable];
[self upgrade];
}
return self;
}

怎么实现版本升级:

//检查数据库是否需要升级
- (void)upgrade {
//获取存储好的原版本号
NSInteger oldVersionNum = [[NSUserDefaults standardUserDefaults] integerForKey:DBVERSIONKEY];
if (DBVERSION <= oldVersionNum || oldVersionNum == ) {
return;
} //升级
[self upgrade:oldVersionNum]; // 保存新的版本号到库中 -这里大家可以使用NSUserDefault存储
[[NSUserDefaults standardUserDefaults]setInteger:DBVERSION forKey:DBVERSIONKEY];
} //根据不同版本执行不同的升级逻辑
- (void)upgrade:(NSInteger)oldVersion {
//对比数据库版本
if (oldVersion >= DBVERSION) {
return;
}
switch (oldVersion) {
case :
//执行相应的升级操作
break;
case :
//执行相应的升级操作
break;
case :
//执行相应的升级操作
break;
default:
break;
}
oldVersion ++;
// 递归判断是否需要升级
[self upgrade:oldVersion];
}

至此原生的Sqlite基础使用就告一段落了,至于高级使用一般情况涉及到的多数是sql语句的使用,sql语句不善长的小伙伴可以去熟悉一下sql数据!这时就在想了IOS有没有像Android一样的第三方数据库框架呢?也让我等sql小白缓解一下压力?特意查询了一下,以下仅供参考:Sqlitepersistentobjects ,FMDB(这个在两年前使用过)。