WCDB

模型绑定

模型绑定(Object-relational Mapping,简称 ORM),通过对 Swift 类或结构进行绑定,形成类或结构 - 表模型、类或结构对象 - 表的映射关系,从而达到通过对象直接操作数据库的目的。

  • 字段映射
  • 字段约束
  • 索引
  • 表约束
  • 虚拟表映射

字段映射

在类内定义 CodingKeys 的枚举类,并遵循 String 和CodingTableKey。枚举列举每一个需要定义的字段。

1.别名映射 => case identifier = “id”

2.对于变量名与 SQLite 的保留关键字冲突 => case offset = “db_offset”

字段约束

它用于定于针对单个字段的约束,如主键约束、非空约束、唯一约束、默认值等。

1
2
3
4
5
6
7
8
9
ColumnConstraintBinding(
isPrimary: Bool = false, // 该字段是否为主键。字段约束中只能同时存在一个主键
orderBy term: OrderTerm? = nil, // 当该字段是主键时,存储顺序是升序还是降序
isAutoIncrement: Bool = false, // 当该字段是主键时,其是否支持自增。只有整型数据可以定义为自增。
onConflict conflict: Conflict? = nil, // 当该字段是主键时,若产生冲突,应如何处理
isNotNull: Bool = false, // 该字段是否可以为空
isUnique: Bool = false, // 该字段是否可以具有唯一性
defaultTo defaultValue: ColumnDef.DefaultType? = nil // 该字段在数据库内使用什么默认值
)

自增

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let autoIncrementObject = Sample()
autoIncrementObject.isAutoIncrement = true

// 插入自增数据
try database.insert(objects: autoIncrementObject, intoTable: "sampleTable")
print(autoIncrementObject.lastInsertedRowID) // 输出 1

// 再次插入自增数据
try database.insert(objects: autoIncrementObject, intoTable: "sampleTable")
print(autoIncrementObject.lastInsertedRowID) // 输出 2

// 插入非自增的指定数据
let specificObject = Sample()
specificObject.identifier = 10
try database.insert(objects: specificObject, intoTable: "sampleTable")

索引

它用于定于针对单个或多个字段的索引,索引后的数据在能有更高的查询效率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Sample: TableCodable {
var identifier: Int? = nil
var description: String? = nil
var multiIndexPart1: Int = 0
var multiIndexPart2: Int = 0

enum CodingKeys: String, CodingTableKey {
typealias Root = Sample
static let objectRelationalMapping = TableBinding(CodingKeys.self)
case identifier
case description
case multiIndexPart1
case multiIndexPart2

static var indexBindings: [IndexBinding.Subfix: IndexBinding]? {
return [
"_uniqueIndex": IndexBinding(isUnique: true, indexesBy: identifier),
"_descendingIndex": IndexBinding(indexesBy: description.asIndex(orderBy: .descending)),
"_multiIndex": IndexBinding(indexesBy: multiIndexPart1, multiIndexPart2.asIndex(orderBy: .ascending))
]
}
}
}

完整的索引名为表名+索引后缀,如:表 “sampleTable” 的索引分别为 “sampleTable_uniqueIndex”、”sampleTable_descendingIndex” 和 “sampleTable_multiIndex”。

表约束

它用于定于针对多个字段或表本身的约束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Sample: TableCodable {
var identifier: Int? = nil
var multiPrimaryKeyPart1: Int = 0
var multiPrimaryKeyPart2: Int = 0
var multiUniquePart1: Int = 0
var multiUniquePart2: Int = 0

enum CodingKeys: String, CodingTableKey {
typealias Root = Sample
static let objectRelationalMapping = TableBinding(CodingKeys.self)
case identifier
case multiPrimaryKeyPart1
case multiPrimaryKeyPart2
case multiUniquePart1
case multiUniquePart2

static var tableConstraintBindings: [TableConstraintBinding.Name: TableConstraintBinding]? {
let multiPrimaryBinding =
MultiPrimaryBinding(indexesBy: multiPrimaryKeyPart1.asIndex(orderBy: .descending), multiPrimaryKeyPart2)
let multiUniqueBinding =
MultiUniqueBinding(indexesBy: multiUniquePart1, multiUniquePart2.asIndex(orderBy: .ascending))
return [
"MultiPrimaryConstraint": multiPrimaryBinding,
"MultiUniqueConstraint": multiUniqueBinding
]
}
}
}

1.MultiPrimaryBinding: 联合主键约束

2.MultiUniqueBinding: 联合唯一约束

3.CheckBinding: 检查约束

4.ForeignKeyBinding: 外键约束

虚拟表映射

它用于定于虚拟表以进行全文搜索等特性。

增删改查

插入操作

插入操作有 “insert” 和 “insertOrReplace” 两个接口。故名思义,前者只是单纯的插入数据,当数据出现冲突时会失败,而后者在主键一致时,新数据会覆盖旧数据。
propertyConvertibleList 插入指定字段

删除操作

1
2
3
4
5
6
func delete(fromTable table: String, // 表名
where condition: Condition? = nil, // 符合删除的条件
orderBy orderList: [OrderBy]? = nil, // 排序的方式
limit: Limit? = nil, // 删除的个数
offset: Offset? = nil // 从第几个开始删除
) throws

将 table 表内,满足 condition 的数据,按照 orderList 的方式进行排序,然后从头开始第 offset 行数据后的 limit 行数据删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 删除 sampleTable 中所有 identifier 大于 1 的行的数据
try database.delete(fromTable: "sampleTable",
where: Sample.Properties.identifier > 1)

// 删除 sampleTable 中 identifier 降序排列后的前 2 行数据
try database.delete(fromTable: "sampleTable",
orderBy: Sample.Properties.identifier.asOrder(by: .descending),
limit: 2)

// 删除 sampleTable 中 description 非空的数据,按 identifier 降序排列后的前 3 行的后 2 行数据
try database.delete(fromTable: "sampleTable",
where: Sample.Properties.description.isNotNull(),
orderBy: Sample.Properties.identifier.asOrder(by: .descending),
limit: 2,
offset: 3)

// 删除 sampleTable 中的所有数据
try database.delete(fromTable: "sampleTable")

更新操作

update with object
update with row

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let object = Sample()
object.description = "update"

// 将 sampleTable 中所有 identifier 大于 1 且 description 字段不为空 的行的 description 字段更新为 "update"
try database.update(table: "sampleTable"
on: Sample.Properties.description,
with: object,
where: Sample.Properites.identifier > 1 && Sample.Properties.description.isNotNull())

// 将 sampleTable 中前三行的 description 字段更新为 "update"
try database.update(table: "sampleTable"
on: Sample.Properties.description,
with: object,
limit: 3)
1
2
3
4
5
6
7
8
9
10
11
12
13
let row: [ColumnCodableBase] = ["update"]

// 将 sampleTable 中所有 identifier 大于 1 且 description 字段不为空 的行的 description 字段更新为 "update"
try database.update(table: "sampleTable"
on: Sample.Properties.description,
with: row,
where: Sample.Properites.identifier > 1 && Sample.Properties.description.isNotNull())

// 将 sampleTable 中前三行的 description 字段更新为 "update"
try database.update(table: "sampleTable"
on: Sample.Properties.description,
with: row,
limit: 3)

查找操作

  • getObjects
  • getObject
  • getRows
  • getRow
  • getColumn
  • getDistinctColumn
  • getValue
  • getDistinctValue

对象查找

1
2
3
4
5
6
7
8
9
10
// 返回 sampleTable 中的所有数据
let allObjects: [Sample] = try database.getObjects(fromTable: "sampleTable")

// 返回 sampleTable 中 identifier 小于 5 或 大于 10 的行的数据
let objects: [Sample] = try database.getObjects(fromTable: "sampleTable",
where: Sample.Properties.identifier < 5 || Sample.Properties.identifier > 10)

// 返回 sampleTable 中 identifier 最大的行的数据
let object: Sample? = try database.getObject(fromTable: "sampleTable",
orderBy: Sample.Properties.identifier.asOrder(by: .descending))

注意: 由于对象查找操作使用了范型,因此需要显式声明返回值的类型以匹配范型

对象部分查询

1
2
let objects: [Sample] = try database.getObjects(fromTable: "sampleTable", 
on: Sample.Properties.identifier)

注意:这里只获取了 identifier 字段,而没有获取 description 的值。这就可能与 Swift 本身存在冲突。 Swift 规定了对象创建时,必须初始化所有成员变量。而进行对象部分查询时,则可能出现某部分变量没有变查询,因此无法初始化的情况。因此,对于可能不被查询的成员变量,应将其类型定义为可选值。
倘若开发者不确定哪些字段可能会被进行对象部分查询,可以将所有字段都定义为可选。

值查询操作

identifier description
1 “sample1”
2 “sample2”
3 “sample3”
4 “sample4”
5 “sample5”
6 “sample6”
  • getRows 接口获取整个矩阵的所有内容,即返回值为二维数组。
  • getRow 接口获取某一横行的数据,即返回值为一维数组。
  • getColumn 接口获取某一纵列的数据,即返回值为一维数组。
  • getDistinctColumngetColumn 类似,但它会过滤掉重复的值。
  • getValue 接口获取矩阵中某一个格的内容。
  • getDistinctValuegetValue 类似,但它会过滤掉重复的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 获取所有内容
let allRows = try database.getRows(fromTable: "sampleTable")
print(allRows[row: 2, column: 0].int32Value) // 输出 3

// 获取第二行
let secondRow = try database.getRow(fromTable: "sampleTable", offset: 1)
print(secondRow[0].int32Value) // 输出 2

// 获取 description 列
let descriptionColumn = try database.getColumn(on: Sample.Properties.description, fromTable: "sampleTable")
print(descriptionColumn) // 输出 "sample1", "sample1", "sample1", "sample2", "sample2"

// 获取不重复的 description 列的值
let distinctDescriptionColumn = try database.getDistinctColumn(on: Sample.Properties.description, fromTable: "sampleTable")
print(distinctDescriptionColumn) // 输出 "sample1", "sample2"

// 获取第二行 description 列的值
let value = try database.getValue(on: Sample.Properties.description, offset: 1)
print(value.stringValue) // 输出 "sample1"

// 获取 identifier 的最大值
let maxIdentifier = try database.getValue(on: Sample.Properties.identifier.max(), fromTable: "sampleTable")

// 获取不重复的 description 的值
let distinctDescription = try database.getDistinctValue(on: Sample.Properties.description, fromTable: "sampleTable")
print(distinctDescription.stringValue) // 输出 "sample1"

数据库、表、事物

数据库 - Database、表 - Table 和 事务 - Transaction

  • 支持增删查改的便捷接口
  • 支持链式接口
  • 数据和状态共享
  • 线程安全

数据和状态共享,意味着对于 同一个路径的数据库 的 不同基础类,它们的标签、数据库是否打开、是否在进行读写操作等所有状态和数据都始终保持一致。

线程安全,意味着开发者可以在 任意线程 对 任意基础类 调用 任意接口,而不需要考虑数据库本身的线程安全问题。同时,WCDB Swift 会根据调用情况,并发执行操作,以达到更高的性能

文档