【Go】SQLBoilerの使い方【ORM】

# SQLBoiler とは

タイトル通り、Go言語のORMです。とりあえず[一次ソース](https://github.com/volatiletech/sqlboiler)貼っておくので一緒に見ましょう。[一次ソース](https://github.com/volatiletech/sqlboiler)です:santa:

GoのORMは[GORM](https://gorm.io/ja_JP/)しか使用経験がありませんが、サクッと自分の思ったことをまとめました。
SQLBoilerを使用したソースコードは[こちら](https://github.com/ishihaya/gopro)。

### 特徴
- 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を使用します。

- [データベース作成](https://www.dbonline.jp/mysql/database/index1.html)
- [MySQL User作成](https://www.dbonline.jp/mysql/user/index1.html)
- [権限付与](https://www.dbonline.jp/mysql/user/index6.html)

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

## 2. DB接続
次に、DBに接続します。

```go: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]は使用しているデータベースの種類を指定しています

```toml:sqlboiler.toml
pkgname="entity"
output="domain/entity"
[mysql]
  dbname = "exampledb"
  host   = "localhost"
  port   = 3306
  user   = "root"
  pass   = "password"
  sslmode= "false"
```

## 5.SQL文記述
[SQLの文法](https://qiita.com/Fendo181/items/2be9a0f28fb0805794b0)をもとに下のようなファイルを作成しました。記事も後半になって僕が優しくなくなってきたので解説はしません。
関係は user:review が1:多で、CONSTRAINT..の部分で外部キー接続もしています。

```sql: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とかのタスクランナー で自動化しておくと良いと思います。

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

```go: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のデータを取ってきます。
(ルーティングの記述は省略しています。)

```go: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
}
```

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

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

```:ターミナル
$ go run main go
```

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

```go: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の関係なら複数形にする)
以上になります。

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