正常情况下,当我们需要进行数据库存储操作时,需要进行以下操作:
1、书写数据库语句创建表
CREATE TABLE IF NOT EXISTS TableName
2、确定数据库中需要插入字段,然后书写数据库语句进行插入操作
INSERT INTO TableName (name1, name2,...) Values (?, ?,...)
真正的项目中我们需要做的操作可能会更多,比如需要判断表是否存在、要插入的字段是否存在等一系列判断,本来好好写代码逻辑的人怎么就被拉去写数据库语句出不来了(心疼自己几分钟)!
当然,一个完整的项目里面不可能只有这些操作,应该包括数据库的增(Insert)、删(delete)、改(update)、查(select)四大操作,数据库查找还包括条件筛选语句,如果你一行一行的写完这些语句,会发现整个人都不好了,难道就没有一个更好地解决办法吗?
我们应该都对数据模型化不陌生,各种数据转模型的第三方库如MJExtension、YYModel应有尽有,我们只需要一行代码即可完成数据模型化的操作,比如MJExtension中的数组转模型:
NSArray *userArray = [MJUser mj_objectArrayWithKeyValuesArray:dictArray];
得到的就是一个模型数组,想象一下数据库的所有操作也都用一行代码来完成是什么感觉?
//Insert
for (LYTeacher *teacher in self.teachers) {
[LYFMDBTool ly_insertDBWithModel:teacher category:FMDB_TEACHER];
}
//Delete
[LYFMDBTool ly_deleteDBWithModel:teacher
category:FMDB_TEACHER
propertyName:@"teacherId"];
//Update
LYTeacher *teacher = self.teachers[arc4random_uniform(20)];
teacher.name = [teacher.name stringByAppendingString:@"updated"];
BOOL success = [LYFMDBTool ly_updateDBWithModel:teacher
category:FMDB_TEACHER
propertyName:@"teacherId"];
//Select
NSArray *result = [LYFMDBTool ly_selectFromDBWithTableName:NSStringFromClass([LYTeacher class]) category:FMDB_TEACHER];
//Filter Select
//按照降序查找数据库中10条数据
NSString *filter = @"ORDER BY teacherId DESC LIMIT 10";
NSArray *result = [LYFMDBTool ly_selectFromDBWithTableName:NSStringFromClass([LYTeacher class]) category:FMDB_TEACHER filter:filter];
基于模型对象的数据库增、删、改、查操作已经完成,而且我们并没有书写一句SQL语句,并且每当你为模型对象增加属性字段后执行插入或者更新操作,数据库中都会增加对应的字段。
这其中的魔法操作便是runtime,我们通过运行时动态获取模型对象的所有属性字段将其存到数组中,然后再对每个属性进行字符串拼接动态生成数据库语句,最后执行对应的数据库语句即可,下面是对应的实现代码
/**
插入模型到指定表中
@param model 模型名称(必须是模型名称)
@param tableName 表名
@param category 表后缀名(用来区别以同一个模型创建的不同表名)
*/
+ (BOOL)ly_insertDBWithModel:(NSObject *)model tableName:(NSString *)tableName category:(NSString *)category {
if (!model) { return NO; }
//动态拼接数据库表名,这里由模型名、表名和表名后缀组成一个真正的数据库表名
NSString *className = NSStringFromClass(object_getClass(model));
className = (tableName == nil) ? className : tableName;
__block BOOL successed = YES;
//判断对应表名的数据库是否创建成功
if (![self ly_createTableName:className category:category]) {
successed = NO;
return successed;
}
tableName = [className stringByAppendingString:category];
//获取模型的所有属性
NSArray *propertyArr = [self ly_getPropertyList:className];
//获取允许插入数据库中的属性
NSArray *allowedProperties = [model.class ly_FMDBAllowedPropertyNames];
//拼接数据库语句
NSString *sql1 = [NSString stringWithFormat:@"INSERT INTO %@%@ ",className,category];
__block NSMutableString *keyStr = [NSMutableString string];
__block NSMutableString *valueStr = [NSMutableString string];
__block NSMutableArray *argumentsArr = [NSMutableArray array];
for (int i = 0; i < propertyArr.count; i++) {
__block MJProperty *property = propertyArr[i];
__block MJPropertyType *type = property.type;
if (![allowedProperties containsObject:property.name]) {
continue;
}
[_queue inDatabase:^(FMDatabase *db) {
//如果对应的属性字段在表中不存在,则在表中增加对应字段
if (![db columnExists:property.name inTableWithName:tableName]) {
NSString *alterStr = [NSString stringWithFormat:@"ALTER TABLE %@ ADD %@ TEXT",tableName,property.name];
BOOL added = [db executeUpdate:alterStr];
if (!added) {
successed = NO;
}
}
NSUInteger length = keyStr.length;
(!length)?[keyStr appendFormat:@"%@",property.name]
:[keyStr appendFormat:@",%@",property.name];
(!length)?[valueStr appendString:@"?"]
:[valueStr appendString:@",?"];
id value = [model valueForKey:property.name];
if (value) {
//由于数据库不支持数组、字典、模型对象的直接存储,因此我们将数组、字典、模型对象转json字符串存储
if ([self _isKeyTypeValidateJSON:type]) {
NSString *jsonStr = [self _switchValue:value toJSON:type];
[argumentsArr addObject:jsonStr ?:@""];
} else {
[argumentsArr addObject:value];
}
} else {
[argumentsArr addObject:@""];
}
}];
if (!successed) {
return successed;
}
}
NSString *sql = [NSString stringWithFormat:@"%@(%@) VALUES (%@)",sql1,keyStr,valueStr];
[_queue inDatabase:^(FMDatabase *db) {
if (![db executeUpdate:sql withArgumentsInArray:argumentsArr]) {
successed = NO;
}
}];
return successed;
}
数据库的更新和删除操作也是这个原理,这里我们采取了MJExtension的处理办法,默认为NSObject对象增加了一个LYFMDBKeyValue协议,我们继承自NSObject的类可以声明需要插入到数据库的属性字段,默认会取所有的属性进行插入操作
@protocol LYFMDBKeyValue <NSObject>
@optional
/**
允许存储到数据库的属性
*/
+ (nonnull NSArray *)ly_FMDBAllowedPropertyNames;
@end
@interface NSObject (LYFMDBKeyValue) <LYFMDBKeyValue>
@end
@implementation NSObject (LYFMDBKeyValue)
static NSMutableArray *_allowedProperties;
+ (NSArray *)ly_FMDBAllowedPropertyNames {
if (_allowedProperties.count) {
return _allowedProperties;
}
NSMutableArray *propertyList = [LYFMDBTool ly_getPropertyList:NSStringFromClass([self class])];
if (!_allowedProperties) {
_allowedProperties = [NSMutableArray array];
}
for (MJProperty *property in propertyList) {
[_allowedProperties addObject:property.name];
}
return _allowedProperties;
}
@end
以上就是所有的实现原理,附上github链接地址:https://github.com/LustySwimmer/LYFMDBManager
说明:
1、该工具基于FMDB进行二次封装操作;
2、数据模型化采用的是MJExtension进行转换处理;
3、支持cocoapod导入
4、数据模型要有一个唯一值得属性字段,可以使用时间作区分,也可以是其他不同的数字或者字符串
注意点:
不支持NSUInteger类型存储,最好使用NSString进行存储操作;
在使用过程中暂时没有发现其他问题,如果有发现问题,欢迎提issue,如果有更好的建议和方法,欢迎指教!
网友评论