美文网首页iOS Mark复制粘贴深入浅出iOS
[iOS]基于ORM思想的数据库处理

[iOS]基于ORM思想的数据库处理

作者: 生活很艰辛 | 来源:发表于2016-11-08 18:54 被阅读2696次

    本文主要是介绍iOS上数据库存储方案,利用ORM对FMDB做一层封装,让代码更加简洁方便。

    一、数据库开发的蛋疼问题

    有了FMDB对sqlite 的封装,iOS端的数据库操作相当方便,我们再也不用面对那些磨人的线程问题和蛋疼的sqlite。但是在实际开发中仍无法避免的遇到一些蛋疼问题。

    - (void)createPaintTable{
    
      [self.db executeUpdate:@"CREATE TABLE paint_table (paintID varchar(32),  
          answercount int, create_timestamp double, last_modified_time double, 
          imgwidth int, imgheight int, imgurl varchar(256), last_reply_user_id 
          varchar(32), text varchar(512), money int, questype int, replytype int,     
          user_id varchar(32), viewcount int, commentcount int, supportcount int, 
          author_id varchar(32), replyAuthor_id varchar(32), accepted_user_id 
          varchar(32))"];
    
          [self checkError];
     }
    

    这是简单的创建一张表,我想你看到这样的代码都要发疯。然而更让人发疯的是某天因为产品的需求我们需要增加一个字段,那我们不得不在在各个地方改啊改。而一旦因为疏忽数据库升级没有处理好,就可能造成很严重的线上事故。(很遗憾我就遇到了这种事故)

    二、如何解决这种问题

    2.1 Key-Value式的存储 - YTKKeyValueStore

    iOS客户端的数据表往往还要对应一个Model,这导致在使用时还要加层Model转化,而移动端对于数据库的查询要求并不高,针对这些特点Key-Value式的存储应运而生。比较代表性的框架有 猿题库的 YTKKeyValueStore(主要思路是将数据模型进行序列化做为Value存储)。但是这类框架的缺陷也相当明显,只能通过主Key查询。而且如果模型发生变化,数据升级处理也只能放在 模型转换时处理。Key-Value式的存储实际上已经不能称之为数据库存储了,它是根据客户端的特殊需求而产生的一种特殊产物,数据库对它而言只是一种容器。

    2.2 ORM框架-GYDataCenter

    ORM(Object Relational Mapping)框架采用元数据来描述对象一关系映射细节,元数据一般采用XML格式,并且存放在专门的对象一映射文件中。只要提供了持久化类与表的映射关系,ORM框架在运行时就能参照映射文件的信息,把对象持久化到数据库中。当前ORM框架主要有四种:Hibernate(Nhibernate),iBATIS,mybatis,EclipseLink。
    以上是百度百科对ORM框架的解释,大家注意我标注的两个关键词,运行时以及对象,iOS客户端的数据库存储本质就是要将一个对象存储到数据库,而OC也正是一种运行时语言,我们要做的就是建立一个映射文件,在运行时参照映射文件产生对应sql语句将对象保存到数据库。

    MLeaksFinder这个框架我想不少同学都不陌生,它采用了一种很巧妙的方式,让我们在DEBUG模式下可以轻松的发现内存泄漏问题。今天我逛作者 Zepo 的gitHub时发现他又写了另外一个框架GYDataCenter我简单的看了一下它正是采用ORM思想写的一款iOS客户端的数据库存储方案,与我的想法不谋而合。

    我并没有看GYDataCenter的源码,只是根据使用方式提出一些问题。GYDataCenter的 映射关系 是通过GYModelObject提供相应方法来提供,而使用必需继承GYModelObject实现对应方法。也就是说作者将 映射关系 与 对象模型 合在了GYModelObject类,造成了耦合。这种耦合会造成一些问题,在项目中 模型使用的地方太多,模型转换,view展示,数据处理都要涉及到模型将 映射关系 加到模型中很容易导致模型的臃肿。而继承关系的使用也使得入侵性太强。所以说 映射关系 应该独立于对象 模型存在。

    三、JYDatabase(铺垫了那么多这才是我要讲的......)

    JYDatabase 这是本人基于ORM思想写的一款数据库应用框架,和GYDataCenter一样也是基于FMDB实现的,下面主要讲一下框架的实现和使用。

    3.1 要解决的问题

        1.自动处理数据库升级,让使用者不用考虑数据库升级带来烦劳。
        2.封装简单常用的查询语句,让使用者只用关注特殊的SQL查询,不用被蛋疼的sql语句折磨。
    

    3.2 数据表的建立(映射关系的建立)

    数据表的建立需要继承 JYContentTable(该类实现了工作中用到的大部分SQL查询),只要重写以下几个方法就可以快速创建一张数据表。

     // 必须实现 contentClass 是该表所对应的模型类,tableName 是表的名字
      - (void)configTableName{             
         self.contentClass = [JYPersonInfo class];
         self.tableName = @"JYPersonTable";
      }
      
      // 必须实现 contentId 是该表的主键(也是唯一索引)比如用户的userId 必须是 contentClass 的属性
      - (NSString *)contentId{
          return @"personnumber";
      }
    
      // 数据表的其他字段,必须是 contentClass 的属性,如不实现则默认取 contentClass 以“DB”结尾 的属性
      - (NSArray<NSString *> *)getContentField{
          return @[@"mutableString1",@"integer1",@"uInteger1",@"int1",@"bool1",@"double1"];
      }
    
      // 表创建时对应字段的默认长度,如不写,取默认。
      - (NSDictionary*)fieldLenght{
          return @[@"mutableString1":@"512"];
      }
      
      // 查询是否使用NSCache缓存,默认YES。
      - (BOOL)enableCache{
          return NO;
      }
      
      注意:1.数据表映射的属性支持 NSString  NSMutableString  NSInteger NSUInteger int BOOL double float NSData 等数据类型,具体如下:
     NSDictionary * jy_correspondingDic(){
        return @{@"Tb":@"BOOL",
                 @"TB":@"BOOL",
                 @"Tc":@"BOOL",
                 @"TC":@"BOOL",
                 @"Td":@"DOUBLE",
                 @"TD":@"DOUBLE",
                 @"Tf":@"FLOAT",
                 @"TF":@"INTEGER",
                 @"Ti":@"INTEGER",
                 @"TI":@"INTEGER",
                 @"Tq":@"INTEGER",
                 @"TQ":@"INTEGER",
                 @"T@\"NSMutableString\"":@"VARCHAR",
                 @"T@\"NSString\"":@"VARCHAR",
                 @"T@\"NSData\"":@"BLOB",
                 @"T@\"UIImage\"":@"BLOB",
                 @"T@\"NSNumber\"":@"BLOB",
                 @"T@\"NSDictionary\"":@"BLOB",
                 @"T@\"NSMutableDictionary\"":@"BLOB",
                 @"T@\"NSMutableArray\"":@"BLOB",
                 @"T@\"NSArray\"":@"BLOB",};
    }
    
      
      2.NSCache的默认缓存条数是20条,可自行设置修改self.cache.countLimit = 20; 使用enableCache 将优先从缓存中取数据
      如自行实现的查询请在适当情况下使用以下三个方法来加入缓存。方法内部有 enableCache 的实现。
      - (id)getCacheContentID:(NSString *)aID;
      - (void)saveCacheContent:(id)aContent;
      - (void)removeCacheContentID:(NSString *)aID;
    

    3.3数据库的创建和升级管理

    3.3.1数据库的创建和升级管理类需要继承JYDataBase
    关键方法:
    // 该方法会根据当前版本判断 是创建数据库表还是 数据表升级
    - (void)buildWithPath:(NSString *)aPath mode:(ArtDatabaseMode)aMode;
    
    // 返回当前数据库版本,只要数据表有修改 返回版本号请 +1 默认返回 1
    - (NSInteger)getCurrentDBVersion{
        return 4;
    }
    
    // 所有数据表的创建请在该方法实现  调用固定方法 - (void)createTable:(FMDatabase *)aDB;
    - (void)createAllTable:(FMDatabase *)aDB{
        [self.personTable createTable:aDB];
    }
    
    // 所有数据表的升级请在该方法实现  调用固定方法 - (void)insertDefaultData:(FMDatabase *)aDb;
    - (void)updateDB:(FMDatabase *)aDB{
        [self.personTable updateDB:aDB];
    }
    
    3.3.2数据库升级的实现- (void)updateDB:(FMDatabase *)aDB
    对于每一张表,所谓的修改无非是 添加了一个 新的字段 或减少了几个字段(这种情况很少)。
    1. 字段的对比
        PRAGMA table_info([tableName]) 可以获取一张表的所有字段
        再与当前需要的字段做对比即可得到要增加的字段和减少的字段
    2. 字段的添加
        sqlite有提供对应的SQL语句实现
        ALTER TABLE tableName ADD 字段 type(lenght)
    3. 减少字段
        sqlite并未提供对应的SQL语句实现但可通过以下方法实现
        a.根据原表新建一个表
        sql = [NSString stringWithFormat:@"create table %@ as select %@%@ from %@", tempTableName,[self contentId],tableField,self.tableName];
        b.删除原表
        sql = [NSString stringWithFormat:@"drop table if exists %@", self.tableName];
        c.将表改名
        sql = [NSString stringWithFormat:@"alter table %@ rename to %@",tempTableName ,self.tableName];
        d.为新表添加唯一索引
        sql = [NSString stringWithFormat:@"create unique index '%@_key' on  %@(%@)", self.tableName,self.tableName,[self contentId]];
    

    3.4条件查询的实现

    关于复杂查询我提供了一个简单的方法 
    - (NSArray *)getContentByConditions:(void (^)(JYQueryConditions *make))block;
    可以看下它的使用
    NSArray*infos = [[JYDBService shared] getPersonInfoByConditions:^(JYQueryConditions *make) {
        make.field(@"personnumber").greaterThanOrEqualTo(@"12345620");
        make.field(@"bool1").equalTo(@"1");
        make.field(@"personnumber").lessTo(@"12345630");
        make.asc(@"bool1").desc(@"int1");
    }];
    其实它不过产生了如下一条查询语句:
    SELECT * FROM JYPersonTable WHERE personnumber >= 12345620 AND bool1 = 1 AND personnumber < 12345630  order by  bool1 asc , int1 desc 
    
    make.field(@"personnumber").greaterThanOrEqualTo(@"12345620"); 就代表了 personnumber >= 12345620
    
    实现实际相当简单,先用 JYQueryConditions 记录下所描描述的参数,最后再拼接出完整的sql语句。
    至于通过点语法的链式调用则参考了 Masonry 的声明方式
    - (JYQueryConditions * (^)(NSString *compare))equalTo;
    - (JYQueryConditions * (^)(NSString *field))field{
    return ^id(NSString *field) {
        NSMutableDictionary *dicM = [[NSMutableDictionary alloc] init];
        dicM[kField] = field;
        [self.conditions addObject:dicM];
        return self;
    };}
    

    四、加入多表关联的存储功能(1.0.0)

    4.1 版本更新记录如下:

    1.增加了多表关联功能,ModelA 包含 ModelB 的存入取出也可用简单的配置解决。
    2.删除了UIImage的数据库存储支持。
    3.删除了内存缓存支持,这是一个很鸡肋无用的处理。需要可以自行在外部加上自己的缓存机制。
    4.DEMO做了大的修改,删除毫无意义的稀烂界面。写了一个测试代码 JYContentTableTest.m
    5.对代码生成工具 JYGenerationCode 做了简单修改。
    

    4.2多表关联使用说明

      DEMO中写了三模型,JYGradeInfo (年级),JYClassInfo (班级),JYPersonInfo(人)
    他们之间的关系是,一个年级有多个班级,一个班级 有一个老师 和 多个学生。对此建立了三张
    表JYGradeTable(年级表),JYClassTable(班级表),JYPersonTable(人表)。
    
      多表关联采用主外键形式:JYClassTable中的 gradeID 字段就是对应JYGradeTable的外
    键.JYPersonTable 中的teacherClassID 对应 JYGradeTable的老师外键studentClassID
    对应 JYGradeTable 的学生外键。
    
      1.外键功能的实现:我在直接使用SQL的外键功能时遇到了一些问题(有空会再回头看看),这理是
    使用简单粗暴的形势实现的级联删除。在进行条件删除条件查询时会比较耗时。(也不是很糟糕
    哈),有空我会有sql外键形式看看做下对比。
      2.关联表设置参考DEMO 按如下设置即可
      - (NSDictionary<NSString *, NSDictionary *> *)associativeTableField{
        
        JYPersonTable *table = [JYDBService shared].personDB.personTable;
        // tableSortKey 不设置查询时会以主key做升序放入数组
        return @{
                 @"teacher" : @{
                                 tableContentObject : table,
                                 tableViceKey       : @"teacherClassID"
                               },
                 @"students" : @{
                                 tableContentObject : table,
                                 tableViceKey       : @"studentClassID",
                                 tableSortKey       : @"studentIdx"
                               }
                 };
        }
      3.为外键字段加上索引提高查询效率
      // 为 gradeID 加上索引
      - (void)addOtherOperationForTable:(FMDatabase *)aDB{
        [self addDB:aDB uniques:@[@"gradeID"]];
      }
    

    五、结尾

    关于JYDatabase的使用和介绍 大家可以去gitHub查看更详细的说明
    https://github.com/weijingyunIOS/JYDatabase,关于上面介绍的框架大家可以根据实际需要做下对比后选择使用。
    如果你喜欢我的文章请点个赞,顺便去gitHub给个小星星,谢谢!

    六、延伸

    刚看了一篇文章走进Realm的世界貌似很强大。看看整整也许更好。

    相关文章

      网友评论

      • 孙掌门:两点:第一:个人建议,你弄个YYModel,或者MJEx都可以,自动解析模型字段,然后处理,查到数据库,既然做成一个SDK,就不可能然用户去把模型里面的每一个字段都告诉你吧,对不,这样不好用,这个是最大的建议,你可以考虑一下
        第二:我最喜欢也是我最想做的,点语法,链式做成ORM,很完美,这点很棒,刚看了几眼你的demo,看到第一点的时候,就感觉不太合理,就评论了一下,哈哈,我再往下看看。
        孙掌门:@生活很艰辛 我觉得你写反了,runtime默认解析所有字段存到数据库,然后给个方法,排除不存的字段应该就可以了,你现在的方法是给个方法,传存的字段,而大部分场景下,server给下发的数据大部分都需要存,很少的字段不存,怎么说呢,各有利弊,这个没办法评论,也不好做到满足每个人的需求
        孙掌门:@生活很艰辛 有时间我会看看你的代码,有什么我觉得不合理的或者好的,或者什么的 ,我会跟你说,共勉
        生活很艰辛:1.这个主要看自己吧,模型并不一定所有的字段都存数据库。我之前也想过加个db后缀表示入库字段,后来感觉不太好看就没做。要实现的话自己 把 getContentField 方法重写下用 runtime获取属性列表就好了。
        2.点语法,链式调用,在实用中才发现我有些地方并没写太好,又要兼容以前代码,也基本成了个半废的功能。
        但基本思路都在这里可以自己玩玩
      • 28bb64fffadd:您好,我想问下,使用这个封装如何把网络图片的 url 地址转化后存到数据库里面呢?我之前是直接在数据请求成功后,将装有model 的数组(model 的 image 是网络连接)直接给了插入数据库的方法,所有数据库的字段 image 其实还是一个 url 地址...
        28bb64fffadd:@云宝宝 好的,已解决,麻烦了
        生活很艰辛:@鲫鱼博博 sdwebImage yywebImage 都有缓存的啊,你数据库存url就可以了啊。
      • 28bb64fffadd:问下你这 oc 版的里面如何给表添加索引,如何把数据添加到对应的表明名以及对应索引下
        生活很艰辛:@鲫鱼博博 具体你参考下 JYGradeTable JYClassTable JYPersonTable 表创建即可
        生活很艰辛:@鲫鱼博博 - (void)addOtherOperationForTable:(FMDatabase *)aDB;重写该方法,可添加额外的操作(如加索引)升级和创建都会调用。 索引添加也有简单方法。 - (void)addDB:(FMDatabase *)aDB uniques:(NSArray<NSString *>*)indexs;
        - (void)addDB:(FMDatabase *)aDB type:(EJYDataBaseIndex)aType uniques:(NSArray<NSString *>*)indexs;
        生活很艰辛:@鲫鱼博博 你可以看下gitHub的说明,Demo更加全面,JYContentTable头文件描述也比较清晰。
      • KyneChen:不错,实用,谢谢楼主分享
        KyneChen:@云宝宝 ok
        生活很艰辛:@KyneChen github给个小星星,谢谢:pray:
      • xiaoliang1:mark
        生活很艰辛:@xiaoliang1 一脸萌比
      • 爃少:不错``一般做过后台的会比较了解ORM...前端应用不多..
      • LeonYin:好东西,给个赞
      • kelio:好东西,必须给赞
      • kalen5241:希望能持续更新
      • e2f2d779c022:好东西不怕来的晚, 谢谢楼主.

      本文标题:[iOS]基于ORM思想的数据库处理

      本文链接:https://www.haomeiwen.com/subject/uhtzuttx.html