iOS之Sqlite3封装.docx
《iOS之Sqlite3封装.docx》由会员分享,可在线阅读,更多相关《iOS之Sqlite3封装.docx(20页珍藏版)》请在冰豆网上搜索。
iOS之Sqlite3封装
iOS之Sqlite3封装
实例项目简介
这个实例主要是封装sqlite3数据库工具,并用这个工具实现记录、删除、修改、查询学生信息的功能,另外一个附属的功能就是根据学生的姓名首字母分组并添加索引。
对于数据库,我做了两层封装。
第一层是对数据库的基本操作进行抽象,第二层是对学生数据的操作进行抽象。
这种分层的思想不仅能使处于底层的功能得到多次重用,调高效率;而且这样做逻辑清晰,易于定位问题,有益于维护与跟新。
四、实例项目分析
1、首先抽象出数据库基本操作工具类,而数据库的基本操作有:
打开、关闭、建表、增、删、改、查……基于这些操作,我封装出如下五个方法来提供数据库的操作:
*打开数据库
*
*@parampath数据库路径
*@paramsuccess打开成功的回调
*@paramfalure打开失败的回调
*/
+(void)openDBPath:
(NSString*)pathsuccesefulBlock:
(void(^)(sqlite3*db))successandFailureBlock:
(void(^)(NSString*msg))failure
{
sqlite3*database=NULL;
intresult=sqlite3_open(path.UTF8String,&database);
if(result==SQLITE_OK){
if(success){
success(database);
}
}
else
{
if(failure){
constchar*msg=sqlite3_errmsg(database);
failure([NSStringstringWithUTF8String:
msg]);
}
if(database){
[selfcloseDB:
databasesuccesefulBlock:
nilandFailureBlock:
nil];
}
}
}
/**
*关闭数据库
*
*@paramdatabase数据库链接
*@paramsuccese成功的回调
*@paramfailure失败的回调
*/
+(void)closeDB:
(sqlite3*)databasesuccesefulBlock:
(void(^)())succeseandFailureBlock:
(void(^)(NSString*msg))failure
{
intresult=sqlite3_close(database);
if(result==SQLITE_OK){
if(succese){
succese();
}
}
else
{
if(failure){
failure([NSStringstringWithUTF8String:
sqlite3_errmsg(database)]);
}
}
}
/**
*执行SQL语句(不适应查询语句和blob(NSData)二进制数据类型的操作)
*
*@paramsqStrSQL语句
*@paramdatabase数据库链接
*@paramsuccese成功的回调
*@paramfailure失败的回调
*/
+(void)executeSql:
(NSString*)sqStrtoDatabase:
(sqlite3*)databasesuccesefulBlock:
(void(^)())succeseandFailureBlock:
(void(^)(NSString*msg))failure
{
DebugLog(@"%@",sqStr);
char*msg=NULL;
intresult=sqlite3_exec(database,sqStr.UTF8String,NULL,NULL,&msg);
if(result==SQLITE_OK){
if(succese){
succese();
}
}
else
{
if(failure){
failure([NSStringstringWithUTF8String:
msg]);
}
}
}
/**
*准备需要sqlite3_stmt结果集的SQL语句
*
*@paramsqStrSQL语句
*@paramdatabase数据库连接
*@paramsuccese成功的回调
*@paramfailure失败的回调
*/
+(void)prepareSql:
(NSString*)sqStrfromDatabase:
(sqlite3*)databasesuccesefulBlock:
(void(^)(sqlite3_stmt*stmt))succeseandFailureBlock:
(void(^)(NSString*msg))failure
{
DebugLog(@"%@",sqStr);
sqlite3_stmt*stmt=NULL;
intresult=sqlite3_prepare_v2(database,sqStr.UTF8String,-1,&stmt,NULL);
if(result==SQLITE_OK){
if(succese){
succese(stmt);
}
}
else
{
if(failure){
failure(@"SQL语句是非法的。
");
}
}
}
2、接着就是抽象学生数据功能类了。
根据需要我抽象出了如下五个方法
/**
*获取所有学生
*
*@paramcallBack回调
*/
+(void)getAllStudents:
(void(^)(NSArray*students,NSString*msg))callBack;
/**
*添加学生
*
*@paramstudentModel学生模型
*@paramsuccese添加成功回调
*@paramfailure添加失败回调
*/
+(void)addStudent:
(StudentModel*)studentModelsuccesefulBlock:
(void(^)(StudentModel*studentModel))succeseandFailureBlock:
(void(^)(NSString*msg))failure;
/**
*更新学生
*
*@paramstudentModel学生模型
*@paramsuccese更新成功回调
*@paramfailure更新失败回调
*/
+(void)updateStudent:
(StudentModel*)studentModelsuccesefulBlock:
(void(^)(StudentModel*studentModel))succeseandFailureBlock:
(void(^)(NSString*msg))failure;
/**
*按条件搜索学生
*
*@paramcondition条件
*@paramcallBack搜索回调
*/
+(void)searchStudents:
(NSString*)conditionandCallBack:
(void(^)(NSArray*students,NSString*msg))callBack;
/**
*删除学生
*
*@paramstudentModel学生模型
*@paramsuccese删除成功回调
*@paramfailure删除失败回调
*/
+(void)deleteStudent:
(StudentModel*)studentModelsuccesefulBlock:
(void(^)(StudentModel*studentModel))succeseandFailureBlock:
(void(^)(NSString*msg))failure;
细节分析
-由于该功能类都是使用类方法,所以不能以实例变量来存储数据,因此声明全局变量staticsqlite3*database来存储数据库的链接,并以static关键字修饰来避免其他源文件对其访问。
-类方法initialize是在手动调用累中任何方法前调用一次,所以在这个方法中打开数据库并创建学生表是非常合适的。
/**
*在手动调用类里的任何方法前自动调用一次
*/
+(void)initialize
{
[SqliteDBAccessopenDBPath:
SqlitePathStrsuccesefulBlock:
^(sqlite3*db){
DebugLog(@"数据库打开成功!
");
[SqliteDBAccessexecuteSql:
[NSStringstringWithFormat:
@"CREATETABLEIFNOTEXISTS%@(identifierintegerPRIMARYKEYAUTOINCREMENT,nametextNOTNULL,studentNumbertextNOTNULLUNIQUE,photoblob,ageintegerNOTNULL,addresstext,describetext);",StudentTableName]toDatabase:
dbsuccesefulBlock:
^{
database=db;
DebugLog(@"学生表创建成功!
");
}andFailureBlock:
^(NSString*msg){
DebugLog(@"学生表创建失败,%@",msg);
}];
}andFailureBlock:
^(NSString*msg){
DebugLog(@"数据库打开失败,%@",msg);
}];
}
在学生数据功能类的内部可以封装一个不公开的方法来从sqlite3_stmt结果集中获取学生模型对象
/**
*从sqlite3_stmt中获取学生数据
*
*@paramstmtsqlite3_stmt结果集
*
*@return学生数组
*/
+(NSArray*)getStudentsFromStatement:
(sqlite3_stmt*)stmt
{
NSMutableArray*students=[NSMutableArrayarrayWithCapacity:
1];
StudentModel*studentModel;
while(sqlite3_step(stmt)==SQLITE_ROW){
studentModel=[[StudentModelalloc]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?
[NSStringstringWithUTF8String:
name]:
nil;
studentModel.studentNumber=studentNumber?
[NSStringstringWithUTF8String:
studentNumber]:
nil;
studentModel.photo=photo?
[UIImageimageWithData:
[NSDatadataWithBytes:
photolength:
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?
[NSStringstringWithUTF8String:
address]:
nil;
studentModel.describe=describe?
[NSStringstringWithUTF8String:
describe]:
nil;
[studentsaddObject:
studentModel];
}
returnstudents;
}
由于表中存在二进制数据,所以插入跟新二进制数据时使用sqlite3_exec函数执行SQL语句是有问题的,需要使用sqlite3_step函数来执行SQL语句。
/**
*更新学生
*
*@paramstudentModel学生模型
*@paramsuccese更新成功回调
*@paramfailure更新失败回调
*/
+(void)updateStudent:
(StudentModel*)studentModelsuccesefulBlock:
(void(^)(StudentModel*studentModel))succeseandFailureBlock:
(void(^)(NSString*msg))failure
{
[SqliteDBAccessprepareSql:
[NSStringstringWithFormat:
@"UPDATE%@SETname=?
studentNumber=?
photo=?
age=?
address=?
describe=?
WHEREidentifier=?
;",StudentTableName]fromDatabase:
databasesuccesefulBlock:
^(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,[databytes],(int)[datalength],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([NSStringstringWithFormat:
@"更新学生失败,%@",[NSStringstringWithUTF8String:
sqlite3_errmsg(database)]]);
}
}
//在遍历完结果集后,调用sqlite3_finalize以释放和预编译的语句相关的资源。
sqlite3_finalize(stmt);
}andFailureBlock:
^(NSString*msg){
if(failure){
failure([NSStringstringWithFormat:
@"更新学生失败,%@",msg]);
}
}];
}
五、问题与补充
1.sqlite3事务:
是并发控制的基本单位。
所谓的事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。
例如,银行转账工作:
从一个账号扣款并使另一个账号增款,这两个操作要么都执行,要么都不执行。
所以,应该把它们看成一个事务。
事务是数据库维护数据一致性的单位,在每个事务结束时,都能保持数据一致性。
针对上面的描述可以看出,事务的提出主要是为了解决并发情况下保持数据一致性的问题。
事务的语句:
-开始事物:
BEGINTRANSACTION
-提交事物:
COMMITTRANSACTION
-回滚事务:
ROLLBACKTRANSACTION
项目中添加学生数据的时候,并没有设置identifer主键,但是主键是有用的,在插入成功后需要查出来,因此添加学生就涉及两步操作:
插入数据,查询数据。
而且这两个操作必须要同时成功才能算是学生添加成功。
所以在这里,我就开启一个事务,先插入学生数据,再查询此学生数据,只要有一个步骤发生错误就回滚事务,全部成功则提交事务:
/**
*添加学生
*
*@paramstudentModel学生模型
*@paramsuccese添加成功回调
*@paramfailure添加失败回调
*/
+(void)addStudent:
(StudentModel*)studentModelsuccesefulBlock:
(void(^)(StudentModel*studentModel))succeseandFailureBlock:
(void(^)(NSString*msg))failure
{
//开启事务
[SqliteDBAccessexecuteSql:
@"BEGINTRANSACTION"toDatabase:
databasesuccesefulBlock:
^{
DebugLog(@"事务启动成功!
");
[SqliteDBAccessprepareSql:
[NSStringstringWithFormat:
@"INSERTINTO%@(name,studentNumber,photo,age,address,describe)VALUES(?
?
?
?
?
?
);",StudentTableName]fromDatabase:
databasesuccesefulBlock:
^(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,[databytes],(int)[datalength],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){
[SqliteDBAccessprepareSql:
[NSStringstringWithFormat:
@"SELECT*FROM%@WHEREstudentNumber=?
;",StudentTableName]fromDatabase:
databasesuccesefulBlock:
^(sqlite3_stmt*stmt){
sqlite3_bind_text(stmt,1,studentModel.studentNumber.UTF8String,-1,NULL);
StudentModel*model=[[selfgetStudentsFromStatement:
stmt]firstObject];
if(studentModel){
studentModel.identifier=model.identifier;
if(succese){
succese(studentModel);
}
//提交事务
[SqliteDBAccessexecuteSql:
@"COMMITTRANSACTION"toDatabase:
databasesuccesefulBlock:
^{
DebugLog(@"提交事务成功!
");
}andFailureBlock:
^(NSString*msg){
DebugLog(@"提交事务失败:
%@",msg);
}];
}
else
{
//回滚事务
[SqliteDBAccessexecuteSql:
@"ROLLBACKTRANSACTION"toDatabase:
databasesuccesefulBlock:
^{
DebugLog(@"回滚成功!
");
}andFailureBlock:
^(NSString*msg){
DebugLog(@"回滚失败:
%@",msg);
}];
}
//在遍历完结果集后,调用sqlite3_finalize以释放和预编译的语句相关的资源。
sqlite3_finalize(stmt);
}andFailureBlock:
^(