LoginSignup
24
17

More than 5 years have passed since last update.

Swift 4 GRDB.swift(SQLite)を使ってみた

Last updated at Posted at 2018-07-27

はじめに

Swift4で開発しているアプリから、SQLiteのデータベースを扱う必要があった為、
GRDB.swiftというライブラリを使ってみた。

最新の環境で動くサンプルコードが、ググっても情報が少なかったので、
本記事で基本的な使用方法を記載します。

導入

CocoaPodsからインストール

Podfile
 pod 'GRDB.swift'

エンティティ

サンプルコード用にユーザテーブルを定義します。
構造体にはCodable、RowConvertible、MutablePersistableのプロトコルを追加します。

UserTable.swift
struct UserTable : Codable, RowConvertible, MutablePersistable {
    // 今回は全カラムNotNullで
    var id: Int
    var name: String
    var age: Int
    var lastUpdate: Int64

    static var databaseTableName: String {
        return "tbl_user"  // テーブル名
    }
    
    static func create(_ db: Database) throws {
        try db.create(table: databaseTableName, body: { (t: TableDefinition) in
            t.column("id", .integer).primaryKey(onConflict: .replace, autoincrement: false)
            t.column("name", .text).notNull()
            t.column("age", .integer).notNull()
            t.column("lastUpdate", .integer).notNull()
        })
    }
}

複合主キーを設定する場合は下記のようにします。

t.column("id", .integer).notNull()
t.column("name", .text).notNull()
t.primaryKey(["id", "name"], onConflict: .replace)

ヘルパークラス

各テーブルの生成処理を一元管理する為にヘルパークラスを作成します。

DatabaseHelper.swift
class DatabaseHelper {
    
    private struct Const {
        /// データベースファイル名
        static let DB_FILE_NAME = "application.db"
        
        /// データベースファイルの格納先
        static let DB_FILE_PATH = "\(File.DocumentsPath)/\(DB_FILE_NAME)"
    }
    
    /// イニシャライザ
    init() {
        self.createDatabase()
    }
    
    /// データベースの操作
    func inDatabase(_ block: (Database) throws -> Void) -> Bool {
        do {
            // 初回実行時にデータベースファイルを生成する
            let dbQueue = try DatabaseQueue(path: Const.DB_FILE_PATH)
            try dbQueue.inDatabase(block)
        } catch let error {
            Log.e("Database Error: \(error.localizedDescription)")
            return false
        }
        return true
    }
    
    /// データベースの生成処理
    private func createDatabase() {
        if File.isExists(Const.DB_FILE_PATH) {
            // 既にファイルが作成済みなら何もしない
            return
        }
        let result = inDatabase { (db) in
            // テーブルの作成処理
            try UserTable.create(db)
            
            // ※複数テーブルがある場合はここに追加する
        }
        if !result {
            Log.e("create failed for Database.")
            File.remove(Const.DB_FILE_PATH)
        }
    }
}

データの取得

全件取得
var result :[UserTable] = []
let helper = DatabaseHelper()

helper.inDatabase { (db) in
    result = try UserTable.fetchAll(db)
}
// 出力
result.forEach { (it) in
    Log.d("\(it.id) - \(it.name) - \(it.age) - \(it.lastUpdate)")
}
条件指定

filterメソッドのSQL指定を使うと、引数にWHEAE句を設定できる。

let helper = DatabaseHelper()
var entity: UserTable?

helper.inDatabase { (db) in
    entity = try UserTable.filter(sql: "id = 3").fetchOne(db)
}
// 出力
if entity != nil {
    Log.d("\(entity!.id) - \(entity!.name) - \(entity!.age) - \(entity!.lastUpdate)")
}

ソースにSQLを書きたくない場合は、カラム名指定で条件を設定できる。

let helper = DatabaseHelper()

helper.inDatabase { (db) in
    try UserTable
        .filter(Column("id") == 2)
        .filter(Column("age") == 22)
        .fetchOne(db)
}

データの挿入/更新

let helper = DatabaseHelper()

helper.inDatabase { (db) in
    // エンティティを設定
    var entity = UserTable(id: 1,
                           name: "hoge",
                           age: 11,
                           lastUpdate: Date.currentTimeMillis())
    
    // 既に存在しているレコードなら上書きを行う
    try entity.insert(db)
}

データの削除

全件削除
let helper = DatabaseHelper()

helper.inDatabase { (db) in
    try UserTable.deleteAll(db)
}
条件指定
let helper = DatabaseHelper()

helper.inDatabase { (db) in
    try UserTable.filter(sql: "id = 1").fetchOne(db)
    try UserTable
        .filter(Column("id") == 2)
        .filter(Column("age") == 22)
        .fetchOne(db)
}

トランザクション

let helper = DatabaseHelper()

helper.inDatabase { (db) in
    // トランザクション開始
    try db.beginTransaction()
    do {
        // 更新処理
            .
            .
            .
        
    } catch let error {
        // ロールバック
        try db.rollback()
        throw error
    }
    // コミット
    try db.commit()
}

生SQLの実行

取得
let helper = DatabaseHelper()

helper.inDatabase { (db) in
    let rawSQL = """
        SELECT
          *
        FROM
          \(UserTable.databaseTableName)
    """

    let rows = try Row.fetchAll(db, rawSQL)

    // 出力
    rows.forEach { (row) in
        let id = row["id"]!
        let name = row["name"]!
        Log.d("\(id) - \(name)")
    }
}
更新
let helper = DatabaseHelper()

helper.inDatabase { (db) in
    let rawSQL = """
        UPDATE
          \(UserTable.databaseTableName)
        SET
          name = 'foo'
        WHERE
          id = 3
    """

    try db.execute(rawSQL)
}

プレースホルダ

SQL文の一部をパラメータに置き換える方法です。

GRDB.StatementArgumentsには配列を指定し、先頭から順にSQL内の「?」割り当てられます。

let rawSQL = """
    SELECT
      *
    FROM
      \(UserTable.databaseTableName)
    WHERE
      name = ?
"""
let args = GRDB.StatementArguments(["hoge"])
let rows = try Row.fetchAll(db, rawSQL, arguments: args, adapter: nil)

終わりに

基本的なデータ操作は上記のサンプルで確認できるかと思います。
まだまだ機能が多くて使いこなせておりませんが、手軽にSQLiteが扱えました。

24
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
17