简介

WCDB(WeChat DataBase)是微信官方的移动端数据库组件,它基于SQLCipher,是一个关系型数据库,支持iOS, macOS和Android。WCDB提供了三个基础类进行数据库操作:WCTDatabase、WCTTable、WCTTransaction。它们的接口都是线程安全的。WCDB几乎涵盖了常用的数据库操作,同时还开放了核心层接口,方便用户扩展一些未封装的复杂SQL操作。安全方面它继承了SQLCipher的加密方式,内建了Repair Kit用于修复损坏的数据库,通过内建宏的方式实现了ORM(Object Relational Mapping),方便地实现了对象属性到数据表字段的映射,通过一种名为WINQ的规则对SQL进行了抽象,避免了冗长的SQL胶水代码,也防止了SQL注入。

ORM

WCDB使用内置的宏来连接类、属性与表、字段。共有三类宏,分别对应数据库的字段、索引和约束。所有宏都定义在WCTCodingMacro.h中。WCDB的ORM会自动识别property的类型,并映射到适合的数据库类型。其与Objective-C类型对应关系为:

  • 整型
    • NSDate(保存其时间戳)
  • 浮点数
    • NSNumber
  • 字符串
    • NSString、NSMutableString
  • 二进制
    • NSData、NSMutableData
    • NSArray、NSMutableArray
    • NSDictionary、NSMutableDictionary
    • NSSet、NSMutableSet
    • NSValue
    • NSURL

关于ORM官方github文档给出了详细的中英文教程包括字段宏、索引宏、约束宏等在此不一一列举。

WINQ

WINQ避免了冗长复杂的SQL拼接操作,对SQL语句进行了抽象。抽象的思路还是从SQL语法规则出发,SQL的语法规则实则上是一种链式操作,可以理解为一些固定的keyword、object和expr的集合。所以WINQ的实现思路是将固定的keyword,封装为函数名,作为连接;将可以展开的token,封装为类,并在类内实现其不同的组合。这句话可能说的有点绕,通过下面的代码块可能比较好理解些。在下面的代码块中,把SELECT定义成了一个类StatementSelect,这个类有三个函数,函数名我们并不陌生,正是wherelimithaving三个SQL关键字,同时函数的返回也是一个StatementSelect对象,这样我们就能实现一种链式操作。实现了上面提到的连接。另外我们可以发现三个函数还有一个共同点,它们的参数都是Expr类型,因为在实际的SQL语法规则中where、limit、having等操作都是接受表达式的,所以这里StatementSelect的类方法都是表达式。然后在Expr内部可以实现表达式的各种组合。

1
2
3
4
5
6
7
8
class StatementSelect : public Statement {
public:
//...
StatementSelect &where(const Expr &where);
StatementSelect &limit(const Expr &limit);
StatementSelect &having(const Expr &having);
//...
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Expr : public Describable {
public:
Expr(const Column &column);
template <typename T>
Expr(const T &value,
typename std::enable_if<std::is_arithmetic<T>::value ||
std::is_enum<T>::value>::type * = nullptr)
: Describable(literalValue(value))
{
}
Expr(const std::string &value);

Expr operator||(const Expr &operand) const;
Expr operator&&(const Expr &operand) const;
Expr operator!=(const Expr &operand) const;

Expr between(const Expr &left, const Expr &right) const;
Expr notBetween(const Expr &left, const Expr &right) const;

Expr isNull() const;
Expr isNotNull() const;

//...
};

基于这个抽象方式,就可以对复杂查询中的条件语句进行重写为:

1
2
3
4
5
6
7
8
9
10
11
12
13
Column content("content");
Column createTime("createTime");
Column modifiedTime("modifiedTime");
Column type("type");
StatementSelect select;
//...
//WHERE content IS NOT NULL
// AND createTime!=modifiedTime
// OR type NOT BETWEEN 0 AND 2
select.where(Expr(content).isNotNull()
&&Expr(createTime)!=Expr(modifiedTime)
||Expr(type).notBetween(0, 2));
//...

实践

建库

WCTDatabase表示一个数据库,可以进行所有数据库操作,包括增删查改、表操作、事务、文件操作、损坏修复等。对于同一个路径的数据库,不同的WCTDatabase、WCTTable、WCTTransaction对象共享同一个WCDB核心。因此,你可以在代码的不同位置、不同线程任意创建不同的基础类对象,WCDB会自动管理它们的共享数据和线程并发。

1
2
3
4
5
6
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"test.db"];
_database = [[WCTDatabase alloc] initWithPath: path];
[_database close:^{
[_database removeFilesWithError:nil];
}];

建表

1
2
3
if (![_database isTableExists:@"student"]) {
[_database createTableAndIndexesOfName:@"student" withClass:Student.class];
}

数据操作

插入

1
2
3
4
Student *student = [[Student alloc] init];
student.studentID = studentID;
student.name = name;
[_database insertObject:student into:@"student"];

查询

1
2
return [_database getOneObjectOfClass:Student.class 
fromTable:@"student" where:Student.studentID == studentID];

总结

以上是我通读了WCDB的iOS官方文档和API做的一些总结,其中有一些自己的理解,同时也概括了WCDB的核心理念和使用方法。另外还有一些关于数据库修复、事务、约束的操作,没有详细列举。可以直接访问其github里面的中英文教程讲的非常详细。另外截止到2016年12月微信全球共计8.89亿月活用户,而新兴的公众号平台拥有1000万个,WCDB作为支撑这么一个平台的移动端数据库,其安全性和稳定性应该还是值得信赖的。如果是纯OC的项目,如果觉得CoreData学习成本高,不容易掌握,不是很稳定的话,推荐可以使用WCDB,但是项目中所有用到WCDB依赖的类实现文件都必须改成.mm的形式。但如果是Swift或者OC/Swift混编项目,WCDB目前对Swift还不是很友好,希望WCDB团队后续能对Swift进行支持吧。

现在,WCDB的Swift版本已经发布了,官方wiki地址:关于 WCDB Swift