Tomo.Log()


[Vapor3]MySQLを使う

[09/13, 2018]

English / 日本語

Vapor3でMySQLを使って、いていろ苦労したのでチートシート的に残します。
とくに、複数のクエリを同時に扱ったり、複数のオブジェクトを同時に保存したりする方法が全然わからず、すごい時間を使いました😭

1.パッケージの追加

Package.swiftにMySQLのパッケージを追加します。

Package.swift

let package = Package(
    name: "Vapor3Practice",
    dependencies: [
        .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"),
        .package(url: "https://github.com/vapor/fluent-mysql.git", from: "3.0.0")
    ],
    targets: [
        .target(name: "App", dependencies: ["FluentMySQL", "Vapor"]),
        .target(name: "Run", dependencies: ["App"]),
        .testTarget(name: "AppTests", dependencies: ["App"])
    ]
)

リポジトリとターゲットの指定

追加したのは下記の2行です。

.package(url: "https://github.com/vapor/fluent-mysql.git", from: "3.0.0")
.target(name: "App", dependencies: ["FluentMySQL", "Vapor"]),

パッケージを更新したら、

vapor update

をして、パッケージをインストールしましょう。

2.Modelの作成

User.swiftを作成して、UserのModelを作る

final class User : Model {
    var id: Int?
    var name: String
    var email: String
    var passwordHash: String
}

extension User : MySQLModel {}
extension User : Migration { }

extensionに MySQLModelをつけることで、MySQLを扱えるようにする。

extension User : MySQLModel {}

IDがInt以外の場合は、「MySQLStringModel」「MySQLUUIDModel」も用意されているみたいです。

3.ConfigでDBの設定をする

configure.swiftのconfigure(...)でデータベースの設定とMigrationを登録をする

public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws {
    //FluentMySQLProviderの登録
    try services.register(FluentMySQLProvider())
    
    //DBの設定
    let mySQLConfig : MySQLDatabaseConfig = MySQLDatabaseConfig(hostname: "127.0.0.1", port:3306, username: "root", password: "password", database: "database")
    services.register(mySQLConfig)
    
    //Migrationの登録
    var migrations = MigrationConfig()
    migrations.add(model: User.self, database: .mysql)
    services.register(migrations)
}

MySQLDatabaseConfigでDBのhostやユーザーを指定できる。ここで指定したdatabseとユーザーは事前に作成しておく必要があります。

ここまでで、MySQLに繋がるようになります。
vapor runして下記が表示されればOK。

[ INFO ] Migrating 'mysql' database (....)
[ INFO ] Migrations complete (....)

4.各種クエリ

Create

let user = User(name: "name", email: "email", passwordHash: "ABC")
user.create(on: req)
    router.post("addUser") { req in
        let user = User(name: "name", email: "email", passwordHash: "ABC")
        return user.create(on: req)
    }

Select

モデルのquery(on: req)に続けて取得条件をつけて、取得します。

User.query(on: req).all()
  • all()
    • selectした結果の全てを取得
  • first()
    • selectした結果の1番最初の要素を取得
  • chunk(max: 12)
    • selectした結果の1番目から12番目までを取得(ただし結果が12未満の場合は全部)

filter で条件をつけて絞ることができる。

    router.get("user") { req in
        return User.query(on: req).filter(\User.email == "email").all()
    }

Update

 user.update(on: req)
    router.patch("user") { req in
        return user.update(on: req)
    }

Delete

userには全てのデータが入っている必要はなく、削除したいidの入ったインスタンスを用意すれば削除できる。

let user = User()
user.id = 12 //削除したいオブジェクトのidを指定する

user.delete(on: req)
    router.delete("user") { req in
        return user.delete(on: req)
    }

Where

whereはfliterを使うことで表現できる。FluentMySQLをインポートしないと、ビルドエラーになる。

import FluentMySQL

Userのnameカラムが"name"のuserを取得する例。

User.query(on: conn).filter(\User.name == "name").all()

column = value

「==」を変えることで条件を指定できる。

.filter(\User.name == "name") // column = value
.filter(\User.name != "name") // column != value
.filter(\User.name >= "name") // column >= value

//and so on...

In句

In句は「~~」で表現します。

.filter(\User.role ~~ [UserRole.admin, UserRole.writer,])

Limit

Limitはrangeで指定します。(内部的にLimitなのかはわかりませんw)

.range(20..<31)   // selectした結果の20番目から30番目を取得する例

Order by

Order byはsortで指定できます。

.sort(\User.id, .descending)

Joinなど

Joiなどは利用しているんですが、まだいまいち使いこなせていないので、もしもう少し理解できたら追記します。

 

5.逆引き辞典w

取得(Select)したオブジェクトの取り出し方

Selectしたarticleをテンプレートに渡して表示する例。

router.get("user") { req in
            return  Article.query(on: conn)
            .filter(\Article.status == Status.published).all().flatMap(to:View.self) {
                    articles in

                   //ここで何かする

                    return req.view().render("templatePath", articles)
            }
}

複数のオブジェクトを保存する

「model.create(on: req)」の部分を変更すれば、updateやdeleteもできます。

router.post("addUsers") { req in
        var models : [User] = []
        
        models.append(User(name: "user1", email: "email", passwordHash: "ABC"))
        models.append(User(name: "user2", email: "email", passwordHash: "ABC"))
        
        return  models.map{
            model -> Future<User> in
            return model.create(on: req)
            }.flatten(on: req).flatMap(to: View.self) {
                results in
               // resultsには[Articleが入っている]
                return req.view().render("templatePath", results)
        }
}

クエリを2個発行して結果を利用する

query()を変数に入れてflatMapで展開して、結果を利用することができます。flatMapは最大4つまでEventLoopFuture<>を展開できるようです。

router.get("allitems") { req in
    let alllArticles : EventLoopFuture<[Article]> = Article.query(on: req).all()
    let allUsers : EventLoopFuture<[User]> = User.query(on: req).all()
    
    flatMap(to: View.self, allArticles, allUsers) {
        articles, users in
        
        //ここで何かする
        
        return req.view().render("templatePath", results)
    }
}