IOS数据存储之Sqlite数据库

前言:

  之前学习了数据存储的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
View Code

 

 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:0];
    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 == 0) {
        return;
    }
    
    //升级
    [self upgrade:oldVersionNum];
    
    // 保存新的版本号到库中 -这里大家可以使用NSUserDefault存储
    [[NSUserDefaults standardUserDefaults]setInteger:DBVERSION forKey:DBVERSIONKEY];
}

//根据不同版本执行不同的升级逻辑
- (void)upgrade:(NSInteger)oldVersion {
    if (oldVersion >= DBVERSION) {
        return;
    }
    switch (oldVersion) {
        case 0:
            //执行相应的升级操作
            break;
        case 1:
             //执行相应的升级操作
            break;
        case 2:
             //执行相应的升级操作
            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], -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];
            NSLog(@"nameString---->%@",nameString);

        }  
        
        sqlite3_finalize(stmt);  
    }  
    [self closeDb];
    
}
@end
View Code

 

具体使用方法:

#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:0];
    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 == 0) {
        return;
    }
    
    //升级
    [self upgrade:oldVersionNum];
    
    // 保存新的版本号到库中 -这里大家可以使用NSUserDefault存储
    [[NSUserDefaults standardUserDefaults]setInteger:DBVERSION forKey:DBVERSIONKEY];
}

//根据不同版本执行不同的升级逻辑
- (void)upgrade:(NSInteger)oldVersion {
    //对比数据库版本
    if (oldVersion >= DBVERSION) {
        return;
    }
    switch (oldVersion) {
        case 0:
            //执行相应的升级操作
            break;
        case 1:
             //执行相应的升级操作
            break;
        case 2:
             //执行相应的升级操作
            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], -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];
            NSLog(@"nameString---->%@",nameString);

        }  
        
        sqlite3_finalize(stmt);  
    }  
    [self closeDb];
    
}


@end
View Code

 

重点来了,曾经做个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 =10000;
          for(int i=0;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 == 0) {
        return;
    }
    
    //升级
    [self upgrade:oldVersionNum];
    
    // 保存新的版本号到库中 -这里大家可以使用NSUserDefault存储
    [[NSUserDefaults standardUserDefaults]setInteger:DBVERSION forKey:DBVERSIONKEY];
}

//根据不同版本执行不同的升级逻辑
- (void)upgrade:(NSInteger)oldVersion {
    //对比数据库版本
    if (oldVersion >= DBVERSION) {
        return;
    }
    switch (oldVersion) {
        case 0:
            //执行相应的升级操作
            break;
        case 1:
             //执行相应的升级操作
            break;
        case 2:
             //执行相应的升级操作
            break;
        default:
            break;
    }
    oldVersion ++;
    // 递归判断是否需要升级
    [self upgrade:oldVersion];
}

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

 

posted on 2016-05-12 15:49  总李写代码  阅读(1526)  评论(0编辑  收藏  举报