【Go】SQLBoilerの使い方【ORM】


SQLBoiler とは

タイトル通り、Go言語のORMです。とりあえず一次ソース貼っておくので一緒に見ましょう。一次ソースです

GoのORMはGORMしか使用経験がありませんが、サクッと自分の思ったことをまとめました。
SQLBoilerを使用したソースコードはこちら

特徴

  • DBを作成してマンドを打つと構造体を自動で生成してくれて、InsertやQueryなどができるようになる
  • (メリット)他のORMよりパフォーマンスが良い(README参照)
  • (メリット)Goのstructとしても活用しやすい
  • (デメリット)ソースが少ない

実装手順

  1. DB作成
  2. DB接続
  3. 導入
  4. tomlファイル作成
  5. SQL文記述
  6. データのやりとり

開発環境

Mac OS Catalina 10.15.5
Go 1.14
MySQL 8.0.19

1.DB作成

(この記事にたどり着くような人は1と2は見なくてもサクッとできそうですが、、わかるわ。と思ったら手順3まで飛ばしてください。)

今回はMySQLを使用します。

をしてください。この手順にある処理はググったらいっぱい出てくると思うので詳しくは書きません。mysqlに入っていろいろするだけです。僕は結構優しいのでリンクは一応貼ってます。
権限付与はデータベースレベルで
grant all on DB名.* to 'ユーザー名' with grant option;
とかでいけると思います。

2. DB接続

次に、DBに接続します。

main.go
func New() *sql.DB {
  db, err := sql.Open("mysql", "ユーザー名:パスワード@/DB名?charset=utf8mb4&parseTime=true")
  if err != nil {
    panic(err)
  }
  return db
}

3.導入

ターミナル
go get -u github.com/volatiletech/sqlboiler
go get github.com/volatiletech/sqlboiler/drivers/sqlboiler-mysql

4.tomlファイル作成

ルートディレクトリにtomlファイルを作ります。

ターミナル
touch sqlboiler.toml

必要事項を記述します。

  • pkgname...作成したいパッケージ名を指定
  • output...ルートディレクトリから見て、ファイルを生成したいディレクトリの場所
  • dbname, user, pass ...DB名、ユーザー名、パスワード
  • [mysql]は使用しているデータベースの種類を指定しています
sqlboiler.toml
pkgname="entity"
output="domain/entity"
[mysql]
  dbname = "exampledb"
  host   = "localhost"
  port   = 3306
  user   = "root"
  pass   = "password"
  sslmode= "false"

5.SQL文記述

SQLの文法をもとに下のようなファイルを作成しました。記事も後半になって僕が優しくなくなってきたので解説はしません。
関係は user:review が1:多で、CONSTRAINT..の部分で外部キー接続もしています。

schema.sql
CREATE TABLE `users` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  `deleted_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `reviews` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `text` text NOT NULL,
  `user_id` bigint NOT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  `deleted_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  CONSTRAINT `user_reviews_fk` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

ユーザー名、パスワード、DB名を入れてスキーマを作成し、

ターミナル
$ mysqldef -uroot -ppassword exampledb < schema.sql

実行します。

$ sqlboiler mysql

上記2つのコマンドはSQLに変更を加えるときに使うので、Makefileとかのタスクランナー で自動化しておくと良いと思います。

自分の指定したディレクトリ以下に大量のファイルが生成されたら成功です。自分の作成したテーブルのファイルを見てるとこんなんがあると思います。

users.go
// 省略

type User struct {
    ID        int64     `boil:"id" json:"id" toml:"id" yaml:"id"`
    Name      string    `boil:"name" json:"name" toml:"name" yaml:"name"`
    CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"`
    UpdatedAt time.Time `boil:"updated_at" json:"updated_at" toml:"updated_at" yaml:"updated_at"`
    DeletedAt null.Time `boil:"deleted_at" json:"deleted_at,omitempty" toml:"deleted_at" yaml:"deleted_at,omitempty"`

    R *userR `boil:"-" json:"-" toml:"-" yaml:"-"`
    L userL  `boil:"-" json:"-" toml:"-" yaml:"-"`
}

// 省略

6. データのやり取り

ファイルの生成ができたら、いよいよデータのやりとりを行います。
INSERTもできますが今回はSELECTを扱います。事前にデータを入れているReviewsテーブルの(削除されていない)id=1のデータを取ってきます。
(ルーティングの記述は省略しています。)

main.go
  func handler(w http.ResponseWriter, r *http.Request) {
    conn := db.New()
    ctx := r.Context()
    var id int64 = 1
    review, err := entity.Reviews(
        // entityパッケージのReviewsテーブルからIDカラムを条件にidと等しいデータをSELECT
        entity.ReviewWhere.ID.EQ(id),
        entity.ReviewWhere.DeletedAt.IsNull(),
    ).One(ctx, conn)
    if err != nil {
        fmt.println(err)
        return
    }
    fmt.Println("----------review----------")
    fmt.Println(review)
    return
}

詳しい文法については一次ソースを参照してもらえるとわかるのではないかと思います。(上の例はQueryBuildingあたりに書いてる)

あとは実行すると、Reviewがprintされるはずです。

ターミナル
$ go run main go

手順としては終了ですが、最後に外部キーの例だけ載せておきます。
上記例でDBで外部キーで紐づけている前提で、

main.go
    review, err := entity.Reviews(
        entity.ReviewWhere.ID.EQ(id),
        entity.ReviewWhere.DeletedAt.IsNull(),
        qm.Load("User")
    ).One(ctx, conn)

 fmt.Println(review.R.User)

とすると、UsersテーブルのUserをEagerLoadingで取ってくることができます。(has_manyの関係なら複数形にする)
以上になります。

間違い誤字脱字等あればご指摘いただけると幸いです🙇‍♂️