Objective-C之数据持久化-SQLite

山涧一点绿,疑似,非是

可惜,可悲,可叹

(保护环境)

1(引自百度图库)

函数说明参照文章:

http://blog.csdn.net/zhuzhihai1988/article/details/7878093

简介

SQLite是一款轻量级数据库,开源,它采用C语言编写,具有可移植性强、可靠性高、小而容易使用的特点。现在使用的是SQLite3。数据库文件后缀一般定义为db或sqlite3,皆可,因为Linux有无后缀名没有本质区别。

SQLite是无数据类型的数据库,就是字段不用指定类型,不过实际操作中我们一般都是指定类型。

SQLite的字段数据类型和MYSQL等不太一样。最常用的数据类型是:

  • INTEGER:有符号的整数类型。
  • REAL:浮点类型。
  • TEXT:字符串类型,采用UTF-8和UTF-16字符编码。
  • BLOB:二进制大对象类型,能够存放任何二进制数据。

在SQLite中没有Boolean类型,可以采用整数0和1替代。也没有日期和时间类型,它们存储在TEXT、REAL和INTEGER类型中。其他数据库的类型可以进行转换:

  • VARCHAR、CHAR和CLOB转换成为TEXT类型。
  • FLOAT、DOUBLE转换成为REAL类型。
  • NUMERIC转换成为INTEGER或者REAL类型。

SQLite3.0开发库

苹果自带SQLite3.0开发库,使用SQLite的工程需要导入SQLite3.0开发库(libsqlite3.dylib或libsqlite3.0.dylib)。SQLite3.0开发库中自带对SQLite数据库的纯C语言函数,Objective-C可以直接调用C函数,不过需要注意两者数据类型的兼容问题。

  • 导入SQLite3.0开发库:

XCode打开需要使用SQLite的工程,单击选择工程,右侧出现工程的设置画面,选择Bulid Phases tab,如下图,打开Link Binary With Libraries,空的吧,点击+来添加IOS自带的库。

1

点击+后,画面如下,搜索sqlite,会检索出两个sqlite3.0的库,任意选择其中一个库,点击“Add”。(添加第三方库时,点击Add Other)

2

添加完毕后,列表中会显示库,如下。现在我们就可以使用SQLite的函数了。

3

  • 使用SQLite3.0库:

数据库无非就是创建DB,创建表,条件检索,全检索,增加,删除,修改。那么我们给出各个代码并分析其使用到的SQLite3.0的函数。

这里我创建了一个类UserDao,对应的Entity类为UserInfo(Java的孩纸们注意啦,这里可以没有映射)

UserInfo类:(只给出头文件喽)

@interface UserInfo : NSObject
@property NSInteger userId;
@property (nonatomic, strong) NSString *userName;
@property (nonatomic, strong) NSString *age;
@property (nonatomic, strong) NSString *tel;
@end

UserDao类:(UserList数据库管理类)

#import <Foundation/Foundation.h>
#import "sqlite3.h"//引入sqlite3.h。
#import "UserInfo.h"

@interface UserDao : NSObject
{
sqlite3 *db;//sqlite库对象
}

+(UserDao *)shareDBManager;//单例模式
-(NSString *)documentDirectoryFile;//UserList数据库路径
-(void)createEditableCopyOfDatabaseIfNeeded;//创建表
-(int) insert:(UserInfo *)userInfo;//添加数据
-(int) remove:(UserInfo *)userInfo;//删除数据
-(int) modify:(UserInfo *)userInfo;//修改数据
-(UserInfo *) findById:(UserInfo *)userInfo;//条件检索
-(NSMutableArray *) findAll;//全检索

@end

UserDao.m:

#define DB_NAME @"UserList.sqlite3"//数据库文件名

static UserDao* singleton = nil;

+(UserDao *)shareDBManager{
static dispatch_once_t onceToken;//GCD单例,只执行一次。
dispatch_once(&onceToken, ^{
singleton = [[self alloc] init];
});
return singleton;
}

-(NSString *)documentDirectoryFile{
NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];//documents沙盒路径
NSString *dbFilePath = [documentPath stringByAppendingPathComponent:DB_NAME];//拼接DB文件路径
return dbFilePath;
}

创建表:

-(void)createEditableCopyOfDatabaseIfNeeded{
NSString *writableDBPath = [self documentDirectoryFile];//获取db文件路径

if (sqlite3_open([writableDBPath UTF8String], &db) != SQLITE_OK) {//打开DB,NSString转为const char
sqlite3_close(db);
}else{
char *err;
NSString *createSQL = [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS users(userId INTEGER PRIMARY KEY AUTOINCREMENT, userName TEXT, age TEXT, tel TEXT)"];//创建表SQL
if (sqlite3_exec(db, [createSQL UTF8String], NULL, NULL, &err) != SQLITE_OK) {//执行SQL
sqlite3_close(db);
NSAssert1(NO, @"建表失败:%s", err);
}
sqlite3_close(db);//关闭DB
}
}

创建表我们用到了三个纯C函数,sqlite3_open→sqlite3_exec→sqlite3_close。

int sqlite3_open(const char *filename, sqlite3 **ppDb)//第一个参数时db文件全路径,第二个参数时sqlite3对象地址。

看完代码你可能会奇怪如果db文件不存在怎么办,不会出问题吗。事实上,sqlite3_open函数,如果该路径下文件不存在的话,会自动创建一个。如果打开成功的话会返回0即SQLITE_OK,SQLite3库自带结果集,其中最常用的是三个正常flag。

#define SQLITE_OK 0 /* Successful result */
#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */
#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */

int sqlite3_exec(sqlite3*,const char *sql,int (*callback)(void*,int,char**,char**),void *, char **errmsg)//第一个参数是打开了的db,第二个是要执行的sql文,第三个是回调函数,第四个是回调函数的第一个参数,第五个是错误信息。

int sqlite3_close(sqlite3 *)//释放DB资源,只要执行过sqlite3_open就一定要有对应的sqlite3_close,即使打开失败。

添加数据:

-(int)insert:(UserInfo *)userInfo{
NSString *writableDBPath = [self documentDirectoryFile];//获取db文件路径
if (sqlite3_open([writableDBPath UTF8String], &db)!=SQLITE_OK) {//打开DB
sqlite3_close(db);
NSAssert(NO, @"打开DB失败!");
}else{
sqlite3_stmt *statement;//SQL语句对象
NSString *sql = @"INSERT OR REPLACE INTO users(userName,age,tel) VALUES(?,?,?)";//追加数据的sql文,参数用"?"代替。
if (sqlite3_prepare_v2(db, [sql UTF8String], -1, &statement, NULL)==SQLITE_OK) {//准备sql给sqlite3_stmt对象
sqlite3_bind_text(statement, 1, [userInfo.userName UTF8String], -1, NULL);//绑定sql中的?参数,不同数据类型对应不同的函数。
sqlite3_bind_text(statement, 2, [userInfo.age UTF8String], -1, NULL);
sqlite3_bind_text(statement, 3, [userInfo.tel UTF8String], -1, NULL);
if (sqlite3_step(statement) != SQLITE_DONE) {//执行sqlite3_prepare_v2创建的sqlite3_stmt对象
NSAssert(NO, @"插入数据失败!");
}
}
sqlite3_finalize(statement);//释放sqlite3_stmt对象
sqlite3_close(db);//释放db
}
return 0;
}

插入数据操作,一般会涉及6个函数:(如果没有参数则不用sqlite3_bind_*)sqlite3_open→sqlite3_prepare_v2→sqlite3_bind_*→sqlite3_step→sqlite3_finalize→sqlite3_close。

这里我们使用了一个新的类型sqlite3_stmt,SQL语句对象,我们使用sqlite3_prepare_v2将sql文和sqlite3_stmt对象绑定,然后通过操作sqlite3_stmt来给sql文中参数传参、执行sql、获取结果。最后记得使用sqlite3_finalize释放它。

int sqlite3_prepare_v2(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt,const char **pzTail)//这个函数将sql文本转换成一个准备语句对象sqlite3_stmt,同时返回这个对象的指针,它实际上并不执行这个SQL语句,仅为执行准备这个sql语句。

  • db:sqlite3对象指针
  • zSql:sql语句,使用UTF-8编码,NSString对象使用UTF8String转换
  • nByte:如果nByte小于0,则函数取出zSql中从开始到第一个0终止符的内容;如果nByte不是负的,那么它就是这个函数能从zSql中读取的字节数的最大值。一般为-1
  • ppStmt:能够使用sqlite3_step()执行的编译好的准备语句的指针,如果错误发生,它被置为NULL,如假如输入的文本不包括sql语句。调用过程必须负责在编译好的sql语句完成使用后使用sqlite3_finalize()释放它。
  • pzTail:上面提到zSql在遇见终止符或者是达到设定的nByte之后结束,假如zSql还有剩余的内容,那么这些剩余的内容被存放到pZTail中,不包括终止符。

int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*))//sqlite3_bind_*()给宿主参数(sql文中的?)绑定值,*代表不同类型,如下,与sqlite数据库数据类型匹配。

  • 第一个参数:sqlite3_stmt对象
  • 第二个参数:sql中第m个?(从1开始)
  • 第三个参数:给第m个?赋的值
  • 第四个参数:第三个参数字符串长度,一般给-1
  • 第五个参数:回调函数

int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*));
int sqlite3_bind_double(sqlite3_stmt*, int, double);
int sqlite3_bind_int(sqlite3_stmt*, int, int);
int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64);
int sqlite3_bind_null(sqlite3_stmt*, int);
int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*));
int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*));
int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*);
int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n);

int sqlite3_step(sqlite3_stmt*)//执行sqlite3_stmt对象,这个语句执行到结果的第一行可用的位置。继续前进到结果的第二行的话,需要再次调用sqlite3_setp()。继续调用sqlite3_setp()直到这个语句完成,用while循环执行,使用SQLITE3_ROW判断,如果返回结果是SQLITE3_ROW则说明不是还有结果语句。那些不返回结果的语句(如:INSERT,UPDATE,或DELETE),sqlite3_step()只执行一次就返回,用SQLITE3_DONE判断是否成功。所以这里是有SQLITE3_DONE判断。

int sqlite3_finalize(sqlite3_stmt *pStmt)//释放SQL语句对象,必须执行(不论准备结果是成功还是失败)。

修改数据:

-(int)modify:(UserInfo *)userInfo{
NSString *writableDBPath = [self documentDirectoryFile];
if (sqlite3_open([writableDBPath UTF8String], &db) != SQLITE_OK) {
sqlite3_close(db);
NSAssert(NO, @"打开DB失败!");
}else{
sqlite3_stmt *statement;
NSString *sql = @"UPDATE users SET userName = ?, age = ?, tel = ? WHERE userId = ? ";
if (sqlite3_prepare_v2(db, [sql UTF8String], -1, &statement, NULL) == SQLITE_OK) {
sqlite3_bind_text(statement, 1, [userInfo.userName UTF8String], -1, NULL);
sqlite3_bind_text(statement, 2, [userInfo.age UTF8String], -1, NULL);
sqlite3_bind_text(statement, 3, [userInfo.tel UTF8String], -1, NULL);
sqlite3_bind_int(statement, 4, (int)userInfo.userId);
if (sqlite3_step(statement) != SQLITE_DONE) {
NSAssert(NO, @"修改数据失败");
}
}
sqlite3_finalize(statement);
sqlite3_close(db);
}
return 0;
}

修改数据操作,一般会涉及6个函数:(如果没有参数则不用sqlite3_bind_*)sqlite3_open→sqlite3_prepare_v2→sqlite3_bind_*→sqlite3_step→sqlite3_finalize→sqlite3_close。

删除数据:

-(int)remove:(UserInfo *)userInfo{
NSString *writableDBPath = [self documentDirectoryFile];
if (sqlite3_open([writableDBPath UTF8String], &db) != SQLITE_OK) {
sqlite3_close(db);
NSAssert(NO, @"打开DB失败!");
}else{
sqlite3_stmt *statement;
NSString *sql = @"DELETE FROM users WHERE userId = ?";
if (sqlite3_prepare_v2(db, [sql UTF8String], -1, &statement, NULL) == SQLITE_OK) {
sqlite3_bind_int(statement, 1, (int)userInfo.userId);
if (sqlite3_step(statement) != SQLITE_DONE) {
NSAssert(NO, @"删除数据失败!");
}
}
sqlite3_finalize(statement);
sqlite3_close(db);
}
return 0;
}

删除数据操作,一般会涉及6个函数:(如果没有参数则不用sqlite3_bind_*)sqlite3_open→sqlite3_prepare_v2→sqlite3_bind_*→sqlite3_step→sqlite3_finalize→sqlite3_close。

数据的增删改的操作流程是一样的,除了sql文不一样。

条件查找:

-(UserInfo *)findById:(UserInfo *)userInfo{
NSString *writableDBPath = [self documentDirectoryFile];
if (sqlite3_open([writableDBPath UTF8String], &db) != SQLITE_OK) {//打开DB,不存在则自动创建
sqlite3_close(db);
NSAssert(NO, @"打开DB失败!");
}else{
sqlite3_stmt *statement;
NSString *sql = @"SELECT * FROM users WHERE userId = ? ";
if (sqlite3_prepare_v2(db, [sql UTF8String], -1, &statement, NULL) == SQLITE_OK) {//准备SQL语句对象
sqlite3_bind_int(statement, 1, (int)userInfo.userId);//绑定SQL参数
if (sqlite3_step(statement) == SQLITE_ROW) {//执行SQL语句对象,每次取一条结果
UserInfo *result = [[UserInfo alloc] init];
result.userId = userInfo.userId;
char *userName= (char *)sqlite3_column_text(statement, 1);//取结果
result.userName = [[NSString alloc] initWithUTF8String:userName];
char *age = (char *)sqlite3_column_text(statement, 2);
result.age = [[NSString alloc] initWithUTF8String:age];
char *tel = (char *)sqlite3_column_text(statement, 3);
result.tel = [[NSString alloc] initWithUTF8String:tel];
return result;
}
}
sqlite3_finalize(statement);
sqlite3_close(db);
}
return nil;
}

查询数据操作,一般会涉及7个函数:(如果没有参数则不用sqlite3_bind_*)sqlite3_open→sqlite3_prepare_v2→sqlite3_bind_*→sqlite3_step→sqlite3_column_*sqlite3_finalize→sqlite3_close。

查找的sqlite3_step返回值用SQLITE_ROW判断是否还有数据。

sqlite3_column_*(sqlite3_stmt*, int iCol)//从执行sqlite3_step()执行一个准备语句得到的结果集的当前行中返回一个列。第一个参数是执行的SQL语句对象,第二个参数是结果集中列的序号。其中函数名中*代表各个数据类型。

const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);
int sqlite3_column_bytes(sqlite3_stmt*, int iCol);
int sqlite3_column_bytes16(sqlite3_stmt*, int iCol);
double sqlite3_column_double(sqlite3_stmt*, int iCol);
int sqlite3_column_int(sqlite3_stmt*, int iCol);
sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol);
const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);
const void *sqlite3_column_text16(sqlite3_stmt*, int iCol);
int sqlite3_column_type(sqlite3_stmt*, int iCol);
sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol);

全检索:

全检索和条件检索类似,只是循环调用sqlite3_step,直到返回结果不是SQLITE_ROW。

-(NSMutableArray *)findAll{
NSString *writableDBPath = [self documentDirectoryFile];
if (sqlite3_open([writableDBPath UTF8String], &db) != SQLITE_OK) {
sqlite3_close(db);
NSAssert(NO, @"打开DB失败!");
}else{
sqlite3_stmt *statement;
NSString *sql = @"SELECT * FROM users";
if (sqlite3_prepare_v2(db, [sql UTF8String], -1, &statement, NULL) == SQLITE_OK) {
NSMutableArray *userList = [[NSMutableArray alloc] init];
while (sqlite3_step(statement) == SQLITE_ROW) {
UserInfo *userInfo = [[UserInfo alloc] init];
NSInteger userId= sqlite3_column_int(statement, 1);
userInfo.userId = userId;
char *userName= (char *)sqlite3_column_text(statement, 1);
userInfo.userName = [[NSString alloc] initWithUTF8String:userName];
char *age = (char *)sqlite3_column_text(statement, 2);
userInfo.age = [[NSString alloc] initWithUTF8String:age];
char *tel = (char *)sqlite3_column_text(statement, 3);
userInfo.tel = [[NSString alloc] initWithUTF8String:tel];
[userList addObject:userInfo];
}
return userList;
}
sqlite3_finalize(statement);
sqlite3_close(db);
}
return nil;
}

全检索操作,一般会涉及6个函数:(如果没有参数则不用sqlite3_bind_*)sqlite3_open→sqlite3_prepare_v2→sqlite3_step→sqlite3_column_*sqlite3_finalize→sqlite3_close。


注:sqlite3_close对应着sqlite3_open,sqlite3_finalize对应着sqlite3_prepare_v2,因此在出现过sqlite3_open和sqlite3_prepare_v2之后的各分支中确保最后一定会有对应的释放函数执行。


扩展:

现在有很多开源的对SQLite进行封装的第三方库,如果客户没有这方面要求可以使用,譬如FMDB,FMDB只有3个类如下,使用的时候,只需要把这六个文件添加到工程中即可使用。

1

已标记关键词 清除标记
相关推荐
<p> <span style="color:#4d4d4d;">当前课程中博客项目的实战源码是我在 GitHub上开源项目 My-Blog,目前已有 2000 多个 star:</span> </p> <p> <span style="color:#4d4d4d;"><img src="https://img-bss.csdnimg.cn/202103310649344285.png" alt="" /><br /> </span> </p> <p> <span style="color:#4d4d4d;">本课程是一个 Spring Boot 技术栈的实战类课程,课程共分为 3 大部分,前面两个部分为基础环境准备和相关概念介绍,第三个部分是 Spring Boot 个人博客项目功能的讲解,<span style="color:#565656;">通过本课程的学习,不仅仅让你掌握基本的 Spring Boot 开发能力以及 Spring Boot 项目的大部分开发使用场景,同时帮你提前甄别和处理掉将要遇到的技术难点,认真学完这个课程后,你将会对 Spring Boot 有更加深入而全面的了解,同时你也会得到一个大家都在使用的博客系统源码,你可以根据自己的需求和想法进行改造,也可以直接使用它来作为自己的个人网站,这个课程一定会给你带来巨大的收获。</span></span> </p> <p> <span style="color:#4d4d4d;"><span style="color:#565656;"> </span></span> </p> <p> <span style="color:#e53333;"><span style="color:#e53333;"><strong>课程特色</strong></span></span> </p> <p> <span style="color:#e53333;"><span style="color:#e53333;"><strong> </strong></span></span> </p> <p> <span style="color:#4d4d4d;"><span style="color:#565656;"> </span></span> </p> <ol> <li> <span style="color:#565656;">课程内容紧贴 Spring Boot 技术栈,涵盖大部分 Spring Boot 使用场景。</span> </li> <li> <span style="color:#565656;">开发教程详细完整、文档资源齐全、实验过程循序渐进简单明了。</span> </li> <li> <span style="color:#565656;">实践项目页面美观且实用,交互效果完美。</span> </li> <li> <span style="color:#565656;">包含从零搭建项目、以及完整的后台管理系统和博客展示系统两个系统的功能开发流程。</span> </li> <li> <span style="color:#565656;">技术栈新颖且知识点丰富,学习后可以提升大家对于知识的理解和掌握,对于提升你的市场竞争力有一定的帮助。</span> </li> </ol> <p> <strong>实战项目预览</strong> </p> <p> <span style="color:#4d4d4d;"><span style="color:#565656;"><span style="color:#e53333;"><strong> </strong></span></span></span> </p> <p> <span style="color:#4d4d4d;"><img src="https://img-bss.csdn.net/202005150303066258.png" alt="" /><br /> </span> </p> <p>   </p> <p> <span style="color:#4d4d4d;"> </span> </p> <p> <span style="color:#4d4d4d;"><img src="https://img-bss.csdn.net/202005150305396930.png" alt="" /><br /> </span> </p> <p> <span style="color:#4d4d4d;"> </span> </p> <p> <span style="color:#4d4d4d;"><img src="https://img-bss.csdn.net/202005150305528842.png" alt="" /><br /> </span> </p> <p> <span style="color:#4d4d4d;"> </span> </p> <p> <span style="color:#4d4d4d;"><img src="https://img-bss.csdn.net/202005150306056323.png" alt="" /><br /> </span> </p>
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页