WCDBSwift笔记

WCDBSwift笔记

1.模型绑定

WCDBSwift的模型绑定分为五部分: 字段映射, 字段约束, 索引, 表约束, 虚拟表映射.

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

1.字段映射

本节主要讲解了怎么去定义一个TableCodable类型的模板对象.

class Sample: TableCodable {
    var identifier: Int? = nil
    var description: String? = nil
    var offset: Int = 0
    var debugDescription: String? = nil
        
    enum CodingKeys: String, CodingTableKey {
        typealias Root = Sample
        static let objectRelationalMapping = TableBinding(CodingKeys.self)
        case identifier = "id"
        case description
        case offset = "db_offset"
    }
}
  1. 字段映射要求在类内定义CodingKeys的枚举类, 并遵循String和CodingTableKey;
  2. 枚举内需要列举每一个需要定义的字段;
  3. 对于变量名和表名不一样的情况, 可以使用别名进行映射, 如case ideatifity = 'id';
  4. 对于不需要写入数据库的字段则不需要写入映射里面.
  5. 对于变量名和SQLite保留关键字冲突的字段, 可以使用别名进行映射.

2.字段约束

字段约束是TableEncodable的一个可选函数, 可根据需求选择实现或不实现. 它用于定义针对单独字段的约束, 如主键约束, 非空约束, 唯一约束, 默认值等.

字段约束示例代码如下:

class Sample: TableCodable {
    var identifier: Int? = nil
    var description: String? = nil
        
    enum CodingKeys: String, CodingTableKey {
        typealias Root = Sample
        static let objectRelationalMapping = TableBinding(CodingKeys.self)
        case identifier
        case description

        static var columnConstraintBindings: [CodingKeys: ColumnConstraintBinding]? {
            return [
                identifier: ColumnConstraintBinding(isPrimary: true),
                description: ColumnConstraintBinding(isNotNull: true, defaultTo: "defaultDescription"),
            ]
        }
    }

    var isAutoIncrement: Bool = false // 用于定义是否使用自增的方式插入
    var lastInsertedRowID: Int64 = 0 // 用于获取自增插入后的主键值
}

字段约束通过使用CodingKeys到字段约束的字典实现, 定义每个CodingKeys对应的约束. ColumnConstraintBinding初始化函数声明如下:

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 // 该字段在数据库内使用什么默认值
)

定义了 isPrimary: 的字段,支持以自增的方式进行插入数据。但仍可以通过非自增的方式插入数据。

当需要进行自增插入时,对象需设置 isAutoIncrement 参数为 true,则数据库会使用 已有数据中最大的值+1 作为主键的值。

3.索引

索引是 TableEncodable 的一个可选函数,可根据需求选择实现或不实现。它用于定于针对单个或多个字段的索引,索引后的数据在能有更高的查询效率。

索引通过索引后的后缀与索引绑定的映射实现.

  1. 对于需要特别指明索引存储顺序的字段,可以通过 asIndex(orderBy:) 函数指定,如 description.asIndex(orderBy: .descending)。
  2. 对于具有唯一性的索引,可以通过 isUnique: 参数指定,如 IndexBinding(isUnique: true, indexesBy: identifier)。
  3. 对于由多个字段组成的联合索引,可以通过 indexesBy: 进行指定,如 (indexesBy: multiIndexPart1, multiIndexPart2.asIndex(orderBy: .ascending))

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

4.表约束

表约束是 TableEncodable 的一个可选函数,可根据需求选择实现或不实现。它用于定于针对多个字段或表本身的约束。

表约束通过约束名到表约束的映射实现.

  1. MultiPrimaryBinding: 联合主键约束;
  2. MultiUniqueBinding: 联合唯一约束;
  3. MultiUniqueBinding: 约束检查;
  4. ForeignKeyBinding: 外键约束;

5.数据库升级

对于映射字段

  1. 表中存在但模型绑定中未定义的字段, 会被忽略, 这个可以用来删除字段.
  2. 表不存在但在模型绑定中有定义的字段, 会被新增到表中. 这可用于新增字段.
  3. 对于需要重命名的字段, 可以通过别名的方式重新映射.

忽略字段并不会删除字段. 对于该字段的旧内容, 会持续存在在表中, 因此文件不会因此变小. 实际上, 数据库作为持续增长的二进制文件, 只有将其数据导出生成另一个新的数据库, 才有可能回收这个字段占用的控件. 对于新插入的数据, 该字段内容为空, 不会对性能产生可见的影响.

2.增删查改

1.插入操作

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

insert函数的原型:

// insert 和 insertOrReplace 函数只有函数名不同,其他参数都一样。
func insert<Object: TableEncodable>(
    objects: [Object], // 需要插入的对象。WCDB Swift 同时实现了可变参数的版本,因此可以传入一个数组,也可以传入一个或多个对象。
    on propertyConvertibleList: [PropertyConvertible]? = nil, // 需要插入的字段
    intoTable table: String // 表名
) throws

插入是最常用且比较容易操作卡顿的操作,因此 WCDB Swift 对其进行了特殊处理。 当插入的对象数大于 1 时,WCDB Swift 会自动开启事务,进行批量化地插入,以获得更新的性能。

2.删除操作

删除函数原型:

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

删除示例代码:

// 删除 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")

删除接口不会删除表本身,开发者需要调用 drop(table:) 接口删除表。

3.更新

更新的操作接口有两个, 函数原型如下:

func update<Object: TableEncodable>(
    table: String,
    on propertyConvertibleList: [PropertyConvertible],
    with object: Object,
    where condition: Condition? = nil,
    orderBy orderList: [OrderBy]? = nil,
    limit: Limit? = nil,
    offset: Offset? = nil) throws

func update(
    table: String,
    on propertyConvertibleList: [PropertyConvertible],
    with row: [ColumnEncodableBase],
    where condition: Condition? = nil,
    orderBy orderList: [OrderBy]? = nil,
    limit: Limit? = nil,
    offset: Offset? = nil) throws

4.查找接口

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

虽然查找的接口比较多, 但是大部分都是为了简化操作而提供的便捷接口. 实现上其实与update类似, 只有两种方式:

func getObjects<Object: TableDecodable>(
    on propertyConvertibleList: [PropertyConvertible],
    fromTable table: String,
    where condition: Condition? = nil,
    orderBy orderList: [OrderBy]? = nil,
    limit: Limit? = nil,
    offset: Offset? = nil) throws -> [Object]

func getObject<Object: TableDecodable>(
    on propertyConvertibleList: [PropertyConvertible],
    fromTable table: String,
    where condition: Condition? = nil,
    orderBy orderList: [OrderBy]? = nil,
    offset: Offset? = nil) throws -> Object?

3.数据库,表,事务

WCDB Swift的三个基础类: 数据库(Database), 表(Table), 事务(Transaction). 他们同事拥有以下特性:

  1. 支持增删改查的便捷接口;
  2. 支持链式接口;
  3. 数据和状态共享;
  4. 线程安全;

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

基础类共享数据和状态的本质是, 他们共享同一个Core, 而所有的操作都在这个Core上发生.

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

WCDB支持多线程读操作 或 单线程写多线程读 并发执行.

1.数据库

Database是WCDB最基础的类, 几乎所有的操作都由该类发起.

// 一般可以通过一个文件路径或者一个文件url初始化
let filePath = "~/Intermediate/Directories/Will/Be/Created/sample.db"
let databaseWithPath = Database(withPath: filePath)

let fileURL = URL(fileURLWithPath: filePath)
let databaseWithFileURL = Database(withFileURL: fileURL)

延迟初始化是WCDB的原则之一, 绝大部分数据只会在需要用到时候才创建并初始化. 数据库的打开就是一个例子:

let database = Database(withPath: filePath)
print(database.isOpened) // 输出 false
try database.create(table: "sampleTable", of: Sample.self) // 数据库此时会被自动打开
print(database.isOpened) // 输出 true

数据库会在第一次进行操作时,自动打开并初始化。开发者不需要手动调用。 关闭数据库一般也不需要开发者手动调用. 当没有指向database所有共享的core时, 数据库会自动关闭, 并回收内存.

2.表

Table指数据库中的一个表. 可以通过getTable接口获取.

let table = try database.getTable(named: "sampleTable", of: Sample.self) //  表不存在时会出错

// 返回值需指定为 [Sample] 类型以匹配范型
let objectsFromDatabase: [Sample] = try database.getObjects(fromTable: "sampleTable")

// table 已经指定了表名和模型绑定的类,因此可以直接获取
let objectsFromTable = try table.getObjects()

表相当于指定了表名和模型绑定类的 Database,其实质只是后者的简化版。

3.事务

事务一般用于提升性能保证数据原子性. Database和Table都能直接发起事务, 也可以通过Transaction更好的控制事务.

try database.run(transaction: {
    try database.insert(objects: object, intoTable: "sampleTable")
})

let table = try database.getTable(named: "sampleTable", of: Sample.self)
table.run(transaction: {
    try database.insert(objects: object)
})

// 与 Database、Table 类似,开发者可以保存 Transaction 变量
let transaction = try database.getTransaction()
transacton.run(transaction: {
    print(transaction.isInTransction) // 输出 true
    try transaction.insert(objects: object)
})

事务提升性能的实质是批量处理.

事务可以用来保证某些多线程操作的原子性. 保证线程安全.

4.语言集成查询

语言集成查询(WCDB Integrated Language Query,简称 WINQ),是 WCDB 的一项基础特性。它使得开发者能够通过 Swift 的语法特性去完成 SQL 语句。

let objects: [Sample] = try database.getObjects(fromTable: "sampleTable", where: Sample.Properties.idetifier > 1)

语言集成查询基于SQLite的SQL语法的实现. 只要是SQL支持的语句, 都能使用语言集成查询完成. 也因此, 语言集成查询具有和SQL语法一样的复杂性. 通过WCDB可以将已有的SQL转换为语言集成查询的写法.

Reference: https://github.com/Tencent/wcdb/wiki/Swift-%E8%AF%AD%E8%A8%80%E9%9B%86%E6%88%90%E6%9F%A5%E8%AF%A2

5.加密与配置

SQLite 支持的配置列表可参考其官方文档。

配置主要用以控制数据库的操作行为. WCDBSwift对SQLite数据库进行了基本的配置以满足WCDB的需求. 同事开发者也可以根据自己的需要, 自定义配置项.

加密功能可通过setCipher(key:pageSize:)接口开启.

其中,pageSize 是加密的页大小参数,SQLCipher 在 iOS 上默认使用 4096,macOS 上默认使用 1024。而 WCDB Swift 则在所有平台上都适用 4096,以获得更好的性能。开发者一般不需要做特别的修改。

let database = Database(withPath: filePath)
let password = "password".data(using: .ascii)

// 设置加密接口应在其他所有调用之前进行,否则会因无法解密而出错
database.setCipher(key: password)

try database.insert(objects: object, intoTable: "sampleTable")

注意:开启加密会有部分性能损耗<br /> 值得注意的是,设置密码是一件很慎重的事情。对于已经创建且存在数据的数据库,无论是原本未加密想要改为加密,还是已经加密想要修改密码,都是成本非常高的操作,因此不要轻易使用。更多相关信息可以参考官方文档。

一个示例demo: https://github.com/hell03W/WCDBSwiftDemo

Reference: https://github.com/Tencent/wcdb/wiki/Swift-%E5%85%B3%E4%BA%8E%20WCDB%20Swift

转载于:https://my.oschina.net/whforever/blog/1614045

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值