这其实是很久以前的东西,以前没有git or blog的习惯,现在就扔上来,就当给自己留个念想,里面好多东西其实也都没啥用了,尤其是现在realm越来越火,又好用,有方便,有高效
之前学习分析大神全套框架源码后的学习产出
BeeFramework
郭大神的超级有名的一整套APP开发框架,涵盖内容非常丰富,非常全面,这里不一一介绍了,里面每个模块每个细节都像宝库一样有超多东西值得学习
由于当初想着重去看看数据库相关的内容,所以重点拆解了BeeFramework得DB模块,由于Bee内部各个子模块之间,有Bee独有的一套signal信息流系统,以数据流式的思想贯穿了自上而下整个框架的每个角落,所以他的数据库模块,非常精妙,但是想要拿出来单独使用是由很大代价的。
于是当初才有了独立,剥离,整理Bee的数据库子模块,从学习的角度,拆解学习bee中的orm代码,去掉MRC改为ARC(当初学得时候bee还是mrc),拆解学习bee的sql工具,而且从外围接口设计上,加入了一些更灵活的接口控制,并且略微扩展了一下,数据表相互关联的orm的case
十分感谢大神的开源项目
针对FMDB,写的一套ORM和SQL辅助工具
这是一个基于FMDB的数据库框架,FMDB在使用上支持多个数据库管理,同步,异步数据库,线程安全等,但是FMDB在工作原理上,输入参数必须是sql语句,这对使用者的数据库相关知识了解程度有很高要求。
在FMDB的使用上,必须将sql语句字符串无差错的传入FMDB最终返回字典查询结果。
在FMDB之上进行封装了,采用了强大的Objective-C运行时控制,能够勿须iOS开发人员懂数据库知识,动态的对任何业务模型,自动生成create , query,insert,update语句并进行执行,并且能将fmdb返回的字典表数据,动态的自动反解称为任何业务模型对象,勿须任何开发操作
由一套强大的动态运行时适配模块,和sql语句生成模块组成
动态运行时适配模块:任意自定义的业务模型通过运行时转化成字典表数据,并且可以由db查询出来的字典表数据反向转化回任意自定义业务模型,这个过程借助VKFMDBHelper的框架纯自动完成,iOS开发人员在这块,不需要进行任何额外的开发
sql语句生成模块组成:通过自动转化出来的字典表数据,全自动的生成create , query,insert,update等SQL代码,iOS开发人员可以完全不懂数据库,也能完成数据库操作。如果开发人员精通SQL,还支持自行书写复杂高效sql语句,直接执行,更加灵活
pod 'FMDB'
VKDBManager.h
即可使用DBManager VKDBModel
即可自动享有动态匹配,自动生成sql的功能 创建一个需要进行数据库管理的对象模型,继承自 VKDBModel
,比如这里创建一个叫做person的类,他具有firstName,lastName,age,personId四个属性,
@interface person : VKDBModel
@property (nonatomic,strong) NSString* personId;
@property (nonatomic,strong) NSString* firstName;
@property (nonatomic,strong) NSString* lastName;
@property (nonatomic,assign) NSInteger age;
@end
这样这个person简简单单的类,就自动拥有了运行时动态匹配表数据,自动生成sql等能力
如何使用后面细说
VKDBModel会自动创建一个隐含的自增主键属性,属性名为dataBaseId
如果用户不指定主键的话,数据库框架依然能正常工作
用户指定主键,需要在person类里重写 uniqueKeyName
方法,指定personId为用户主键,方便后期查找
+(NSString *)uniqueKeyName
{
return @"personId";
}
打开数据库,如果使用 openDefault
方法会自动以app bundlename当做数据库名,创建并打开
[[VKDBManager defaultManager] openDefault]
VKDBManager
本身是单例,支持同时打开关闭多个数据库,并且切换当前使用中的数据库,具体参见相关接口文档
每一个 VKDBModel
的模型,在使用前都需要进行mapping,通过运行时建立好属性的映射表,才能在后面进行运行时转换以及sql自动生成
每一个 VKDBModel
的模型,都要先在数据库里成功的创建好表,如果表已经建好重复调用没事
[K12StudyKeyPointDBModel buildTable];//数据库建表 or 标记表已建好
创建一个person对象,对象赋值,然后执行 saveToDB
自动生成这个person对象写入数据库所需要的sql语句
自动在打开的数据库中执行这条sql语句
最终实现了这个person对象入库的流程
person* p = [[person alloc]init];
p.personId = @"1";
p.firstName = @"defu";
p.lastName = @"wang";
p.age = 28;
[p saveToDB];
如果一个person对象,已经其用户主键已经有值,调用 refresh
方法,就会自动从数据库中读取该主键键值下的数据,并且赋值刷新给person
自动生成 这个person对象 这个主键-键值的读取sql语句
自动将数据库中读取出来的表结构,运行时匹配赋值给person
person* p = [[person alloc]init];
p.personId = @"1";
[p refresh];
获取person表下所有对象,或者获取person表下某个范围的对象合集
自动生成query的sql语句
从数据库中取出大量表数据
一键自动映射到对象上
NSArray* result1 = [person allObjectData]; //查询person表所有数据 NSArray* result2 = [person objectDataWhereKeyName:@"lastName" Opertor:@"=" Value:@"wang"]; //查询person表 lastname = wang的所有数据 NSArray* result3 = [person objectDataWhereKeyName:@"age" Opertor:@"<" Value:@"50"]; //查询person表 age < 50 的所有数据 //所有返回的数据都是数组 不是person对象 NSMutableArray* objectArr = [[NSMutableArray alloc]init]; for (NSDictionary* itemdic in result1) { person* p = [[person alloc]initWithDictionary:itemdic]; //一键自动将数组转化为对象 [objectArr addObject:p]; }
借助 VK_SqlGenerator
可以无需懂sql语法,根据行为和语义生成sql语句
每一个操作都是可以生成一条sql语句字符串string,从而交个 VKDBManager
来执行
每次查找数据库返回的都是字典,不是对象
字典可以借助运行时匹配,一键转化为对象
saveToDB
refresh
都是内部封装好了,先生成sql,再执行,再转化对象赋值
VK_SqlGenerator
采用了时下流行的链式语法,在语义的表述下,流畅的生成了sql语句
.Table
.From
等为生成器开始头,多用from,table为创建表的使用使用,已经封装在 buildTable
方法里
.Where
.Limit
.Set
等为生成器中部,用于执行筛选,或者设置字段,并且这些语句可以重复执行,set多次或者筛选多个条件
.Get
.Update
.Insert
.Create
等为生成器尾部,执行了这个方法,最终会根据前边执行过的路径生成最终的sql语句,并返回
数据库SQL语句生成样例
NSString* getSql = VK_SqlGenerator.FROM([person tableName]).WHERE_OPERATOR(@"age",@"<",@"28").WHERE_OPERATOR(@"lastName",@"=",@"wang").GET();
这是一条2个组合筛选条件的查找
NSString* sql = VK_SqlGenerator.FROM(self.tableName).WHERE_OPERATOR(@"course",@"=",course).DELETE();
这是一条单筛选条件的删除
VK_SqlGenerator .FROM( [person tableName] ) .WHERE( @"personId", @"1" ); for ( 属性运行时mapping循环 ) { VK_SqlGenerator.SET( name, value ); } NSString* updateSql = VK_SqlGenerator.UPDATE();
这是一条有筛选条件的数据更新
循环赋值部分为伪代码,已经被封装在saveToDB里
如果开发者有数据库的知识和能力,可以自行书写复杂sql语句,直接交给 VKDBManager
来执行
尤其是数据库升级 alternate等复杂操作
目前还没对数据库迁移 升级 做好完善强大的语句生成器
NSString* updateSql = @"INSERT INTO tablename SET column_name1 = value1, column_name2 = value2";
//手写sql
[VKDBManager doUpdateSQL:sql];
//如果写的是写入语句 执行doUpdate
[VKDBManager doQuerySql:sql];
//如果写的是查询语句 执行doQuery
因为查询接口返回的数据,都是字典结构,而不是对象,所以需要一次转化
person* p = [[person alloc]initWithDictionary:itemdic];//一键自动将数组转化为对象
生成sql
, 执行
, 字典映射
,这三个步骤是VKommonX DataBase的核心,灵活的拆解,变化,运用,可以实现多变的扩展
FMDB支持传入一组sql语句,由其内部异步队列完成,最后通过block回调结果
VKFMDBHelper将其封装,所有sql语句,可以直接执行,也可以传入mannager的sql队列管理器,进行一次统一的异步执行
处理异步的回调,VKFMDBHelper封装了一套事件注册机制,每一次异步操作都可以在注册后在主线程监听其回复
注册异步数据库监听事件,所有异步的数据返回
都走监听事件返回,为了区别不同事件,每个事件在提交的时候可以指定tag,根据不同tag来区别处理返回数据
[[VKDBManager defaultManageVKr]addDataBaseNotificationAtTarget:self withComplete:^(NSString *tag, BOOL succuse, NSArray *result) { if ([tag isEqualToString:@"event"]) { //可以识别提交任务的tag NSLog(@"ccccc"); } }];
[VKDBManager beginBackgroundUpdate]; [VKDBManager beginBackgroundQuery];
//将生成好的sql放入对应队列
[VKDBManager addUpdateTaskSQL:sql];
[VKDBManager addQueryTaskSQL:sql];
//使用VKDBModel的封装方法加入队列
person* p;
[p saveInBackground];
[p deleteInBackground];
//将自动生成的sql加入update队列 暂不执行
VKDBManager
来提交sql队列中的未执行sql语句,提交的时候可以带上tag名,如果没有带则使用默认tag defaultUpdate
or defaultQuery
[VKDBManager commitBackgroundUpdateWithTaskTag:@"savetask"];//提交tag为savetask
[VKDBManager commitBackgroundUpdate];//提交tag为默认tag
VKSQLQueueManager
sql队列管理器包含两个队列, update``query
两个队列,这两个队列各不相干,
[[VKDBManager defaultManager]addDataBaseNotificationAtTarget:self withComplete:^(NSString *tag, BOOL succuse, NSArray *result) { if ([tag isEqualToString:@"savetask"]) { } }]; [VKDBManager beginBackgroundUpdate]; NSLog(@"aaa time ==="); for (NSInteger i = 300; i < 4000; i++) { K12KeyPointVisitDBModel * model = [[K12KeyPointVisitDBModel alloc]init]; model.kpid = [NSString stringWithFormat:@"adfadf%@",@(i)]; model.deltaCount = 100; [model saveInBackground]; //循环将sql string 添加到队列 } [VKDBManager commitBackgroundUpdateWithTaskTag:@"savetask"];
VKDBModel
中含有另一个 VKDBModel
新建一个dog的 VKDBModel
对象
@interface dog : VKDBModel
@property (nonatomic,strong)NSString* dogId;
@property (nonatomic,strong) NSString* dogName;
@end
改写person对象
@interface person : VKDBModel
@property (nonatomic,strong)NSString* personId;
@property (nonatomic,strong) NSString* firstName;
@property (nonatomic,strong) NSString* lastName;
@property (nonatomic,assign) NSInteger age;
@property (nonatomic,strong) dog* pet;
@end
对person建表的时候,dog字段会当做string, 将dog类的唯一key存入数据库(dog必须重写uniqueKey,否则会报断言错误)
对person对象进行save的时候,会将person的pet属性,生成一条dog的sql并执行,也生成一条person的sql并执行
对person对象进行saveInBackground的时候,会直接生成2条sql语句,进入sql队列
数据读取,无论是通过refresh方式,还是通过字典转化,person的dog属性,都只有uniqueKey有值,其他为空
refreshInnerDBModel
,可以补全dog对象的数值 //从person的表中查找出来的字典结果 转化为对象
person* p = [[person alloc]initWithDictionary:itemdic];
//person直接refresh出来的结果
person *p =[[person alloc]init];
p.personId = @"1";
[p refresh];
//以上两个结果 p 的dog属性对象,都只有dogId有数值,dog的其他属性如dogname 为空
[p refreshInnerDBModel];
//p 的dog属性补全
VKDBModel
中含有一个数组,数组内均为另一个 VKDBModel
改写person对象
@interface person : VKDBModel
@property (nonatomic,strong)NSString* personId;
@property (nonatomic,strong) NSString* firstName;
@property (nonatomic,strong) NSString* lastName;
@property (nonatomic,assign) NSInteger age;
@property (nonatomic,strong) NSArray* pets;
@end
对person建表的时候,pets字段会当做string
将pets数组里的所有dog对象的唯一key,序列化转为字符串,存入数据库(dog必须重写uniqueKey,否则会报断言错误)
对person对象进行save的时候,会将person的pets属性,生成一组dog的sql并执行,也生成一条person的sql并执行
对person对象进行saveInBackground的时候,会直接生成N+1条sql语句,进入sql队列
数据读取,无论是通过refresh方式,还是通过字典转化,pet数组里的dog对象的属性,都只有uniqueKey有值,其他为空
refreshInnerDBModel
,可以补全dog对象的数值 //从person的表中查找出来的字典结果 转化为对象
person* p = [[person alloc]initWithDictionary:itemdic];
//person直接refresh出来的结果
person *p =[[person alloc]init];
p.personId = @"1";
[p refresh];
//以上两个结果 p 的dog属性对象,都只有dogId有数值,dog的其他属性如dogname 为空
[p refreshInnerDBModel];
//p 的dog数组所有对象属性补全
VKFMDBHelper的数据库模块并没有支持很强大的数据内联的对象之间,关联查找的功能,只具备最基本的内联形式。
可以从业务数据库表结构入手,将表结构关联性简化,从而使用VKFMDBHelperVK的数据库
如果业务需求需要更高级的数据内联模式,或者现有功能无法满足需求,可以选择手写sql语句,业务逻辑自行赋值的方法,完成更复杂的功能
VKFMDBHelperVK的数据内联,可能还不稳定,后续在持续更新中
VKDBModel 只支持 NSNumber(nsinterge cgfloat) NSDate NSArray NSString
NSArray 中只有 NSString NSNumber
如果出现不支持的属性字段会断言报错
内联对象没指定uniqueKey会断言报错
NSDictionary出现在属性会报错(不支持,以后可以考虑扩展,字典内只有NSString NSNumber 才有效)
所有异步操作的接口,必须在主线程调用,在线程调用会断言报错,因为内部会统一线程管理
-(instancetype)initWithDictionary:(NSDictionary*)dic;
字典自动转化为 VKDBModel
对象
+(NSString*)uniqueKeyName;
重载,指定唯一uniqueKey
+(BOOL)buildTable;
建表,主线程,可重复执行
+(BOOL)clearTable;
清空表,主线程
-(BOOL)exsit;
对象的uniqueKey在数据库中是否存在
-(BOOL)refresh;
使用对象的uniqueKey,从数据库中查找,赋值给对象
-(void)refreshInnerDBModel;
补全对象内联对象的其他属性
-(void)saveInBackground;
生成保存sqlstring,放入队列
-(void)saveToDB;
生成保存sqlstring,直接执行
-(void)deleteFromDB;
生成删除sql,直接执行
-(void)deleteInBackground;
生成删除sql,放入队列
+(NSArray*)allObjectData;
生成查找全体sql,直接执行
+(void)allObjectDataQuerryInBackground;
生成查找全体sql,放入队列
+(NSArray*)objectDataWhereKeyName:(NSString*)key Opertor:(NSString*)opera Value:(NSString*)value;
生成条件查找sql,直接执行
+(void)objectDataQuerryInBackgroundWhereKeyName:(NSString*)key Opertor:(NSString*)opera Value:(NSString*)value;
生成条件查找sql,放入队列
- (instancetype)sharedInstance;
+ (instancetype)defaultManager;
单例功能
-(BOOL)openDefault;
打开app bundleID为名的数据库
-(BOOL)closeDefault;
关闭app bundleID为名的数据库
-(BOOL)openDataBaseWithName:(NSString*)name;
打开指定名称数据库
-(BOOL)closeDataBaseWithName:(NSString*)name;
关闭指定名称数据库
-(VKDataBase*)currentDataBase;
当前操作的数据库
-(void)changeCurrentDataBase:(NSString*)name;
切换当前操作的数据库
-(void)changeCurrentDataBaseDefault;
切换当前操作的数据库为默认数据库
-(void)addDataBaseNotificationAtTarget:(id)target withComplete:(VKDataBaseQueueBlockReturn)block;
异步数据库注册监听,以注册对象的名称注册
-(void)removeDataBaseNotificationAtTarget:(id)target withComplete:(VKDataBaseQueueBlockReturn)block;
异步数据库移除监听,以注册对象的名称注册
-(void)addDataBaseNotificationAtTarget:(id)target withIdentifier:(NSString*)idstr withComplete:(VKDataBaseQueueBlockReturn)block;
异步数据库注册监听,以自定义名称注册
-(void)removeDataBaseNotificationAtTarget:(id)target withIdentifier:(NSString*)idstr withComplete:(VKDataBaseQueueBlockReturn)block;
异步数据库移除监听,以自定义名称注册
+(NSInteger)lastInsertRowId;
当前操作数据库上条插入的RowId
+(void)addUpdateTaskSQL:(NSString*)sql;
sql语句加入写队列
+(void)addQueryTaskSQL:(NSString*)sql;
sql语句加入读队列
+(BOOL)doUpdateSQL:(NSString*)sql;
执行写语句
+(NSArray*)doQuerySql:(NSString*)sql;
执行读语句
+(void)beginBackgroundQuery;
开始后台查找,清空队列
+(void)beginBackgroundUpdate;
开始后台写入,清空队列
+(void)commitBackgroundQuery;
提交查询,使用默认tag,失败默认不回滚
+(void)commitBackgroundQueryWithTaskTag:(NSString*)tag;
提交查询,使用tag,失败默认不会滚
+(void)commitBackgroundQueryWithTaskTag:(NSString*)tag errorRollback:(BOOL)enablerollback;
提交查询,使用tag,
+(void)commitBackgroundUpdate;
提交写入,使用默认tag,失败不回滚
+(void)commitBackgroundUpdateWithTaskTag:(NSString*)tag;
提交写入,使用tag,失败不回滚
+(void)commitBackgroundUpdateWithTaskTag:(NSString*)tag errorRollback:(BOOL)enablerollback;
提交写入,使用tag,