Swift-Kuery-ORM 是一个为 Swift 构建的 ORM (对象关系映射) 库。 使用它可以简化模型对象与服务器的持久性。
Swift-Kuery-ORM 构建于 Swift-Kuery 之上,这意味着如果 ORM 的功能不足,可以使用 Swift-Kuery 自定义对数据库进行的 SQL 查询。
Swift-Kuery-ORM 的关键组成部分是协议 Model
。
让我们提出一个 struct 作为例子。 我们可以声明一个像这样的对象
struct Grade: Codable {
var course: String
var grade: Int
}
感谢 Kitura 2.0 中的 Codable Routing,我们将 struct 声明为 Codable
,以简化服务器上这些对象的 RESTful 路由。 Model
协议扩展了 Codable
的功能,使其与 ORM 协同工作。 在您的服务器应用程序中,您将像这样扩展您的对象
extension Grade: Model { }
现在您的 Grade
struct 符合 Model
,在您设置好数据库连接池并创建数据库表后,您将自动获得大量方便的对象函数。
需要检索 Grade
的所有实例吗? 您可以实现
Grade.findAll()
需要添加 Grade
的新实例吗? 如下所示
grade.save()
Model
协议是使用 ORM 的关键。 让我们逐步了解如何完全设置一个应用程序以使用 ORM。
按照 入门指南 创建 Kitura 服务器。 在此示例中,您将使用 Swift Kuery PostgreSQL 插件,因此您需要在本地计算机上运行 PostgreSQL,您可以使用 brew install postgresql
进行安装。 PostgreSQL 的默认端口是 5432。
将 Swift-Kuery-ORM 和 Swift-Kuery-PostgreSQL 添加到您应用程序的 Package.swift
中。 将 "x.x.x"
替换为最新的 Swift-Kuery-ORM
版本 和最新的 Swift-Kuery-PostgreSQL
版本。
dependencies: [
...
// Add these two lines
.package(url: "https://github.com/Kitura/Swift-Kuery-ORM.git", from: "x.x.x"),
.package(url: "https://github.com/Kitura/Swift-Kuery-PostgreSQL.git", from: "x.x.x"),
],
targets: [
.target(
name: ...
// Add these two modules to your target(s)
dependencies: [..., "SwiftKueryORM", "SwiftKueryPostgreSQL"]),
]
假设您想将 ORM 功能添加到名为 Application.swift
的文件中。 您需要在文件顶部添加以下 import 语句
import SwiftKueryORM
import SwiftKueryPostgreSQL
如前所述,我们建议您使用 Homebrew 在您的机器上设置 PostgreSQL。 您可以安装 PostgreSQL 并像这样设置您的表
brew install postgresql
brew services start postgresql
createdb school
在您的 Application.swift
文件中初始化您的数据库
let pool = PostgreSQLConnection.createPool(host: "localhost", port: 5432, options: [.databaseName("school")], poolOptions: ConnectionPoolOptions(initialCapacity: 10, maxCapacity: 50, timeout: 10000))
Database.default = Database(pool)
与之前一样,假设您将使用如下所示的 struct
struct Grade : Codable {
var course: String
var grade: Int
}
在您的 Application.swift
文件中,扩展 Grade
以符合 Model
extension Grade : Model {
// here, you can add any server-side specific logic to your object
}
现在,您需要创建您的表。 如果您在启动服务器时配置数据库,则可以使用 createTableSync()
,它以同步方式运行。 如果您想使用异步函数,可以在其他地方使用 createTable()
。 您可以像这样实现这些函数中的任何一个
do {
try Grade.createTableSync()
} catch let error {
// Error
}
重要的是要注意,如果您已经创建了您的表,这将在此处抛出一个错误。
您的应用程序现在可以使用 Model
协议中提供的所有功能了。 如果您想查看使用 Codable Routing 的 ORM 的完整工作示例,请访问我们的 FoodTracker 示例。
让我们介绍一下您现在可以使用的所有功能。
如果您想将新对象保存到数据库,您必须创建该对象并使用 save()
函数
let grade = Grade(course: "physics", grade: 80)
grade.save { grade, error in
...
}
您还可以选择将新保存对象的 ID 传递到您的闭包中。 将其添加到参数集合中,如下所示
grade.save { (id: Int?, grade: Grade?, error: RequestError?) in
...
}
如果您有对象现有记录的 id,并且您想使用对象更新该记录,则可以使用 update()
函数来执行此操作
let grade = Grade(course: "physics", grade: 80)
grade.course = "maths"
grade.update(id: 1) { grade, error in
...
}
如果您想查找特定对象,并且您有它的 id,您可以使用 find()
函数来检索它
Grade.find(id: 1) { result, error in
...
}
如果您想检索特定对象的所有实例,您可以将 findAll()
用作您尝试检索的类型的静态函数
Grade.findAll { (result: [Grade]?, error: RequestError?) in
...
}
您还可以以不同的方式和格式形成您的结果,如下所示
Grade.findAll { (result: [(Int, Grade)]?, error: RequestError?) in
...
}
Grade.findAll { (result: [Int: Grade]?, error: RequestError?) in
...
}
如果您想删除一个对象,并且您有它的 id,您可以像这样使用 delete()
函数
Grade.delete(id: 1) { error in
...
}
如果您感到大胆,并且想从数据库中删除对象的所有实例,您可以使用类型上的静态函数 deleteAll()
Grade.deleteAll { error in
...
}
ORM 定义了 Model
的扩展,它提供了许多 public static executeQuery(…)
函数。 这些可以用于在模型中创建自定义函数,以执行更复杂的数据库操作。 下面的示例定义了一个 Person 模型,其中包含一个自定义函数,该函数将检索所有年龄 > 20 的记录
// define the Person struct
struct Person: Codable {
var firstname: String
var surname: String
var age: Int
}
// extend Person to conform to model and add overTwenties function
extension Person: Model {
// Define a synchronous function to retrieve all records of Person with age > 20
public static func getOverTwenties() -> [Person]? {
let wait = DispatchSemaphore(value: 0)
// First get the table
var table: Table
do {
table = try Person.getTable()
} catch {
// Handle error
}
// Define result, query and execute
var overTwenties: [Person]? = nil
let query = Select(from: table).where("age > 20")
Person.executeQuery(query: query, parameters: nil) { results, error in
guard let results = results else {
// Handle error
}
overTwenties = results
wait.signal()
return
}
wait.wait()
return overTwenties
}
}
或者,您可以定义一个异步的 getOverTwenties 函数
public static func getOverTwenties(oncompletion: @escaping ([Person]?, RequestError?)-> Void) {
var table: Table
do {
table = try Person.getTable()
} catch {
// Handle error
}
let query = Select(from: table).where("age > 20")
Person.executeQuery(query: query, parameters: nil, oncompletion)
}
可以以类似于以下方式调用
Person.getOverTwenties() { result, error in
guard let result = result else {
// Handle error
}
// Use result
}
如果您想了解更多关于如何自定义查询的信息,请查看 Swift-Kuery 存储库以获取更多信息。
ORM 有几个选项可用于标识模型的实例。
如果您在定义 Model
时没有指定 ID 属性,无论是通过使用 idColumnName
属性还是默认名称 id
,则 ORM 将在模型的数据库表中创建一个名为 id
的自动递增列,例如。
struct Person: Model {
var firstname: String
var surname: String
var age: Int
}
该模型不包含 ID 的属性。 ORM 提供了一个特定的 save
API,它将返回分配的 ID。 重要的是要注意,ORM 不会以任何方式将返回的 ID 链接到 Model 的实例; 如果有必要,您负责维护这种关系。 下面是检索上面定义的 Person
模型的实例的 ID 的示例
let person = Person(firstname: "example", surname: "person", age: 21)
person.save() { (id: Int?, person, error) in
guard let id = id, let person = person else{
// Handle error
return
}
// Use person and id
}
编译器要求您声明完成处理程序接收的 ID 的类型; 对于已自动分配的 ID,该类型应为 Int?
。
您可以通过将 id
属性添加到您的模型来自己管理 ID 的分配。 您可以通过定义 idColumnName
来自定义此属性的名称。 例如
struct Person: Model {
var myIDField: Int
var firstname: String
var surname: String
var age: Int
static var idColumnName = "myIDField"
static var idColumnType = Int.self
}
当使用以这种方式定义的 Model
时,您负责 ID 的分配和管理。 下面是保存上面定义的 Person
模型的实例的示例
let person = Person(myIDField: 1, firstname: "example", surname: "person", age: 21)
person.save() { (person, error) in
guard let person = person else {
// Handle error
return
}
// Use newly saved person
}
将您的 ID 属性声明为可选允许 ORM 在保存模型时自动分配 ID。 如果 ID 的值为 nil
,则数据库将分配一个自动递增的值。 目前,这仅支持 Int?
类型。
您可以改为提供一个显式值,该值将代替自动分配使用。
可选 ID 必须通过定义 idKeypath: IDKeyPath
属性来标识,如下例所示
struct Person: Model {
var id: Int?
var firstname: String
var surname: String
var age: Int
static var idKeypath: IDKeyPath = \Person.id
}
在上面的例子中,Model
是用与默认 idColumnName
值匹配的 ID 属性定义的,但是如果您希望使用备用名称,您必须相应地定义 idColumnName
。
下面是保存上面定义的 Person
的实例的示例,包括显式定义的 ID 和没有 ID 的情况
let person = Person(id: nil, firstname: “Banana”, surname: “Man”, age: 21)
let specificPerson = Person(id: 5, firstname: “Super”, surname: “Ted”, age: 26)
person.save() { (savedPerson, error) in
guard let newPerson = savedPerson else {
// Handle error
}
print(newPerson.id) // Prints the next value in the databases identifier sequence, eg. 1
}
specificPerson.save() { (savedPerson, error) in
guard let newPerson = savedPerson else {
// Handle error
}
print(newPerson.id) // Prints 5
}
注意 - 当使用手动或可选 ID 属性时,您应该准备好处理违反唯一标识符约束的情况。 如果您尝试保存一个已经存在的 ID 的模型,或者在 Postgres 的情况下,如果自动递增的值与先前显式插入的 ID 冲突,则可能会发生这种情况。
默认情况下,您的模型上声明为 Date
的任何属性都将编码和解码为 Double
。
您可以通过覆盖属性 dateEncodingStrategy
的默认值来更改此行为。 dateEncodingStrategy 将应用于模型上的所有 Date 属性。
下面的示例定义了一个模型,该模型将具有其编码和解码为时间戳的 Date 属性
struct Person: Model {
static var dateEncodingFormat: DateEncodingFormat = .timestamp
var firstname: String
var surname: String
var age: Int
var dob: Date
}
有关更多信息,请访问我们的 API 参考。
我们喜欢谈论服务器端 Swift 和 Kitura。 加入我们的 Slack 来认识团队!
此库根据 Apache 2.0 获得许可。 完整的许可证文本可在 LICENSE 中找到。