-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Objc 模型绑定
模型绑定(Object-relational Mapping,简称 ORM),通过对 Objc 类进行绑定,形成类 - 表模型的映射关系,从而达到通过对象直接操作数据库的目的。
WCDB使用内置的宏来连接类、属性与表、字段。共有三类宏,分别对应数据库的字段、索引和约束。所有宏都定义在WCTMacro.h中。
关于字段、索引、约束的具体描述及用法,请参考SQLite的相关文档:Create Table和Create Index。
WCDB Objc 的模型绑定分为五个部分:
- 字段映射
- 字段约束
- 表约束
- 索引
- 虚拟表映射
这其中大部分是格式化的模版代码,我们在最后介绍文件模版和代码提示模版,以简化模型绑定的操作。
字段映射主要使用WCDB_PROPERTY
宏来声明,用WCDB_SYNTHESIZE
系列宏来实现。以下是一个字段映射的示例代码:
@interface Sample : NSObject<WCTTableCoding>
@property(nonatomic, assign) int identifier;
@property(nonatomic, strong) NSString* content;
@property(nonatomic, assign) int offset;
@property(nonatomic, strong) NSString* debugContent;
WCDB_PROPERTY(identifier)
WCDB_PROPERTY(content);
WCDB_PROPERTY(offset)
@end
@implementation Sample
WCDB_IMPLEMENTATION(Sample)
WCDB_SYNTHESIZE_COLUMN(identifier, "id")
WCDB_SYNTHESIZE(content)
WCDB_SYNTHESIZE_COLUMN(offset, "db_offset")
@end
将一个ObjC类进行ORM绑定的过程如下:
- 定义该类遵循
WCTTableCoding
协议。可以在类声明上定义,也可以通过[文件模版][WCTTableCoding-file-template]在category内定义。 - 使用
WCDB_PROPERTY
宏在头文件声明需要绑定到数据库表的字段。对于不需要写入数据库的字段,则不需要声明,比如debugContent
字段。 - 使用
WCDB_IMPLEMENTATION
宏在类文件定义绑定到数据库表的类。 - 使用
WCDB_SYNTHESIZE
宏在类文件定义需要绑定到数据库表的字段,这样数据库中的列名和字段名是一样的。 - 对于字段名与表的列名不一样的情况,可以使用别名进行映射,如
WCDB_SYNTHESIZE_COLUMN(identifier, "id")
。 - 对于字段名与 SQLite 的保留关键字冲突的字段,同样可以使用别名进行映射,如
offset
是 SQLite 的关键字,就需要WCDB_SYNTHESIZE_COLUMN(offset, "db_offset")
。
字段映射定义完成后,调用 createTable:withClass:
接口即可根据这个定义创建表。
// 以下代码等效于 SQL:CREATE TABLE IF NOT EXISTS sampleTable(id INTEGER, content TEXT, db_offset INTEGER)
BOOL ret = [database createTable:@"sampleTable" withClass:Sample.class];
并非所有类型的变量都支持被绑定为字段。WCDB Objc 内建了常用类型的支持,包括:
C类型 | 数据库类型 |
---|---|
整型(包括但不限于int 、unsigned 、long 、unsigned long 、long long 、unsigned long long 等所有基于整型的C基本类型) |
整型(INTEGER) |
枚举型(enum 及所有基于枚举型的C基本类型) |
整型(INTEGER) |
浮点数(包括但不限于float 、double 、long double 等所有基于浮点型的C基本类型) |
浮点型( REAL) |
字符串(const char * 的C字符串类型) |
字符串( TEXT) |
Objective-C类型 | 数据库类型 |
---|---|
NSDate |
浮点型(REAL) |
NSNumber |
浮点型(REAL) |
NSString 、NSMutableString
|
字符串(TEXT) |
其他所有符合NSCoding 协议的NSObject 子类 |
二进制(BLOB) |
对于没有内建支持的类型,开发者可以手动为其添加支持。我们将在自定义字段映射类型一章进一步介绍。
字段约束是针对单个字段的约束,如主键约束、非空约束、唯一约束、默认值等。字段约束有下面这些宏实现,主要是写在实现文件中:
-
主键约束以
WCDB_PRIMARY
开头,定义了数据库的主键,支持自定义主键的排序方式、是否自增。-
WCDB_PRIMARY(propertyName)
是最基本的用法,它直接使用propertyName
作为数据库主键。 -
WCDB_PRIMARY_ASC(propertyName)
定义主键升序。 -
WCDB_PRIMARY_DESC(propertyName)
定义主键降序。 -
WCDB_PRIMARY_AUTO_INCREMENT(propertyName)
定义主键自增,下面会有详细介绍。 -
WCDB_PRIMARY_ASC_AUTO_INCREMENT(propertyName)
是主键自增和升序的组合。
-
-
非空约束为
WCDB_NOT_NULL(propertyName)
,当该字段插入数据为空时,数据库会返回错误。 -
默认值约束为
WCDB_DEFAULT(propertyName, defaultValue)
,默认值可以是任意的C类型或NSString
、NSData
、NSNumber
、NSNull
。 -
唯一约束为
WCDB_UNIQUE(propertyName)
,当该字段插入数据与其他列冲突时,数据库会返回错误。 -
使用表达式建立约束
WCDB_CHECK(propertyName, condition)
以下是对上面给出的Sample
类添加字段约束的示例代码:
@implementation Sample
WCDB_IMPLEMENTATION(Sample)
WCDB_SYNTHESIZE_COLUMN(identifier, "id")
WCDB_SYNTHESIZE(content)
WCDB_SYNTHESIZE_COLUMN(offset, "db_offset")
WCDB_PRIMARY(identifier)
WCDB_NOT_NULL(content)
WCDB_DEFAULT(offset, 0)
WCDB_CHECK(identifier, Sample.identifier > 1000 && Sample.identifier < 10000)
@end
定义了 WCDB_PRIMARY_AUTO_INCREMENT
的字段,支持以自增的方式进行插入数据。但仍可以通过非自增的方式插入数据。
当需要进行自增插入时,对象需设置 isAutoIncrement
属性为 YES
,则数据库会使用 已有数据中最大的值+1 作为主键的值。
Sample* autoIncrementObject = [[Sample alloc] init];
autoIncrementObject.isAutoIncrement = YES;
// 插入自增数据
BOOL ret = [database insertObject:autoIncrementObject intoTable:@"sampleTable"];
if(ret) {
NSLog(@"%lld", autoIncrementObject.lastInsertedRowID); // 输出 1
}
// 再次插入自增数据
ret = [database insertObject:autoIncrementObject intoTable:@"sampleTable"];
if(ret) {
NSLog(@"%lld", autoIncrementObject.lastInsertedRowID); // 输出 2
}
// 插入非自增的指定数据
Sample* specificObject = [[Sample alloc] init];
specificObject.identifier = 10;
ret = [database insertObject:specificObject intoTable:@"sampleTable"];
WCDB没有默认实现lastInsertedRowID这个属性,要使用它就要在model中使用@synthesize lastInsertedRowID来将它实现一下,否则调用时会因为找不到属性导致闪退。
- 多主键约束以
WCDB_MULTI_PRIMARY
开头,定义了数据库的多主键,支持自定义每个主键的排序方式。-
WCDB_MULTI_PRIMARY(constraintName, propertyName)
是最基本的用法,与索引类似,多个主键通过constraintName
匹配。 -
WCDB_MULTI_PRIMARY_ASC(constraintName, propertyName)
定义了多主键propertyName
对应的主键升序。 -
WCDB_MULTI_PRIMARY_DESC(constraintName, propertyName)
定义了多主键中propertyName
对应的主键降序。
-
- 多字段唯一约束以
WCDB_MULTI_UNIQUE
开头,定义了数据库的多字段组合唯一,支持自定义每个字段的排序方式。-
WCDB_MULTI_UNIQUE(constraintName, propertyName)
是最基本的用法,与索引类似,多个字段通过constraintName
匹配。 -
WCDB_MULTI_UNIQUE_ASC(constraintName, propertyName)
定义了多字段中propertyName
对应的字段升序。 -
WCDB_MULTI_UNIQUE_DESC(constraintName, propertyName)
定义了多字段中propertyName
对应的字段降序。
-
- 无Rowid约束
WCDB_WITHOUT_ROWID
,这种适用于一些简单表,具体见SQLite-WITHOUT ROWID Optimization。
以下是一个表约束的示例代码:
@implementation Sample
WCDB_IMPLEMENTATION(Sample)
WCDB_SYNTHESIZE_COLUMN(identifier, "id")
WCDB_SYNTHESIZE(content)
WCDB_SYNTHESIZE_COLUMN(offset, "db_offset")
WCDB_MULTI_PRIMARY("primary_identifier_offset", identifier)
WCDB_MULTI_PRIMARY("primary_identifier_offset", offset)
WCDB_MULTI_UNIQUE("unique_identifier_offset", identifier)
WCDB_MULTI_UNIQUE("unique_identifier_offset", offset)
@end
索引宏以WCDB_INDEX
开头,定义了数据库的索引属性。支持定义索引的排序方式。
-
WCDB_INDEX(indexSubfixName, propertyName)
是最简单的用法,它直接定义某个字段为索引。同时,WCDB会将tableName
+indexSubfixName
作为该索引的名称。 -
WCDB_INDEX_ASC(indexSubfixName, propertyName)
定义索引为升序。 -
WCDB_INDEX_DESC(indexSubfixName, propertyName)
定义索引为降序。 -
WCDB_UNIQUE_INDEX(indexSubfixName, propertyName)
定义唯一索引。 -
WCDB_UNIQUE_INDEX_ASC(indexSubfixName, propertyName)
定义唯一索引为升序。 -
WCDB_UNIQUE_INDEX_DESC(indexSubfixName, propertyName)
定义唯一索引为降序。
WCDB通过indexSubfixName
匹配多索引。相同的indexSubfixName
会被组合为多字段索引,而且索引中的字段顺序按照宏的声明次序。下面是Sample
类使用索引的示例:
@implementation Sample
WCDB_IMPLEMENTATION(Sample)
WCDB_SYNTHESIZE_COLUMN(identifier, "id")
WCDB_SYNTHESIZE(content)
WCDB_SYNTHESIZE_COLUMN(offset, "db_offset")
WCDB_INDEX_ASC("_index", identifier)
WCDB_INDEX("_multiIndex", identifier)
WCDB_INDEX("_multiIndex", offset)
@end
使用这些宏定义的索引的名字都是表名拼接indexSubfixName。比如表名为sampleTable
,上面两个索引的名字分别是sampleTable_index
和sampleTable_multiIndex
。
普通表不需要用到虚拟表映射,因此这里暂且按下不表,我们会在全文搜索一章中进行介绍。
在开发过程中,经过多个版本的迭代后,经常会出现数据库字段升级的情况,如增加新字段、删除或重命名旧字段、新增索引等等。 对于 SQLite 本身,其并不支持对字段的删除和重命名。新增加字段则需要考虑不同版本升级等情况。而这个问题通过模型绑定可以很好的解决。
纵观上述字段映射、字段约束、索引和表约束等四个部分,都是通过调用 -[WCTTableProtocol createTable:withClass:]
接口使其生效的。
实际上,该接口会将 模型绑定的定义 与 表本身的结构 联系起来,并进行更新。
对于字段映射:
- 表已存在但模型绑定中未定义的字段,会被忽略。这可以用于删除字段。
- 表不存在但模型绑定中有定义的字段,会被新增到表中。这可以用于新增字段。
- 对于需要重命名的字段,可以通过别名的方式重新映射。
忽略字段并不会删除字段。对于该字段旧内容,会持续存在在表中,因此文件不会因此变小。实际上,数据库作为持续增长的二进制文件,只有将其数据导出生成另一个新的数据库,才有可能回收这个字段占用的空间。对于新插入的数据,该字段内容为空,不会对性能产生可见的影响。
对于索引,不存在的索引会被新增到数据库中。
对于数据库已存在但模型绑定中未定义的索引,
-[WCTTableProtocol createTable:withClass:]
接口不会自动将其删除。如果需要删除,开发者需要调用-[WCTTableProtocol dropIndex:]
接口,或者使用WCDB_INDEX_TO_BE_DROPPED
显式声明需要删除索引。因为建索引需要遍历原有数据,是个耗时操作,对旧表建索引要谨慎考虑性能问题。可以使用
WCDB_INDEX_FOR_NEWLY_CREATED_TABLE_ONLY
宏显式声明新增的索引只在新表中创建,而不在已有的表中添加。
以下是数据库升级的一个例子:
在第一个版本中,Sample
的模型绑定定义如下,并在数据库创建了以之对应的表 sampleTable
。
@interface Sample : NSObject<WCTTableCoding>
@property(nonatomic, assign) int identifier;
@property(nonatomic, strong) NSString* discription;
@property(nonatomic, strong) NSDate* createDate;
WCDB_PROPERTY(identifier)
WCDB_PROPERTY(discription);
WCDB_PROPERTY(createDate)
@end
@implementation Sample
WCDB_IMPLEMENTATION(Sample)
WCDB_SYNTHESIZE(identifier)
WCDB_SYNTHESIZE(discription)
WCDB_SYNTHESIZE(createDate)
@end
[database createTable:@"sampleTable" withClass:Sample.class];
到了第二个版本,sampleTable 表进行了升级。
@interface Sample : NSObject<WCTTableCoding>
@property(nonatomic, assign) int identifier;
@property(nonatomic, strong) NSString* content;
@property(nonatomic, strong) NSString* title;
WCDB_PROPERTY(identifier)
WCDB_PROPERTY(content);
WCDB_PROPERTY(title)
@end
@implementation Sample
WCDB_IMPLEMENTATION(Sample)
WCDB_SYNTHESIZE(identifier)
WCDB_SYNTHESIZE_COLUMN(content, "discription")
WCDB_SYNTHESIZE(title)
WCDB_INDEX("_index", identifier)
@end
[database createTable:@"sampleTable" withClass:Sample.class];
可以看到,通过修改模型绑定,并再次调用 -[WCTTableProtocol createTable:withClass:]
。
-
description
字段通过别名的特性,被重命名为了content
。 - 已删除的
createDate
字段会被忽略。 - 对于新增的
title
会被添加到表中。 - 新增的索引
sampleTable_index
会被添加到表中。
模型绑定的大部分都是格式固定的代码,因此,WCDB Objc 提供了文件模版和代码模版两种方式,以简化模型绑定操作。
文件和代码模版都在源代码的 tools/templates
目录下
- 未获取 WCDB 的 Github 仓库的开发者,可以在命令执行
curl https://raw.githubusercontent.com/Tencent/wcdb/master/tools/templates/install.sh -s | sh
- 已获取 WCDB 的 Github 仓库的开发者,可以手动执行
cd path-to-your-wcdb-dir/tools/templates; sh install.sh;
。 - 安装完成后重启Xcode,选择新建文件,滚到窗口底部,即可看到对应的文件模版。
文件模版安装完成后,在 Xcode 的菜单 File
-> New
-> File...
中创建新文件,选择 TableCodable
。
在弹出的菜单中输入文件名,并选择 Language 为 Objective-C 即可。
这里以Sample
类为例,Xcode会自动创建Sample+WCTTableCoding.h
文件模版:
#import "Sample.h"
#import <WCDB/WCDB.h>
@interface Sample (WCTTableCoding) <WCTTableCoding>
WCDB_PROPERTY(<#property1 #>)
WCDB_PROPERTY(<#property2 #>)
WCDB_PROPERTY(<#property3 #>)
WCDB_PROPERTY(<#property4 #>)
WCDB_PROPERTY(<#... #>)
@end
加上类的ORM实现即可。
//Sample.h
#import <Foundation/Foundation.h>
@interface Sample : NSObject
@property(nonatomic, assign) int intValue;
@property(nonatomic, retain) NSString *stringValue;
@end
//WCTSampleAdvance.mm
@implementation Sample
WCDB_IMPLEMENTATION(Sample)
WCDB_SYNTHESIZE(intValue)
WCDB_SYNTHESIZE(stringValue)
WCDB_PRIMARY_ASC_AUTO_INCREMENT(intValue)
@end
//Sample+WCTTableCoding.h
#import "Sample.h"
#import <WCDB/WCDB.h>
@interface Sample (WCTTableCoding) <WCTTableCoding>
WCDB_PROPERTY(intValue)
WCDB_PROPERTY(stringValue)
@end
此时,原来的Sample.h
中不包含任何C++的代码。因此,其他文件对其引用时,不需要修改文件名后缀。只有Model层需要使用WCDB接口的类,才需要包含Sample+WCTTableCoding.h
,并修改文件名后缀为.mm
。
- 欢迎使用 WCDB
- 基础教程
- 进阶教程
- 欢迎使用 WCDB
- 基础教程
- 进阶教程
- 欢迎使用 WCDB
- 基础教程
- 进阶教程
- 欢迎使用 WCDB
- 基础教程
- 进阶教程