前言
WCDB是微信移动端团队开源的移动端数据库组件,提供了一个高效、完整、易用的移动端存储方案。第一次应用到WCDB还是在现公司的工程中,由于现在的team成员主要来自鹅厂,在工程中应用到前东家的东西也是理所当然,这同时也充分说明了WCDB的易用性,不好用谁会继续再使用它呢?本文主要是对WCDB做简单的介绍以及使用方法的归纳总结。
为什么选择WCDB
- 之所以选择WCDB,主要还是因为它的高效、完整、易用性。
-
高效
WCDB支持多线程的读读、读写并发以及写写串行执行,在批量写操作的性能测试中,WCDB性能是FMDB的180%左右。
而在多线程读写操作中,WCDB的多线程读写操作性能优于FMDB 62% ,而多线程读操作基本与FMDB持平。FMDB在多线程写测试中,直接返回错误SQLITE_BUSY,因此无法比较。而基于SQLite的机制,WCDB的多线程写操作实质也是串行执行,但不会出错导致操作中断。
- 完整
加密:WCDB提供基于SQLCipher的数据库加密。
损坏修复:WCDB内建了Repair Kit用于修复损坏的数据库。
WCDB提供接口直接获取SQL的执行耗时,可用于监控性能。
反注入:WCDB内建了对SQL注入的保护 - 易用
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的头文件中引入了
首先是category文件,category中需要引入
#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系列(二) — 数据库修复三板斧