SQLite学习和业务封装
看了一段时间的MySQL,正好借此机会把常用于嵌入式和移动设备的SQLite复习了一下. 我结合了公司的项目 以实际的业务需求为导向,对sqlite3进行了简单的封装,实现对项目中搜索记录的管理. 需求分析
准备工作在准备的过程中遇到的第一个问题就是,项目中用来存储历史记录的数据库和对应的表,是要动态调用sql语句创建还是直接在本地创建好,然后把文件导入工程中. 如果是动态创建,那么在项目的整体逻辑中要经常判断是否已经存在了我们所需要的库和表,个人觉得很烦,而且一个db文件占内存也就才几K,所以我就选择了直接创建然后导入工程的方式. 我用的sqlite图形化工具是 DB Browser for SQLite,操作起来很简单,库和表分分钟搞定.
表名search_history 封装类的方法
其实就是这两个类,先忽略那个Error文件吧,我感觉自己处理error做的不太好,里面也就没写啥,只有两个字段. 重点是SearchDBHelper这个类. 下面是.h文件中的代码,我只抛出了需要的这几个方法 #import <Foundation/Foundation.h> @class BaseSQLError; NS_ASSUME_NONNULL_BEGIN // 历史记录类型 // 根据项目中的实际使用场景来定义,每个场景定义一个type. // 比如说项目搜索和商品搜索页面都要分别记录搜索历史,那就创建两个类型,一个对应项目列表,一个对应商品列表,以此类推 extern NSString * const SEARCH_TYPE_1; extern NSString * const SEARCH_TYPE_2; @interface SearchDBHelper : NSObject /** 创建SearchDBHelper实例,设置变量的默认 @param searchType 历史记录类型 @return 实例 */ + (instancetype)initWithSearchType:(NSString *)searchType; /** 设置最大存储数量和类型 @param max 最大储存数量 */ - (void)setMax:(NSInteger)max; /** 获取当前分类下所有的历史记录 @return 所有历史记录 */ - (NSArray<NSString *> *)getAllSearchData; /** 插入数据 @param keyword 要要入到表中的搜索记录 @param complete 完成的回调 */ - (void)insertData:(NSString *)keyword callBack:(void (^)(BaseSQLError * _Nullable error))complete; /** 清除一条搜索记录 @param keyword 要清除的搜索关键字 @param complete 完成回调 */ - (void)clearSingleSearchData:(NSString *)keyword callBack:(void (^)( BaseSQLError * _Nullable error))complete; /** 删除当前类型的所有搜索记录 */ - (void)clearAllSearchDataFromCurrentType:(void (^)(BaseSQLError * _Nullable error))complete; /** 获取该数据库占内存大小 @return 数据库大小:单位k */ + (NSString *)getSizeFromDataBase; /** 删除所有的搜索历史记录(清空记录缓存) */ + (void)clearAllSearchData:(void (^)(BaseSQLError * _Nullable error))complete; NS_ASSUME_NONNULL_END @end 针对这个.h文件中的代码,我说几个点
方法实现逻辑其实大部分的功能看 .m 中的代码都是可以看明白的,我这里稍微简单的说一下一些思路问题吧. 就拿insert这个方法来说,这个方法也是比较麻烦的一个了吧,下面是方法的实现代码 - (void)insertData:(NSString *)keyword callBack:(void (^)(BaseSQLError *_Nullable error))complete { // 判断是否已经存储过此keyword 已存储:update 未存储:insert if ([self selectKeyWordIsExit:keyword] == ExitKeyWord) { [self updateSearchKeyword:keyword callBack:complete]; } else if ([self selectKeyWordIsExit:keyword] == NotExitKeyWord){ [self insertSearchKeyword:keyword callBack:complete]; } else if ([self selectKeyWordIsExit:keyword] == ExitUnknown){ BaseSQLError * sqlErr = [BaseSQLError new]; sqlErr.errInfo = @"插入数据库失败"; sqlite3_close(_sql3); if(complete){ complete(sqlErr); } return; } } 可以看出来,在插入一个数据之前,要先要先判断这个内容是否已经存在了,也就是selectKeyWordIsExit:这个方法,他返回的是一个枚举值(存在,不存在,未知),未知就是代表在操作数据库是出错. 如果已经存在了,那就调用update的方法,来更新这个search_content对应的search_time. 如果不存在,在进行下一步的判断. 那就是判断当前的这个search_type下储存的数据条数,是否已经到了最大值了,这个最大值我们可以通过调用方法设置,如果不设置默认我给的是10条. 就拿max为10来说,如果还没有到达10条,现在可以调用insert的方法了,如果已经有10条了,那就得把时间最早的那一条删掉,然后再insert这条新的. 数据库操作无非就是增删改查,增删改这个三类操作比较简单,因为没有返回的数据,但是查询呢,有一种数量的查询(查询某个searchTpye下已经储存的数据的条数),这种只是放回了一个int的值. 在一种就是查询数据了(比如说当前searchType下所有的储存的历史距离),那这一种就要返回一个数组NSArray <NSString *>*. 这两种查询的sql实现不太一样,我在代码里都有标识了. 大家可以具体看看代码 . 这个类的封装逻辑就是这个样子,具体的sqlite语句的用法,大家可以看看代码. 我这里把整个.m代码也放出来,大家可以这样看,也可以去github上面下载代码看一下,而且我在github的代码中还做了一个小demo来演示用法. 本项目的github地址 .m实现代码// // SearchDBHelper.m // LearnSQLite // // Created by Sunxb on 2018/4/26. // Copyright ? 2018年 Sunxb. All rights reserved. // #import "SearchDBHelper.h" #import <sqlite3.h> #import "BaseSQLError.h" // 表名 static NSString * const TABLE_NAME = @"search_history"; // 类型名 NSString * const SEARCH_TYPE_1 = @"type1"; NSString * const SEARCH_TYPE_2 = @"type2"; // 是否已经存在某条记录 typedef NS_ENUM(NSInteger,DBExitKeyWord) { ExitKeyWord = 0,NotExitKeyWord,ExitUnknown }; @interface SearchDBHelper() @property (nonatomic,strong) NSString * searchType; @property (nonatomic,assign) NSInteger maxNum; @property (nonatomic,assign) sqlite3 *sql3; @end @implementation SearchDBHelper + (instancetype)initWithSearchType:(NSString *)searchType { SearchDBHelper * helper = [[SearchDBHelper alloc] init]; helper.searchType = searchType; return helper; } - (instancetype)init { if (self = [super init]){ self.maxNum = 10; self.searchType = @""; } return self; } - (void)setMax:(NSInteger)max { self.maxNum = max; } - (NSArray<NSString *> *)getAllSearchData { return [self runSelectListSQLWithType:self.searchType]; } - (void)insertData:(NSString *)keyword callBack:(void (^)(BaseSQLError *_Nullable error))complete { // 判断是否已经存储过此keyword 已存储:update 未存储:insert if ([self selectKeyWordIsExit:keyword] == ExitKeyWord) { [self updateSearchKeyword:keyword callBack:complete]; } else if ([self selectKeyWordIsExit:keyword] == NotExitKeyWord){ [self insertSearchKeyword:keyword callBack:complete]; } else if ([self selectKeyWordIsExit:keyword] == ExitUnknown){ BaseSQLError * sqlErr = [BaseSQLError new]; sqlErr.errInfo = @"插入数据库失败"; sqlite3_close(_sql3); if(complete){ complete(sqlErr); } return; } } - (void)clearSingleSearchData:(NSString *)keyword callBack:(void (^)(BaseSQLError *_Nullable error))complete { NSString * deleteSingle = [NSString stringWithFormat:@"delete from search_history where search_content="%@" and search_type="%@";",keyword,self.searchType]; [self runSQL:deleteSingle callBack:complete]; } - (void)clearAllSearchDataFromCurrentType:(void (^)(BaseSQLError *_Nullable error))complete { NSString * deleteSingle = [NSString stringWithFormat:@"delete from search_history where search_type="%@";",self.searchType]; [self runSQL:deleteSingle callBack:complete]; } + (NSString *)getSizeFromDataBase { NSString * dbPath = [self getDBPath]; NSLog(@"=== %@",dbPath); NSFileManager * fileManager = [NSFileManager defaultManager]; if (dbPath.length > 0) { long long dbSize = [fileManager attributesOfItemAtPath:dbPath error:nil].fileSize; return [NSString stringWithFormat:@"%gk",dbSize/1024.0]; } return @"0k"; } + (void)clearAllSearchData:(void (^)(BaseSQLError *))complete { SearchDBHelper * helper = [[SearchDBHelper alloc] init]; NSString * deleteAll = [NSString stringWithFormat:@"delete from search_history"]; [helper runSQL:deleteAll callBack:complete]; } #pragma mark - private // 获取数据库文件在工程中的路径 + (NSString *)getDBPath { NSString *filePath = [[NSBundle mainBundle] pathForResource:@"mydb" ofType:@"db"]; NSFileManager * manager = [NSFileManager defaultManager]; if ([manager fileExistsAtPath:filePath]) { return filePath; } return @""; } // 是否打开数据库 - (BOOL)openDataBase { NSString * dbPath = [SearchDBHelper getDBPath]; if(dbPath.length > 0) { if (sqlite3_open([dbPath UTF8String],&_sql3) != SQLITE_OK) { sqlite3_close(_sql3); _sql3 = nil; return NO; } else { return YES; } } return NO; } // 运行普通无返回sql语句 : insert || update - (void)runSQL:(NSString *)sql callBack:(void (^)(BaseSQLError *))complete { BaseSQLError * sqlErr = [BaseSQLError new]; if (![self openDataBase]) { sqlErr.errInfo = @"打开数据库失败"; if(complete){ complete(sqlErr); } return; } char * err; int code = sqlite3_exec(_sql3,[sql UTF8String],NULL,&err); if (code != SQLITE_OK) { BaseSQLError * sqlErr = [BaseSQLError new]; sqlErr.errInfo = [NSString stringWithCString:err encoding:NSUTF8StringEncoding]; sqlErr.errCode = code; complete(sqlErr); } else { complete(nil); } sqlite3_close(_sql3); _sql3 = nil; } // TODO: 运行查询性质的sql语句 - 返回数组 (比如查询所有的数据) - (NSArray *)runSelectListSQLWithType:(NSString *)searchType { if (![self openDataBase]) { BaseSQLError * sqlErr = [BaseSQLError new]; sqlErr.errInfo = @"打开数据库失败"; return nil; } NSString * sql = [NSString stringWithFormat:@"select search_content from search_history where search_type="%@" order by search_time desc;",searchType]; sqlite3_stmt * statement; if (sqlite3_prepare_v2(_sql3,-1,&statement,NULL)==SQLITE_OK){ NSMutableArray * historyArr = [NSMutableArray new]; while (sqlite3_step(statement) == SQLITE_ROW) { NSString * str = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(statement,0)]; [historyArr addObject:str]; } sqlite3_finalize(statement); statement = nil; return historyArr; } sqlite3_close(_sql3); return nil; } /** 运行查询性质的sql语句 - 返回数量 (比如查询符合某条件的结果数) @param dict 需要查询的条件(key value形式) @param complete 完成回调 @return 查询的数值 (查询过程出错返回 -1) */ - (NSInteger)runCountSelectSQL:(NSDictionary *)dict callBack:(void (^)(BaseSQLError *))complete { if (![self openDataBase]) { BaseSQLError * sqlErr = [BaseSQLError new]; sqlErr.errInfo = @"打开数据库失败"; if(complete){ complete(sqlErr); } return -1; } NSMutableString * searchRequire = [NSMutableString new]; NSArray * keysArr = dict.allKeys; for (int i = 0; i < keysArr.count; i ++){ NSString * key = keysArr[i]; if (i == 0){ [searchRequire appendFormat:@"%@="%@"",key,[dict objectForKey:key]]; } else { [searchRequire appendFormat:@"and %@="%@"",[dict objectForKey:key]]; } } NSString * selSql = [NSString stringWithFormat:@"select count(*) from search_history where %@;",searchRequire]; sqlite3_stmt * statement; //预处理 if(sqlite3_prepare_v2(_sql3,[selSql UTF8String],NULL)==SQLITE_OK){ sqlite3_step(statement); NSInteger result = sqlite3_column_int(statement,0); sqlite3_finalize(statement); statement = nil; return result; } return -1; } // 查询某记录是否已经存在 - (DBExitKeyWord)selectKeyWordIsExit:(NSString *)keyword { NSInteger count = [self runCountSelectSQL:@{@"search_content":keyword,@"search_type":self.searchType} callBack:nil]; if(count > 0) { return ExitKeyWord; } else if (count == 0){ return NotExitKeyWord; } else { return ExitUnknown; } } //插入数据 - (void)insertSearchKeyword:(NSString *)keyword callBack:(void (^)(BaseSQLError *))complete { // TODO: 是否已经超过了最大存储数量 NSInteger alreadCount = [self runCountSelectSQL:@{@"search_type":self.searchType} callBack:complete]; if(alreadCount >= 0) { if (alreadCount >= self.maxNum) { // 先删除时间最早的记录,再插入 [self deleteEarliestSearchKeywordWithType:self.searchType callBack:^(BaseSQLError *err) { if (err==nil) { [self sqlInsert:keyword callBack:complete]; } else { if(complete){ complete(err); } } }]; } else { // 直接插入 [self sqlInsert:keyword callBack:complete]; } } else { BaseSQLError * sqlErr = [BaseSQLError new]; sqlErr.errInfo = @"插入数据失败"; if(complete){ complete(sqlErr); } } } - (void)sqlInsert:(NSString *)keyword callBack:(void (^)(BaseSQLError *))complete{ NSString * keyStr = @"search_type,search_content,search_time"; NSString * valuesStr = [NSString stringWithFormat:@""%@","%@","%@"",self.searchType,[self getCurrentTimestamp]]; NSString * insertSql = [NSString stringWithFormat:@"insert into search_history (%@) values (%@);",keyStr,valuesStr]; [self runSQL:insertSql callBack:complete]; } //更新数据 - (void)updateSearchKeyword:(NSString *)keyword callBack:(void (^)(BaseSQLError *))complete { NSString * updateSql = [NSString stringWithFormat:@"update search_history set search_time="%@" where search_content="%@";",[self getCurrentTimestamp],keyword]; [self runSQL:updateSql callBack:complete]; } // 删除当前分类下,时间最早的记录 - (void)deleteEarliestSearchKeywordWithType:(NSString *)searchType callBack:(void (^)(BaseSQLError *))complete{ NSString * deleteStr = @"delete from search_history where id in (select id from search_history where search_type="%@" order by search_time limit 0,1)"; NSString * deleteSql = [NSString stringWithFormat:deleteStr,searchType]; [self runSQL:deleteSql callBack:complete]; } // 查询当前类型下已经存储的历史记录的条数 - (NSInteger)getHistoryCountInDB { return [self getHistoryCountInDBWithSearchType:self.searchType]; } - (NSInteger)getHistoryCountInDBWithSearchType:(NSString *)type { NSInteger count = [self runCountSelectSQL:@{@"search_type":type} callBack:nil]; count = (count>0?count:0); return count; } // 获取当前时间戳 - (NSString*)getCurrentTimestamp { NSDate * data = [NSDate dateWithTimeIntervalSinceNow:0]; NSTimeInterval a = [data timeIntervalSince1970]; NSString * timeString = [NSString stringWithFormat:@"%0.f",a]; return timeString; } @end 特此感谢: https://my.oschina.net/u/2340... 给我巨大的灵感. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |