-
Notifications
You must be signed in to change notification settings - Fork 1.4k
C++ 模型绑定
模型绑定(Object-relational Mapping,简称 ORM),通过对 C++ 类或结构体进行绑定,形成类或结构体 - 表模型的映射关系,从而达到通过对象直接操作数据库的目的。
WCDB使用内置的宏来连接类或结构体、属性与表、字段。共有三类宏,分别对应数据库的字段、索引和约束。所有宏都定义在CPPORMMacro.h中。
关于字段、索引、约束的具体描述及用法,请参考SQLite的相关文档:Create Table和Create Index。
WCDB 的模型绑定分为五个部分:
- 字段映射
- 字段约束
- 表约束
- 索引
- 虚拟表映射
字段映射主要使用WCDB_CPP_ORM_DECLARATION
宏来声明,用WCDB_CPP_SYNTHESIZE
系列宏来实现。以下是一个字段映射的示例代码:
// Sample.hpp
class Sample {
public:
Sample();//必须要有默认构造函数
Sample(int identifier, const std::string& content);//非必须实现的构造函数,只是为了演示方便
// 只支持绑定 public 的成员变量
int identifier;
std::string content;
int offset;
WCDB_CPP_ORM_DECLARATION(Sample)
private:
std::string debugContent;
};
// Sample.cpp
WCDB_CPP_ORM_IMPLEMENTATION_BEGIN(Sample)
WCDB_CPP_SYNTHESIZE_COLUMN(identifier, "id")
WCDB_CPP_SYNTHESIZE(content)
WCDB_CPP_SYNTHESIZE_COLUMN(offset, "db_offset")
WCDB_CPP_ORM_IMPLEMENTATION_END
将一个C++类或结构体进行ORM绑定的过程如下:
- 使用
WCDB_CPP_ORM_DECLARATION
宏在头文件的类或结构体中声明它实现了模型绑定。 - 使用
WCDB_CPP_ORM_IMPLEMENTATION_BEGIN
宏在类实现文件中定义绑定到数据库表的类。 - 使用
WCDB_CPP_SYNTHESIZE
宏在类实现中配置需要绑定到数据库表的字段,这样数据库中的列名和字段名是一样的。 - 对于字段名与表的列名不一样的情况,可以使用别名进行映射,如
WCDB_CPP_SYNTHESIZE_COLUMN(identifier, "id")
。 - 对于字段名与 SQLite 的保留关键字冲突的字段,同样可以使用别名进行映射,如
offset
是 SQLite 的关键字,就需要WCDB_CPP_SYNTHESIZE_COLUMN(offset, "db_offset")
。 - 对于不需要写入数据库的字段,则不需要用
WCDB_CPP_SYNTHESIZE
声明,比如debugContent
字段。 - 用
WCDB_CPP_ORM_IMPLEMENTATION_END
标记模型绑定部分内容结束。
模型绑定相关内容都需要写到
WCDB_CPP_ORM_DECLARATION
和WCDB_CPP_ORM_IMPLEMENTATION_END
之间,且不能添加其他不是模型绑定宏的逻辑。
字段映射定义完成后,调用 createTable
接口即可根据这个定义创建表。
// 以下代码等效于 SQL:CREATE TABLE IF NOT EXISTS sampleTable(id INTEGER, content TEXT, db_offset INTEGER)
bool ret = database.createTable<Sample>("sampleTable");
并非所有类型的变量都支持被绑定为字段。WCDB 内建了常用类型的支持,包括:
C类型 | 数据库类型 |
---|---|
整型(包括但不限于int 、unsigned 、long 、unsigned long 、long long 、unsigned long long 等所有基于整型的 C 基本类型) |
整型(INTEGER) |
枚举型(enum 及所有基于枚举型的C基本类型) |
整型(INTEGER) |
浮点数(包括但不限于float 、double 等所有基于浮点型的C基本类型) |
浮点型( REAL) |
字符串(C字符串类型char * ,标准库字符串std::string ,WCDB内部字符串类型WCDB::StringView ) |
字符串( TEXT) |
二进制数组(标准库二进制数组std::vector<unsigned char> ,WCDB内部二进制类型WCDB::Data ) |
二进制(BLOB) |
上面提到的类型的std::optional
和std::shared_ptr
类型也支持绑定为DB字段。
对于没有内建支持的类型,暂不支持模型绑定,后面我们会扩展更多类型。
字段约束是针对单个字段的约束,如主键约束、非空约束、唯一约束、默认值等。字段约束有下面这些宏实现,主要是写在实现文件中:
-
主键约束以
WCDB_CPP_PRIMARY
开头,定义了数据库的主键,支持自定义主键的排序方式、是否自增。-
WCDB_CPP_PRIMARY(fieldName)
是最基本的用法,它直接使用propertyName
作为数据库主键。 -
WCDB_CPP_PRIMARY_ASC(fieldName)
定义主键升序。 -
WCDB_CPP_PRIMARY_DESC(fieldName)
定义主键降序。 -
WCDB_CPP_PRIMARY_AUTO_INCREMENT(fieldName)
定义主键自增,下面会有详细介绍。 -
WCDB_CPP_PRIMARY_ASC_AUTO_INCREMENT(fieldName)
是主键自增和升序的组合。
-
-
非空约束为
WCDB_NOT_NULL(fieldName)
,当该字段插入数据为空时,数据库会返回错误。 -
默认值约束为
WCDB_CPP_DEFAULT(fieldName, defaultValue)
,默认值可以是任意的C类型或NSString
、NSData
、NSNumber
、NSNull
。 -
唯一约束为
WCDB_CPP_UNIQUE(fieldName)
,当该字段插入数据与其他列冲突时,数据库会返回错误。
以下是对上面给出的Sample
类添加字段约束的示例代码:
// Sample.cpp
WCDB_CPP_ORM_IMPLEMENTATION_BEGIN(Sample)
WCDB_CPP_SYNTHESIZE_COLUMN(identifier, "id")
WCDB_CPP_SYNTHESIZE(content)
WCDB_CPP_SYNTHESIZE_COLUMN(offset, "db_offset")
WCDB_CPP_PRIMARY(identifier)
WCDB_CPP_NOT_NULL(content)
WCDB_CPP_DEFAULT(offset, 0)
WCDB_CPP_ORM_IMPLEMENTATION_END
定义了 WCDB_CPP_PRIMARY_AUTO_INCREMENT
的字段,支持以自增的方式进行插入数据。但仍可以通过非自增的方式插入数据。
当需要进行自增插入时,对象需设置 isAutoIncrement
为 YES
,则数据库会使用 已有数据中最大的值+1 作为主键的值。
Sample autoIncrementObject;
autoIncrementObject.isAutoIncrement = true;
// 插入自增数据
bool ret = database.insertObjects<Sample>(autoIncrementObject, "sampleTable");
if(ret) {
printf("%lld", *autoIncrementObject.lastInsertedRowID); // 输出 1
}
// 再次插入自增数据
ret &= database.insertObjects<Sample>(autoIncrementObject, "sampleTable");
if(ret) {
printf("%lld", *autoIncrementObject.lastInsertedRowID); // 输出 2
}
// 插入非自增的指定数据
Sample specificObject;
specificObject.identifier = 10;
ret &= database.insertObjects<Sample>(specificObject, "sampleTable");
- 多主键约束以
WCDB_CPP_MULTI_PRIMARY
开头,定义了数据库的多主键,支持自定义每个主键的排序方式。-
WCDB_CPP_MULTI_PRIMARY(constraintName, fieldName)
是最基本的用法,与索引类似,多个主键通过constraintName
匹配。 -
WCDB_CPP_MULTI_PRIMARY_ASC(constraintName, fieldName)
定义了多主键fieldName
对应的主键升序。 -
WCDB_CPP_MULTI_PRIMARY_DESC(constraintName, fieldName)
定义了多主键中fieldName
对应的主键降序。
-
- 多字段唯一约束以
WCDB_CPP_MULTI_UNIQUE
开头,定义了数据库的多字段组合唯一,支持自定义每个字段的排序方式。-
WCDB_CPP_MULTI_UNIQUE(constraintName, fieldName)
是最基本的用法,与索引类似,多个字段通过constraintName
匹配。 -
WCDB_CPP_MULTI_UNIQUE_ASC(constraintName, fieldName)
定义了多字段中fieldName
对应的字段升序。 -
WCDB_CPP_MULTI_UNIQUE_DESC(constraintName, fieldName)
定义了多字段中fieldName
对应的字段降序。
-
- 无Rowid约束
WCDB_CPP_WITHOUT_ROWID
,这种适用于一些简单表,具体见SQLite-WITHOUT ROWID Optimization。
以下是一个表约束的示例代码:
// Sample.cpp
WCDB_CPP_ORM_IMPLEMENTATION_BEGIN(Sample)
WCDB_CPP_SYNTHESIZE_COLUMN(identifier, "id")
WCDB_CPP_SYNTHESIZE(content)
WCDB_CPP_SYNTHESIZE_COLUMN(offset, "db_offset")
WCDB_CPP_MULTI_PRIMARY("primary_identifier_offset", identifier)
WCDB_CPP_MULTI_PRIMARY("primary_identifier_offset", offset)
WCDB_CPP_MULTI_UNIQUE("unique_identifier_offset", identifier)
WCDB_CPP_MULTI_UNIQUE("unique_identifier_offset", offset)
WCDB_CPP_ORM_IMPLEMENTATION_END
索引宏以WCDB_CPP_INDEX
开头,定义了数据库的索引属性。支持定义索引的排序方式。
-
WCDB_CPP_INDEX(indexSubfixName, fieldName)
是最简单的用法,它直接定义某个字段为索引。同时,WCDB会将tableName
+indexSubfixName
作为该索引的名称。 -
WCDB_CPP_INDEX_ASC(indexSubfixName, fieldName)
定义索引为升序。 -
WCDB_CPP_INDEX_DESC(indexSubfixName, fieldName)
定义索引为降序。 -
WCDB_CPP_UNIQUE_INDEX(indexSubfixName, fieldName)
定义唯一索引。 -
WCDB_CPP_UNIQUE_INDEX_ASC(indexSubfixName, fieldName)
定义唯一索引为升序。 -
WCDB_CPP_UNIQUE_INDEX_DESC(indexSubfixName, fieldName)
定义唯一索引为降序。
WCDB通过indexSubfixName
匹配多索引。相同的indexSubfixName
会被组合为多字段索引,而且索引中的字段顺序按照宏的声明次序。下面是Sample
类使用索引的示例:
// Sample.cpp
WCDB_CPP_ORM_IMPLEMENTATION_BEGIN(Sample)
WCDB_CPP_SYNTHESIZE_COLUMN(identifier, "id")
WCDB_CPP_SYNTHESIZE(content)
WCDB_CPP_SYNTHESIZE_COLUMN(offset, "db_offset")
WCDB_CPP_INDEX_ASC("_index", identifier)
WCDB_CPP_INDEX("_multiIndex", identifier)
WCDB_CPP_INDEX("_multiIndex", offset)
WCDB_CPP_ORM_IMPLEMENTATION_END
使用这些宏定义的索引的名字都是表名拼接indexSubfixName。比如表名为sampleTable
,上面两个索引的名字分别是sampleTable_index
和sampleTable_multiIndex
。
普通表不需要用到虚拟表映射,因此这里暂且按下不表,我们会在全文搜索一章中进行介绍。
在开发过程中,经过多个版本的迭代后,经常会出现数据库字段升级的情况,如增加新字段、删除或重命名旧字段、新增索引等等。 对于 SQLite 本身,其并不支持对字段的删除和重命名。新增加字段则需要考虑不同版本升级等情况。而这个问题通过模型绑定可以很好的解决。
纵观上述字段映射、字段约束、索引和表约束等四个部分,都是通过调用 createTable
接口使其生效的。
实际上,该接口会将 模型绑定的定义 与 表本身的结构 联系起来,并进行更新。
对于字段映射:
- 表已存在但模型绑定中未定义的字段,会被忽略。这可以用于删除字段。
- 表不存在但模型绑定中有定义的字段,会被新增到表中。这可以用于新增字段。
- 对于需要重命名的字段,可以通过别名的方式重新映射。
忽略字段并不会删除字段。对于该字段旧内容,会持续存在在表中,因此文件不会因此变小。实际上,数据库作为持续增长的二进制文件,只有将其数据导出生成另一个新的数据库,才有可能回收这个字段占用的空间。对于新插入的数据,该字段内容为空,不会对性能产生可见的影响。
对于索引,不存在的索引会被新增到数据库中。
对于数据库已存在但模型绑定中未定义的索引,
createTable
接口不会自动将其删除。如果需要删除,开发者需要调用dropIndex
接口,或者使用WCDB_CPP_INDEX_TO_BE_DROPPED
显式声明需要删除索引。因为建索引需要遍历原有数据,是个耗时操作,对旧表建索引要谨慎考虑性能问题。可以使用
WCDB_CPP_INDEX_FOR_NEWLY_CREATED_TABLE_ONLY
宏显式声明新增的索引只在新表中创建,而不在已有的表中添加。
以下是数据库升级的一个例子:
在第一个版本中,Sample
的模型绑定定义如下,并在数据库创建了以之对应的表 sampleTable
。
// Sample.hpp
class Sample {
public:
int identifier;
std::string discription;
double createDate;
WCDB_CPP_ORM_DECLARATION(Sample)
};
// Sample.cpp
WCDB_CPP_ORM_IMPLEMENTATION_BEGIN(Sample)
WCDB_CPP_SYNTHESIZE(identifier)
WCDB_CPP_SYNTHESIZE(discription)
WCDB_CPP_SYNTHESIZE(createDate)
WCDB_CPP_ORM_IMPLEMENTATION_END
database.createTable<Sample>("sampleTable");
到了第二个版本,sampleTable 表进行了升级。
// Sample.hpp
class Sample {
public:
Sample();
Sample(int identifier, const std::string& content);
int identifier;
std::string content;
std::string title;
WCDB_CPP_ORM_DECLARATION(Sample)
};
// Sample.cpp
WCDB_CPP_ORM_IMPLEMENTATION_BEGIN(Sample)
WCDB_CPP_SYNTHESIZE(identifier)
WCDB_CPP_SYNTHESIZE_COLUMN(content, "discription")
WCDB_CPP_SYNTHESIZE(title)
WCDB_CPP_INDEX("_index", identifier)
WCDB_CPP_ORM_IMPLEMENTATION_END
database.createTable<Sample>("sampleTable");
可以看到,通过修改模型绑定,并再次调用 createTable
-
description
字段通过别名的特性,被重命名为了content
。 - 已删除的
createDate
字段会被忽略。 - 对于新增的
title
会被添加到表中。 - 新增的索引
sampleTable_index
会被添加到表中。
- 欢迎使用 WCDB
- 基础教程
- 进阶教程
- 欢迎使用 WCDB
- 基础教程
- 进阶教程
- 欢迎使用 WCDB
- 基础教程
- 进阶教程
- 欢迎使用 WCDB
- 基础教程
- 进阶教程