WCDB的基本使用

前言

WCDB是微信移动端团队开源的移动端数据库组件,提供了一个高效、完整、易用的移动端存储方案。第一次应用到WCDB还是在现公司的工程中,由于现在的team成员主要来自鹅厂,在工程中应用到前东家的东西也是理所当然,这同时也充分说明了WCDB的易用性,不好用谁会继续再使用它呢?本文主要是对WCDB做简单的介绍以及使用方法的归纳总结。

为什么选择WCDB

  • 之所以选择WCDB,主要还是因为它的高效、完整、易用性。
  1. 高效
    WCDB支持多线程的读读、读写并发以及写写串行执行,在批量写操作的性能测试中,WCDB性能是FMDB的180%左右。


    WCDB的基本使用_第1张图片
    批量读写操作性能对比.jpg

    而在多线程读写操作中,WCDB的多线程读写操作性能优于FMDB 62% ,而多线程读操作基本与FMDB持平。FMDB在多线程写测试中,直接返回错误SQLITE_BUSY,因此无法比较。而基于SQLite的机制,WCDB的多线程写操作实质也是串行执行,但不会出错导致操作中断。


    WCDB的基本使用_第2张图片
    多线程读写操作性能对比.jpg
  2. 完整
    加密:WCDB提供基于SQLCipher的数据库加密。
    损坏修复:WCDB内建了Repair Kit用于修复损坏的数据库。
    WCDB提供接口直接获取SQL的执行耗时,可用于监控性能。
    反注入:WCDB内建了对SQL注入的保护
  3. 易用
    WCDB的查询语言是用WINQ进行查询,无需为了拼接SQL字符串写很长的代码。
    WCDB的对象关系映射也非常方便,可以很便捷地定义表、索引、约束和增删改查操作等。

WCDB的使用

  • 创建数据库
-(WCTDatabase *)db {
    if (_db) {
        return _db;
    }
    //获取沙盒根目录
    NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    // 文件路径
    NSString *filePath = [documentsPath stringByAppendingPathComponent:@"RSDBService.sqlite"];
    NSLog(@"path = %@",filePath);
    _db = [[WCTDatabase alloc]initWithPath:filePath];
    if (![_db canOpen]) {
        NSLog(@"RSDBService.sqlite canOpen fail");
        [_db createTableAndIndexesOfName:@"" withClass:[NSArray class]];
    }
    return _db;
}
  • 对象关系映射

WCDB使用内建的宏实现ORM的功能,通过ORM可以达到直接通过Object进行数据库操作。此处需要注意的一点是,由于WCDB是基于Objective C++,如果在model的头文件中引入了,就需要把.m文件改变为.mm文件。为了不影响到使用model的controller或者view类,此处可以用category特性将wcdb的引用隔离。在category中引用,并遵守WCTTableCoding协议,使用WCDB_PROPERTY将声明绑定到数据库表的字段。以下用一个好友关系的contactModel做举例说明

首先是category文件,category中需要引入并遵守WCTTableCoding协议

#import "RSContactModel.h"
#import 

@interface RSContactModel (WCTTableCoding) 

WCDB_PROPERTY(uid)
WCDB_PROPERTY(nickName)
WCDB_PROPERTY(avatarUrl)
WCDB_PROPERTY(sex)
WCDB_PROPERTY(delFlag)
WCDB_PROPERTY(addFriendImgUrl)
WCDB_PROPERTY(registerTime)
WCDB_PROPERTY(addFriendTime)

@end

然后是.h文件,在.h中主要做的就是将model所需要暴露的属性暴露出来,以供其他类使用

#import 
#import "RSModel.h"
#import "Spcgicommdef.pbobjc.h"

@interface RSContactModel : RSModel
@property (nonatomic, strong) NSString *nickName;
@property (nonatomic, assign) long long uid;
@property (nonatomic, strong) NSString *avatarUrl;
@property (nonatomic, assign) RSenSex sex;
@property (nonatomic, assign) RSenDelFlag delFlag;
@property (nonatomic, strong) NSString *addFriendImgUrl;
@property (nonatomic, assign) int32_t registerTime;
@property (nonatomic, assign) int32_t addFriendTime;
@end

最后是.m文件,在.m文件中需要定义类文件中绑定到数据库表的字段以及主键的设置、索引的设置以及约束等。并且在init方法中通过dispacth_once初始化数据库表。

#import "RSContactModel+WCTTableCoding.h"
#import "RSContactModel.h"
#import 
#import "RSDBService.h"

@implementation RSContactModel

WCDB_IMPLEMENTATION(RSContactModel)
WCDB_SYNTHESIZE(RSContactModel, nickName)
WCDB_SYNTHESIZE(RSContactModel, uid)
WCDB_SYNTHESIZE(RSContactModel, avatarUrl)
WCDB_SYNTHESIZE(RSContactModel, sex)
WCDB_SYNTHESIZE(RSContactModel, addFriendImgUrl)
WCDB_SYNTHESIZE_DEFAULT(RSContactModel, delFlag, 0);
WCDB_SYNTHESIZE(RSContactModel, registerTime)
WCDB_SYNTHESIZE(RSContactModel, addFriendTime)
WCDB_UNIQUE(RSContactModel, uid)
WCDB_NOT_NULL(RSContactModel, uid)
-(instancetype)init {
    self = [super init];
    if (self) {
        static dispatch_once_t token;
        dispatch_once(&token, ^{
            [RSContactModel createDBTable];
        });
    }
    return self;
}

+(void)createDBTable {
    if ([[RSDBService db] createTableAndIndexesOfName:NSStringFromClass([RSContactModel class]) withClass:[RSContactModel class]]) {
        NSLog(@"creat table RSContactModel success");
    } else {
        NSLog(@"creat table RSContactModel fail");
    }
}
@end

WCDB_PROPERTY用于在头文件中声明绑定到数据库表的字段。

WCDB_IMPLEMENTATION,用于在类文件中定义绑定到数据库表的类。同时,该宏内实现了WCTTableCoding。因此,开发者无须添加更多的代码来完成WCTTableCoding的接口

WCDB_SYNTHESIZE,用于在类文件中定义绑定到数据库表的字段。

WCDB_PRIMARY用于定义主键

WCDB_PRIMARY_AUTO_INCREMENT 用于定义自增主键

WCDB_INDEX用于定义索引

WCDB_UNIQUE用于定义唯一约束

WCDB_NOT_NULL用于定义非空约束

  • 增删改查CRUD

对数据库访问的接口实现建议提供专门的Service类来进行操作,比如对好友关系的数据库模型可以提供一个专门的RSContactService来对RSContact表进行操作。如果熟悉RAC的话,可以在Service中结合RAC的信号来通知业务层说该数据库表有更新。

1.增:

-(BOOL)saveContactList:(NSArray *)contactList {
    //contactList中为服务端下发的contact列表
    NSMutableArray *tmp = [[NSMutableArray alloc] init];
    for (RSContact *contact in contactList) {
        RSContactModel *model = [[RSContactModel alloc] init];
        model.uid = contact.uin;
        model.nickName = contact.nickName;
        model.avatarUrl = contact.headImgURL;
        model.sex = (RSenSex)contact.sex;
        model.delFlag = (RSenDelFlag)contact.delFlag;
        model.addFriendImgUrl = contact.addFriendImgURL;
        model.registerTime = contact.registerTime;
        model.addFriendTime = contact.addFriendTime;
        [tmp addObject:model];
    }
    BOOL result = [[RSDBService db] insertOrReplaceObjects:tmp into:NSStringFromClass([RSContactModel class])];
    if (result) {
        [self.updateSignal sendNext:@(YES)];
        //如果result为true表示插入数据库成功,发送一个数据库更新的信号
    }
    return result;
}

2.删:

删除RSContactModel表中uid字段值为testUid的记录

    BOOL result = [[RSDBService db] deleteObjectsFromTable:NSStringFromClass([RSContactModel class]) where: RSContactModel.uid.is(testUid)];

3.改:

以下事例为删除某个uid为uid值的好友关系的时候,将contactModel中的delFlag更新为已删除的代码。实际上就是更新该记录中的delFlag的字段

- (BOOL)deleteContactWithUid:(long long)uid {
    RSContactModel *contactModel = [[RSContactModel alloc] init];
    contactModel.delFlag = RSenDelFlag_DelflagNotExist;
    BOOL result = [[RSDBService db] updateRowsInTable:NSStringFromClass([RSContactModel class]) onProperty:RSContactModel.delFlag withObject:contactModel where:RSContactModel.uid.is(uid)];
    if (result) {
        [self.updateSignal sendNext:@(YES)];
        //如果result为true表示修改数据库成功,发送一个数据库更新的信号
    }
    return result;
}

4.查:

数据库查询的接口就更多了,这里举例为根据uid的array查询表中uid为array中的值的记录。

-(NSArray*)getContactsByUids:(NSArray *)uids {
    NSArray *tmp = [[RSDBService db] getObjectsOfClass:[RSContactModel class] fromTable:NSStringFromClass([RSContactModel class]) where:RSContactModel.uid.in(uids)];
    return tmp;
}

  • 事务transaction

WCDB的事务有两种写法,一种是通过block来实现,另一种是通过获取WCTTransaction来实现,block的方式使用更简单,但是WCTTransaction的方式更易于传递。

//Block的方式
BOOL commited = [[RSDBService db] runTransaction:^BOOL{
    [[RSDBService db] insertObject:contact into:NSStringFromClass([RSContactModel class])];
    return YES;
}];

//WCTTransaction方式
WCTTransaction *transaction = [[RSDBService db] getTransaction];
BOOL result = [transaction begin];
[[RSDBService db] insertObject:contact into:NSStringFromClass([RSContactModel class])];
result = [transaction commit];
if(!result) {
    NSLog(@"%@",[transaction getError]);
}

扩展阅读

微信移动端数据库组件WCDB系列(一)-iOS基础篇

微信移动端数据库组件WCDB系列(二) — 数据库修复三板斧

你可能感兴趣的:(WCDB的基本使用)