当前位置: 首页 > 编程日记 > 正文

从C#到Objective-C,循序渐进学习苹果开发(7)--使用FMDB对Sqlite数据库进行操作

本随笔系列主要介绍从一个Windows平台从事C#开发到Mac平台苹果开发的一系列感想和体验历程,本系列文章是在起步阶段逐步积累的,希望带给大家更好,更真实的转换历程体验。本篇主要开始介绍基于XCode进行IOS程序的开发,介绍使用FMDB对Sqlite数据库进行操作,以及对数据库操作类进行抽象设计,以期达到重用、简化、高效开发的目的。

在.NET领域开发了很多年,一般常见的项目都需要操作数据库,包括有Oracle、SqlServer、Mysql、Sqlite、Access等数据库,这些数据库是很常见的,我们在.NET环境里面开发的各种系统,可能都或多或少需要和其中一种以上的数据库打交道,这也是我致力于提炼我的.NET领域的Winform开发框架、Web开发框架、混合式开发框架的目的,尽可能达到简化、重用、高效开发的目的。

虽然现在在IOS领域做一些研究开发,即使IOS设备更多强调的是一个多媒体的设备,但是数据库的操作还是必不可少,因此我先从我熟悉的数据库这块入手,了解其中数据库是如何操作的,有哪些现成的组件进行参考学习等等。

在IOS里面开发,提起和数据库打交道,可能很多人都熟悉FMDB这个数据库的组件,它对IOS里面操作Sqlite数据库进行了很大程度的简化,简化后,我们大多数情况下,只需要和FMDatabase和FMResultSet打交道即可,使用起来非常方便。

1、FMDB的操作

为了较好介绍整体性的内容,我们先从FMDB的各种操作进行介绍。

1)数据库打开操作

FMDatabase *db= [FMDatabase databaseWithPath:dbPath] ;  
if (![db open]) {  
NSLog(@"无法打开数据库");  
return ;  
}  

2)数据库操作executeUpdate

使用FMDB,对于没有返回记录的操作,都可以用executeUpdate进行操作,如下是删除记录的函数

- (BOOL) deleteByCondition:(NSString *) condition {NSString *query = [NSString stringWithFormat:@"Delete FROM %@ Where %@ ", self.tableName, condition];BOOL result = [self.database executeUpdate:query];return result;
}

当然,对于SQLite很多数据库操作,我们可以使用参数化语句进行操作,如下例子所示

[db executeUpdate:@"INSERT INTO User (Name,Age) VALUES (?,?)",@"张三",[NSNumber numberWithInt:30]]  

参数化也可以使用 : 字符作为参数标识,如下所示,是我封装的一个数据库操作函数

- (BOOL) deleteById:(id) key
{NSDictionary *argsDict = [NSDictionary dictionaryWithObjectsAndKeys:key, @"id", nil];NSString *query = [NSString stringWithFormat:@"Delete FROM %@ Where %@ =:id", self.tableName, self.primaryKey];BOOL result = [self.database executeUpdate:query withParameterDictionary:argsDict];return result;
}

3)返回集合的操作

操作有返回集合的语句,我们就需要用到FMResultSet对象了,这个对象类似于以前见过的游标,不过不一样的东西而已。

FMResultSet *rs=[db executeQuery:@"SELECT * FROM User"];
rs=[db executeQuery:@"SELECT * FROM User WHERE Age = ?",@"20"];
while ([rs next]){
NSLog(@"%@ %@",[rs stringForColumn:@"Name"],[rs stringForColumn:@"Age"]);
}

上面的代码操作,返回一个Resultset集合进行遍历使用,我们可以根据Resultset的一些方法获取到不同的数据内容。

相对于FMResult的操作方式,原生态的Sqlite数据库操作代码如下所示。看完是不是感觉使用FMDB类库操作数据库方便很多呢。

 NSString *sqlQuery = @"SELECT * FROM User";sqlite3_stmt * statement;if (sqlite3_prepare_v2(db, [sqlQuery UTF8String], -1, &statement, nil) == SQLITE_OK) {while (sqlite3_step(statement) == SQLITE_ROW) {char *name = (char*)sqlite3_column_text(statement, 1);NSString *nsNameStr = [[NSString alloc]initWithUTF8String:name];int age = sqlite3_column_int(statement, 2);char *address = (char*)sqlite3_column_text(statement, 3);NSString *nsAddressStr = [[NSString alloc]initWithUTF8String:address];NSLog(@"name:%@  age:%d  address:%@",nsNameStr,age, nsAddressStr);}}sqlite3_close(db);

FMResultSet方法有下面几种,分别用于获取不同的数据:

  • intForColumn:
  • longForColumn:
  • longLongIntForColumn:
  • boolForColumn:
  • doubleForColumn:
  • stringForColumn:
  • dateForColumn:
  • dataForColumn:
  • dataNoCopyForColumn:
  • UTF8StringForColumnIndex:
  • objectForColumn:

2、数据库操作层的设计

上面小节介绍了使用FMDB类库对Sqlite数据库的操作,使我们大致了解了在IOS里面对数据库操作的过程。但是单纯如果介绍这些,我觉得太泛泛了,而且对我们使用起来也还是很不方便,很多时候,我总是想通过设计的方式,来简化我的各种操作处理。如我们知道,IOS里面的Objective C也提供了很多高级语言都有的属性,如对象继承,接口、多态等等。

我很早之前写过的随笔《Winform开发框架之数据访问层的设计》 ,介绍了我在.NET领域里面的数据库访问层的设计,由于这种设计,能很大程度上减少代码量,并提高开发效率,因此,我也想再IOS里面形成这样的数据访问设计,虽然IOS里面可能主要是使用单机版的数据库,如SQLite数据库,所以我想简化一些.NET里面的设计模型。

在.NET里面,我的框架分层主要如下所示,这种框架的设计模式,已经很好应用在了我的Winform开发框架、WCF及混合式开发框架、Web框架里面。它们常见的分层模式,可以分为UI层、BLL层、DAL层、IDAL层、Entity层、公用类库层等等

而其中的DAL层的设计,示意图如下所示,DAL层主要是通过继承自BaseDAL基类(如BaseDALSQL)进行, BaseDALSQL进行更高一层的抽象,已达到更好的应用目的。

而我们在IOS里面,则可以主要考虑SQLite数据库即可,因此,我把设计通过简化,构造下面的设计模型

在项目里面,数据访问层的文件如下所示(为了演示测试方便,使用User表进行操作)。

为了实现数据的承载,我们需要把表的数据转换为实体类进行显示和操作,因此实体类的设计也需要考虑好。由于数据访问层的基类,封装了大多数的数据操作,也包括返回的数据对象和集合,因此数据访问层的基类,也涉及到了数据类型返回的问题。

由于IOS里面的Objective C里面没有泛型这样的东西,因此有两种方式可以来实现基类实体类的处理:一是使用动态类型id类型作为实体类类型,一种是使用一种半类型化的类型(实体类的基类)作为对象,我倾向于使用后者,因为毕竟比较接近真实的类型了。

//
//  BaseEntity.h
//  MyDatabaseProject
//
//  Created by 伍华聪 on 14-4-2.
//  Copyright (c) 2014年 伍华聪. All rights reserved.
//

#import <Foundation/Foundation.h>@interface BaseEntity : NSObject@end
//
//  UserInfo.h
//  MyDatabaseProject
//
//  Created by 伍华聪 on 14-3-30.
//  Copyright (c) 2014年 伍华聪. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "BaseEntity.h"@interface UserInfo : BaseEntity@property(nonatomic, copy) NSString *ID;
@property(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *fullName;@end

这种继承模式和.NET的继承关系差不多了。

对于数据访问层的设计代码,它就是在数据访问基类的定义如下,基类使用了FMDB的数据访问类进行操作的,里面很多操作的接口就是模仿我在.NET领域里面的数据访问层的设计。

//
//  BaseDAL.h
//  MyDatabaseProject
//
//  Created by 伍华聪 on 14-3-30.
//  Copyright (c) 2014年 伍华聪. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "FMDatabase.h"
#import "BaseEntity.h"@interface BaseDAL : NSObject
{NSString *pathToDatabase;
}#pragma 数据库相关属性@property (nonatomic, strong) NSString *pathToDatabase;
@property (nonatomic, readonly) FMDatabase *database;  // 数据库操作对象
@property (nonatomic, strong) NSString *tableName;//表名称
@property (nonatomic, strong) NSString *primaryKey;//主键
@property (nonatomic, strong) NSString *sortField;//排序,默认为主键
@property (nonatomic, assign, getter=isDescending) BOOL descending;//是否降序查询//将DataReader的属性值转化为实体类的属性值,返回实体类(子类必须重写)
-(id) rsToEntity:(FMResultSet *)rs;//将实体对象的属性值转化为字典列表对应的键值(子类必须重写)
-(NSDictionary *) dictByEntity:(BaseEntity *) info;#pragma 基础操作接口//根据指定对象的ID,从数据库中删除指定对象
- (BOOL) deleteById:(id) key;//根据指定条件,从数据库中删除指定对象
- (BOOL) deleteByCondition:(NSString *) condition;//更新对象属性到数据库中
-(BOOL) update:(BaseEntity *) info byKey:(id) key;//插入指定对象到数据库中
-(BOOL) insert:(BaseEntity *) info;//插入指定对象到数据库中,并返回最后插入的ID
-(NSInteger) insert2:(BaseEntity *) info;//查询数据库,检查是否存在指定ID的对象
- (BaseEntity *) findById:(id) key;//根据条件查询数据库,如果存在返回第一个对象
-(BaseEntity *) findSingle:(NSString *) condition;//根据条件查询数据库,如果存在返回第一个对象
-(BaseEntity *) findSingle:(NSString *) condition orderBy:(NSString *) orderBy;//根据条件查询数据库,并返回对象集合
- (NSArray *) find:(NSString *) condition;//根据条件查询数据库,并返回对象集合
- (NSArray *) find:(NSString *) condition orderBy:(NSString *) orderBy;//获取表的全部数据
- (NSArray *) getAll;//获取表的全部数据
- (NSArray *) getAll:(NSString *) orderBy;//获取某字段数据字典列表
-(NSArray *) getFieldList:(NSString *) fieldName;//获取表的所有记录数量
-(int) getRecordCount;//根据条件,获取表查询的记录数量
-(int) getRecordCount:(NSString *) condition;//根据条件,判断是否存在记录
-(BOOL) isExistRecord:(NSString *)condition;//查询数据库,检查是否存在指定键值的对象
-(BOOL) isExist:(NSString *)fieldName value:(id) value;//根据主键和字段名称,获取对应字段的内容
-(NSString *) getFieldValue:(NSString *)key fieldName:(NSString *)fieldName;//执行SQL查询语句,返回查询结果的所有记录的第一个字段,用逗号分隔。
-(NSString *) sqlValueList:(NSString *)query;#pragma 数据库初始化函数及关闭操作//根据SQLite数据库地址初始化数据库
-(id) initWithPath:(NSString *)filePath;//根据SQLite数据库名称初始化数据库
-(id) initWithFileName:(NSString *)fileName;// 关闭连接
-(void) close;@end

和我的.NET框架里面的数据访问层设计一样,数据访问基类已经封装了大多数的数据访问操作,因此各个表的数据访问对象,它的代码就可以很简洁了。从上面的基类接口设计可以看到,里面一些实体类返回函数或者列表返回函数,都使用了BaseEntity作为对象,我们具体在起子类使用的时候,把它返回的对象再一次转换即可。对于数据库访问基类,我们以一个返回集合的接口实现来分析。

- (NSArray *) find:(NSString *) condition orderBy:(NSString *) orderBy {NSString *query = [NSString stringWithFormat:@"SELECT * FROM %@ ", self.tableName];if (condition != nil) {query = [query stringByAppendingFormat:@" where %@ ", condition];}if (orderBy != nil) {query = [query stringByAppendingString:orderBy];}else {query = [query stringByAppendingFormat:@" ORDER BY %@ %@ ", self.sortField, self.isDescending ? @"DESC" : @"ASC"];}FMResultSet *rs = [self.database executeQuery:query];NSMutableArray *array = [NSMutableArray arrayWithCapacity:[rs columnCount]];BaseEntity *info = nil; //默认初始化为空while ([rs next]) {info = [self rsToEntity:rs];[array addObject:info];}[rs close];return array;
}

上面代码使用了参数化的SQL语句进行查询,并且,对返回的数据库的ResultSet进行转换为实体类。

info = [self rsToEntity:rs];

由于基类封装了大多数的数据库操作函数,因此数据访问层的具体表的实现类,可以很简洁,但是已经具备了常见的CRUD操作,以及一些分页查询等复杂的数据操作功能。

//
//  UserDAL.h
//  MyDatabaseProject
//
//  Created by 伍华聪 on 14-3-30.
//  Copyright (c) 2014年 伍华聪. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "FMDatabase.h"
#import "BaseDAL.h"
#import "UserInfo.h"@interface UserDAL : BaseDAL
{
}//单例模式
+(UserDAL *) defaultDAL;@end

基于篇幅的原因,我将在下一篇介绍如何在界面层中使用这样的数据访问设计类,先放上一些测试程序的界面截图。

 

本文转自博客园伍华聪的博客,原文链接:从C#到Objective-C,循序渐进学习苹果开发(7)--使用FMDB对Sqlite数据库进行操作,如需转载请自行联系原博主。




相关文章:

nginx做方向代理不显示图片的问题

在nginx的配置文件中加上 location ~ \.(jpg|png|jpeg|bmp|gif|swf|css)$ { access_log off; expires 30d; root /www/htdocs/market; break; }

Linux系统挂载ntfs分区

Linux系统挂载ntfs分区 http://www.2cto.com/os/201404/297079.htmlposted on 2015-02-21 22:20 雪山看雪 阅读(...) 评论(...) 编辑 收藏 转载于:https://www.cnblogs.com/zker/p/4297223.html

谷歌新深度学习系统可以促进放射科医生的发展

编译 | 禾木木 出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09; 谷歌人工智能研究人员团队在《自然》上发表了一篇新论文&#xff0c;深度学习可以检测出异常胸部 X 光片&#xff0c;其准确度可与专业放射科医生相媲美。 深度学习系统可以帮助放射科医师优先考虑胸部…

【AngularJS】—— 12 独立作用域

独立作用域的作用 为了便于理解&#xff0c;先看一下下面这个例子&#xff1a; <!doctype html> <html ng-app"myApp"><head><meta http-equiv"Content-Type" content"text/html; charsetutf-8" /><script src"…

nginx虚拟目录设置 alias 和 root

nginx貌似没有虚拟目录的说法&#xff0c;因为它本来就是完完全全根据目录来设计并工作的。 如果非要给nginx安上一个虚拟目录的说法&#xff0c;那就只有alias标签比较“像”&#xff0c;干脆来说说alias标签和root标签的区别吧。 最基本的区别&#xff1a;alias指定的目录是…

避免死锁的一些注意事项

1. 避免嵌套锁&#xff0c; 如果每个线程都只占有一个锁&#xff0c; 则可以很大程度上避免死锁。其死锁的情况是&#xff0c; 线程 1 依次获得 A 对象和 B 对象的锁&#xff0c; 然后决定等另一个线程的信号再继续&#xff0c; 从而先释放了 B 对象的的锁。可是线程 2 需要同时…

这是一个好问题:既然机器可以学习,它们能忘掉吗?

编译 | 禾木木 出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09; 很多公司都使用机器学习来分析人们的欲望、厌恶或面孔。研究人员提出了一个不同的问题&#xff1a;我们如何让机器忘记学习&#xff1f; 机器学习正在寻找如何在人工智能软件中诱发选择性失忆的方法。目…

python tar.gz格式压缩、解压

压缩 代码 import tarfile import os def tar(fname):t tarfile.open(fname ".tar.gz", "w:gz")for root, dir, files in os.walk(fname):print root, dir, filesfor file in files:fullpath os.path.join(root, file)t.add(fullpath)t.close()if __nam…

bzoj1251: 序列终结者 (splay)

splay可以用于维护序列&#xff0c;比如noi的维修序列&#xff0c;比如这道 发现当时splay没写总结&#xff0c;也没题解 然后重新写splay竟然耗了一个晚上 结果是因为max【0】没有附最小值&#xff01;&#xff01;血一样的教训 最后祭出inline大法才过&#xff0c;我的splay真…

模型神器组合,yyds!

作者 | 东哥起飞来源 | Python数据科学最近在kaggle上有一个调参神器非常热门&#xff0c;在top方案中频频出现&#xff0c;它就是OPTUNA。知道很多小伙伴苦恼于漫长的调参时间里&#xff0c;这次结合一些自己的经验&#xff0c;给大家带来一个LGBM模型OPTUNA调参的使用教程&am…

理解http响应头中的Date和Age

Date&#xff1a;Date头域表示消息发送的时间&#xff0c;时间的描述格式由rfc822定义。例如&#xff0c;Date: Mon, 04 Jul 2011 05:53:36 GMT。 Age&#xff1a;当代理服务器用自己缓存的实体去响应请求时&#xff0c;用该头部表明该实体从产生到现在经过多长时间了。 比如访…

linux 保留内核中sas驱动的加载导致crash问题

[rootlocalhost ~]# uname -a Linux localhost.localdomain 3.10.0-693.5.2.el7.x86_64 问题描述&#xff0c;在crash的时候&#xff0c;小内核因为分配中断号失败而触发panic&#xff0c;打印如下&#xff1a;&#xff08;备注&#xff1a;本文大内核就是指正常运行的内核&am…

四层和七层负载均衡的区别

负载均衡设备也常被称为"四到七层交换机"&#xff0c;那补充&#xff1a;所谓四层就是基于IP端口的负载均衡&#xff1b;七层就是基于URL等应用层信息的负载均衡&#xff1b;同理&#xff0c;还有基于MAC地址的二层负载均衡和基于IP地址的三层负载均衡。换句换说&…

关于数据库,你可能最想知道的几件事

【CSDN 编者按】随着技术不断更新&#xff0c;数据库的发展可谓全面开花&#xff0c;也吸引了越来越多人的关注&#xff0c;但大家真的都足够了解数据库吗&#xff1f;作者 | 易璜珵 责编 | 侯淼淼出品 | 《新程序员》互联网飞速发展的时代里&#xff0c;数据库、中间件和…

Visual C++ 2012/2013的内存溢出检測工具

在过去&#xff0c;每次编写C/C程序的时候&#xff0c;VLD差点儿是我的标配。有了它&#xff0c;就能够放心地敲代码&#xff0c;随时发现内存溢出。 VLD最高可支持到Visual Studio 2012。不知道以后会不会支持Visual Studio 2013&#xff0c;但反正眼下是不支持的。 相关的讨论…

.NetCore Docker

转载于:https://blog.51cto.com/linhongquan/2047736

集生态之力跨城市数字化之难题,英特尔交上了一份完美答卷

随着数字孪生、人工智能、大数据、云计算、区块链等新兴技术的发展成熟&#xff0c;社会正加大步伐向数字化时代迈进。城市&#xff0c;作为社会民生与经济发展的重要载体&#xff0c;自然站在了数字化建设历程的第一线。当然&#xff0c;数字化城市建设并不是搭建“空中楼阁”…

设置Squid Cache_mem大小

squid代理服务器一般的Unix,Linux都自带。我使用的是CentOS 5.3,Squid是自已编译的。 Squid 默认 cache_mem 100 16 256 打开/etc/squid/squid.conf 配置 $vi /etc/squid/squid.conf #http_port ,是代理的端口&#xff0c;如果没有其他的http服务占用80端口或8080&#xf…

centos iptables关于ping

配置iptables策略后&#xff0c;一般来说INPUT都是DROP然后配置需要通过的 当执行&#xff1a; iptables -P INPUT DROP 后&#xff0c;机器就不能被ping通了&#xff01; 因为icmp没有添加到规则中&#xff01; 于是我执行如下代码&#xff1a; iptables -A INPUT -p icmp -j …

禁止蒙层底部页面跟随滚动

场景概述 弹窗是一种常见的交互方式&#xff0c;而蒙层是弹窗必不可少的元素&#xff0c;用于隔断页面与弹窗区块&#xff0c;暂时阻断页面的交互。但是&#xff0c;在蒙层元素中滑动的时候&#xff0c;滑到内容的尽头时&#xff0c;再继续滑动&#xff0c;蒙层底部的页面会开始…

squid日志文件太大,怎样处理?

Squid 默认的&#xff15;天会压缩一次, 在 /etc/logrotate.d/squid中有设置。如果你修改了日志的位置, 请修改 /etc/logrotate.d/squid /home/log/squid/access.log { weekly rotate 5 copytruncate compress notifempty missingok } /home…

安卓系列七(广播机制)

2019独角兽企业重金招聘Python工程师标准>>> 一、什么是广播接收者 广播接收者&#xff08;BroadcastReceiver&#xff09;用于接收广播Intent&#xff0c;广播Intent的发送是通过调用Context.sendBroadcast()、Context.sendOrderedBroadcast()来实现的。通常一个广…

第九代小冰惊喜登场,多端融合且琴棋书画样样精通

谈及智能助手&#xff0c;相信大家都不会漏过小冰这款具有划时代意义的产品。从最初的微软小冰到现在的第九代小冰&#xff0c;AI的技术在不断的演进&#xff0c;而小冰也从最初的贴心助手变成了如今琴棋书画样样精通的人工智能前沿技术载体。 北京时间2021年9月22日&#xff…

C++对象赋值的四种方式

1. 引用作为参数的方式传递. 1 GetObject(Object& obj) 2 { 3 obj.value value1; 4 } 特点: 在外部构造一个对象. 把该对象以引用的方式传递到函数中. 从而实现对该对象的改变, 该参数实质是一个[out]类型的参数, 而非[in]类型的参数. 这里的引用可以称为别名. 点评: …

金九银十,不要跳槽!

前言:又到了求职的金九银十的黄金月份&#xff0c;我相信有不少小伙伴已经摩拳擦掌的准备寻找下一份工作。就目前国内的面试模式来讲&#xff0c;在面试前积极的准备面试&#xff0c;复习整个 Java 知识体系将变得非常重要&#xff0c;可以很负责任地说一句&#xff0c;复习准备…

FreeMarker标签介绍

FreeMarker标签使用 一、FreeMarker模板文件主要有4个部分组成 1、文本&#xff0c;直接输出的部分 2、注释&#xff0c;即<#--...-->格式不会输出 3、插值&#xff08;Interpolation&#xff09;&#xff1a;即${..}或者#{..}格式的部分,将使用数据模型中的部分替代输…

让Squid 显示本地时间

Squid的Error messages 默认的时间显示的GMT时间&#xff0c;而非本地时间&#xff0c;这个有时候看着很别扭。 下面是修改方法&#xff0c;找到Squid的源文件src/errorpage.c 大概在60多行&#xff0c; { ERR_SQUID_SIGNATURE, "\n<BR clear\"all\">\n&…

linux mysql 命令 大全

linux mysql 命令 大全 1.linux下启动mysql的命令&#xff1a; mysqladmin start /ect/init.d/mysql start (前面为mysql的安装路径) 2.linux下重启mysql的命令&#xff1a; mysqladmin restart /ect/init.d/mysql restart (前面为mysql的安装路径) 3.linux下关闭mysql的…

助力5G行业应用扬帆启航,第二届5G毫米波产业高峰论坛圆满召开

当前&#xff0c;5G发展如火如荼&#xff0c;成为引领我国高质量发展的新引擎。5G要想进一步实现向千行百业拓展&#xff0c;离不开全频段的支持&#xff0c;推动5G毫米波发展成为各国共识。为进一步推进5G毫米波产业发展&#xff0c;释放5G全部潜能&#xff0c;助力5G行业应用…

Bootstrap3.x - 源代码分析

参照http://v3.bootcss.com/css/ 文档与源代码colors 比较全面定义总结有意义的颜色。所有uI要用的颜色&#xff0c;都先从已定义的读&#xff0c;这样保证样式的同一性&#xff0c;而且方便以后开发主题库。(建议想自己写css模块的&#xff0c;可以参考一下bootstrap里颜色定义…