GRDB入门使用

这篇笔记主要是介绍 GRDB 的基本使用(增删改查)。主要是感觉官方的文档有点杂乱,这篇笔记从入门使用者角度看起来更简单容易上手。

GRDB 是操作 SQLite 数据库的工具,特点是对多线程操作的应用提供了比较友好的支持,提供了比较方便的 API 的支持,很多时候是不需要写 SQL 语句的,也不用操心底层操作。

# 建立连接

GRDB 提供了两种访问 SQLite 数据库的方式 DatabaseQueue 和 DatabasePool,我们可以从中选择一种方式

import GRDB
// Pick one:
let dbQueue = try DatabaseQueue(path: "/path/to/database.sqlite")
let dbPool = try DatabasePool(path: "/path/to/database.sqlite")

两者的区别在于

  1. DatabasePool 允许并行的数据库访问,同时是通过 WAL mode 来打开数据库的
  2. DatabaseQueue 支持内存数据库

如果不确定的话可以先使用 DatabaseQueue 这种方式,后续可以再切到  DatabasePool。接下来演示使用 DatabaseQueue 这种方式

DatabaseQueue 建立连接的方式

let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
//数据库连接配置
var config = Configuration.init()
config.readonly = false
//初始化 DatabaseQueue 实例
let dbQueue = try DatabaseQueue(path: path, configuration: config)

DatabaseQueue 初始化的 API 里,如果 path 传 nil,则是在内存中创建数据库

有了数据库连接实例之后,我们开始对数据库进行操作。

# 操作

假设我们要保存一份玩家的数据在本地,需要存储玩家的姓名和分数,并根据需要更新玩家表。

# 建表

进行建表操作

try dbQueue.write({ db in
    try db.create(table: "player", body: { t in
        t.autoIncrementedPrimaryKey("id")
        t.column("name", .text).notNull()
        t.column("score", .integer).notNull()
    })
})

对上面代码进行简单说明

  1. func write<T>( updates: (Database) throws -> T) throws -> T API (opens new window) 是用来执行数据操作,并在执行完之后返回结果。
  2. 我们使用 Database 类型的实例进行建表操作,对应 API (opens new window) create(table:options:body:),在闭包里返回 TableDefinition参数。
  3. 我们使用 TableDefinition 实例来对数据库表进行配置,定义自增主键 id,添加数据库表列 func column(_ name: String,_ type: Database.ColumnType? = nil) -> ColumnDefinition 通过拿到列定义,来对数据库列进行限制。

# 插入数据

代码如下

struct Player: Codable, FetchableRecord, PersistableRecord {
    var id: Int64
    var name: String
    var score: Int
}
try dbQueue.write { db in
    try Player(id: 1, name: "Arthur", score: 100).insert(db)
    try Player(id: 2, name: "Barbara", score: 1000).insert(db)
}

代码说明

  1. Player 遵守 Codable (opens new window), FetchableRecord (opens new window), PersistableRecord (opens new window) 协议。PersistableRecord 是一种可以被持久化到数据中的类型, FetchableRecord (opens new window) 是可以把从数据库行中解出数据的类型,为了遵守前者需要提供 encode(to:) (opens new window) 的实现,后者需要提供 init(row:) 的实现。两者都有默认的实现所以遵守 Codable 协议就好了。
  2. PersistableRecord 提供了 insert 方法,所以可以调用 Player 的 insert 插入到 db 中。
  3. 关于 Codable 的详细说明,我自己的另外一篇博客有介绍 iOS开发中JSON-Model转化 (opens new window)

# 查询数据

代码如下

func queryPlayerDB(with name:String) throws -> Set<Player>? {
    let players = try dbQueue?.read { db in
        let sql = "SELECT * FROM player WHERE name = ?"
        return try Player.fetchSet(db, sql: sql, arguments: [name])
    }
    return players
}

代码说明

  1. 和之前的 dbQueue.write 不一样,这里使用读 API func read(_ value: (Database) throws -> T) throws -> T (opens new window),执行只读操作,在执行完成后返回读取结果。
  2. Player 数据结构遵守了 FetchableRecord 协议,直接调用 fetchSet 方法,传入 Sql 语句参数,系统就直接把查询的结果序列化好返回来了。
  3. 查询这儿还有很多别的相关的 API,比如 fetchAll(SELECT * FROM player)fetchOne(SELECT * FROM player LIMIT 1) 等 .. 可以根据需要来使用

# 修改数据

代码如下

let sql = "SELECT * FROM player WHERE name = ?"
var player = try dbQueue?.read { db in
    return try Player.fetchOne(db, sql: sql, arguments: [name])
}
player?.score = 2000
try dbQueue?.write({ db in
    try player?.saved(db)
})

代码说明

  1. 修改数据步骤 ① 查出要修改的数据 ② 修改查出来的数据 ③ 保存数据
  2. 保存数据使用的 API 是 save(_:onConflict:) (opens new window),这个 API 是执行 insert 和 update 语句。
  3. 还有一种更加简洁的方法如下,使用的 update(_:onConflict:) (opens new window) API.
try dbQueue.write { db in
    if var player = Player.fetchOne(db, id: 1) {
        player.score += 10
        try player.update(db)
    }
}

# 删除数据

代码如下

let player = try dbQueue?.read({ db in
    let sql = "select * from player where name = ?"
    return try Player.fetchOne(db, sql: sql, arguments: ["Barbara"])
})
try dbQueue?.write({ db in
    try player?.delete(db)
})

代码说明

  1. 查询出来要删除的数据
  2. 调用 func delete(_ db: Database) throws -> Bool (opens new window) API 来对实例进行删除。

以上是入门级别的使用,相对于 FMDB 来说少了很多不必要的代码。但是并没有深入到更高级的并行操作的支持。

参考地址:

  1. Swift Package Index Documentation-GRDB (opens new window) #非常详细的官方文档
  2. Github GRDB README (opens new window)