👉目录
1 简介
2 挑战
3 重大升级
4 开源地址
5 总结
class Sample {
public:
int id;
std::string content;
WCDB_CPP_ORM_DECLARATION(Sample)
};
// INSERT INTO myTable(id, content) VALUES(1, 'text')
database.insertObjects<Sample>(Sample(1, "text"), myTable);
// SELECT id, content FROM myTable WHERE id > 0
auto objects = database.getAllObjects<Sample>(myTable, WCDB_FIELD(Sample::id) > 0);
WCDB_FIELD(Sample::id)
,它既可以表示表中 id
这个字段,用来组成各种条件表达式,也可以用来访问Sample
的实例中的id
这个成员变量,进而可以实现将一个C++对象序列化写到数据库,或者从数据库中反序列化读出来,就像里面包含了id
这个成员变量的Getter
和Setter
。WCDB_FIELD(Sample::id)
又如何生效呢?这恰是 C++ ORM 设计的难点。早期比较成熟的 C++ ORM 方案是用了预编译的方法,将这些元数据通过代码生成的方式 hardcode 到代码中。Sample
对应的表,DB 对象的类型就会变得非常复杂,模版膨胀问题可见一斑:// 指向 id 成员变量的指针 memberPointer 中包含了 Sample 和 int 两个类型
int Sample::* memberPointer = &Sample::id;
Sample obj;
// 用类成员指针 写 成员变量
obj.*memberPointer = 1;
// 用类成员指针 读 成员变量
int id = obj.*memberPointer;
template<typename T, typename O>
void* castMemberPointer(T O::*memberPointer) {
union {
T O::*memberp;
void* voidp;
};
memberp = memberPointer;
return voidp;
}
SFINAE
机制,将支持写入数据库的类型映射到这些数值上,就完成了类型到数值的转换:long long
,浮点型的标准类型是double
,这个标准类型能够不丢失精度地存储这个类别里面所有类型的所有值。这样我们将标准类型作为Getter
和Setter
的出入参,在 Getter
和Setter
内部实现中,再负责将标准类型的数据转换成具体的类型。SQLite_sequence
表的查询语句,使用 Winq 来编写可以是这样:WCDB::StatementSelect().select({WCDB::ColumnResult(WCTSequence.seq)})
.from("sqlite_sequence")
.where(WCTSequence.seq > 1000)
.orderBy({WCTSequence.seq.order(WCTOrderedAscending)})
.limit(10)
.offset(100)
Token
抽象成C++
类,将不同的 Token 的连接能力抽象成了 C++ 类的接口,并通过链式调用的方式,让 Winq 拼接出来的 SQL 语句读起来跟实际的 SQL 语句接近,可读性好。但随着在微信中的应用推广,这一版的 Winq 还有下面几个明显的问题:
.select()
中接收 ORM 的 Property 时需要先构造WCDB::ColumnResult
,再显式转成数组传入。WCDB::StatementSelect().select(WCTSequence.seq)
.from("sqlite_sequence")
.offset(100)
.limit(10)
.order(WCTSequence.seq)
.where(WCTSequence.seq > 1000)
rowid
,然后根据这个rowid
,把新插入的未压缩内容读出来,压缩了,再更新到表中。这样执行的语句虽然多了,但是因为都在一个事务内,数据都还在内存中,所以并不会增加 IO 量,对性能的影响不会太明显。rowid
,然后用 rowid 逐行更新,再把更新的数据读出来,压缩完再写进去。这样执行语句看上去多了很多,但是因为都在一个事务内,每条更新的数据都还在内存中,所以也不会增加 IO 量,对性能的影响也是有限。decompress
函数,然后再把 SELECT/DELETE 语句中用到压缩字段的地方,全部替换成解压函数,这样就能把数据解压之后再使用:Column
对象用于 Winq 中组装语句,比如上文例子中用到的 WCTSequence.seq
就是调用WCTSequence
这个类的方法来获取 seq 属性配置的列名来构造一个 Column
对象。所以我们可以在使用这种途径构造Column
时,将整个 ORM 类的数据库配置信息一并传入,并保存在Column
中,这样就可以在 Winq 语句中获取到其中所用到的列所在的 ORM 类的全部配置信息。因为 ORM 信息是保存在堆上的全局量,所以这个改动实际上只是多传递和保存一个指针,并不会给 Winq 的使用带来性能影响。SELECT city FROM China WHERE city MATCH '广东: 广州'
会报错no such column: 广东
,但实际并不存在这一列,只是 fts 的搜索语法误把冒号前面这部分识别为列名。这种情况可以通过提取报错信息中的列名去匹配 Winq 语句中的列名来解决。Commit Transaction
,将事务修改内容写入磁盘。如果事务还不可以结束,再判断主线程是否因为当前事务阻塞,没有的话就回调外部逻辑,继续执行后面的循环,直到外部逻辑处理完毕。如果检测到主线程因为当前事务阻塞,则会立即 Commit Transaction
,先将部分修改内容写入磁盘,并唤醒主线程执行 DB 操作。等到主线程的 DB 操作执行完成之后,在重新开一个新事务,让外部可以继续执行之前中断的逻辑。可中断事务的整体逻辑如下图所示:
📢📢欢迎加入腾讯云开发者社群,享前沿资讯、大咖干货,找兴趣搭子,交同城好友,更有鹅厂招聘机会、限量周边好礼等你来~
(长按图片立即扫码)