【Go】新しいORM「Bun」を使ってみる(MySQL)
はじめに
Goの新しいORMを見かけたので、使ってみました。
今回はCRUDだけです。
「Bun」とは
Bunは、Go用のSQLファーストのデータベースクライアントです。SQLファーストとは、ほとんどのSQLクエリを自動的にBun式にコンパイルでき、Bun式はSQLクエリのように見えることを意味します。
Bunの目的は、古き良きSQLを使用してクエリを記述できるようにし、結果を一般的なGo型(構造体、マップ、スライス、スカラー)にスキャンできるようにすることです。
らしいです。
※公式より引用
GORMよりも早いみたいです。
Bunの良いところは公式にPlayGroundがあり、そこにSQL文を貼り付けると自動的にBunのコードに変換してくれるところが素晴らしい!(ただし取得のみ)
サブクエリとかJoinしまくりの複雑な取得も一発で解決します!!(これは本当に凄い!)
環境
go 1.17
github.com/go-sql-driver/mysql v1.6.0
github.com/uptrace/bun v1.0.13
github.com/uptrace/bun/dialect/mysqldialect v1.0.13
ドライバーの設定
今回はDockerで立てたMySQLを利用します。
package main
import (
"context"
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/mysqldialect"
)
func main() {
engine, err := sql.Open("mysql", "root:root@tcp([127.0.0.1]:3306)/sample_db?charset=utf8mb4&parseTime=true")
if err != nil {
panic(err)
}
db := bun.NewDB(engine, mysqldialect.New())
}
// 動かす時にコメントインする
// dropTable(db)
// createTable(db)
// insertOne(db)
// insertAll(db)
// getOne(db)
// getAll(db)
// delete(db)
// update(db)
_ "github.com/go-sql-driver/mysql"
と"github.com/uptrace/bun/dialect/mysqldialect"
はgo mod tidyだけでは自動的に入らなかったので、手動で追記をしてください。
上記でdbという名前でドライバーが作成されたので、メソッドに渡していきます。
テーブル
今回利用する構造体です。
type User struct {
Id int64 `bun:"id"`
Name string `bun:"name"`
Age int `bun:"age"`
Password string `bun:"password"`
}
Create Table
まずはテーブルを作成します。
func createTable(db *bun.DB) {
_, err := db.NewCreateTable().Model((*User)(nil)).Exec(context.Background())
if err != nil {
panic(err)
}
fmt.Println("createTable")
}
結果
空のusersテーブルが作成されました。
テーブル名を指定しない場合は、構造体の名前の複数形になるようです。
Drop Table
テーブルの削除です。
func dropTable(db *bun.DB) {
_, err := db.NewDropTable().Model((*User)(nil)).Exec(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println("dropTable")
}
結果
先程のusersテーブルが削除されました。
Insert
もう一度CreateTableでusersテーブルを削除してから、
次はInsertをやって行きます。
以下では、1つのレコードを追加する場合(insertOne)と複数のレコードを追加する場合(insertAll)です。
Modelの引数に値を渡すのですが、構造体でもスライスでもOKです。
構造体の場合、1つのレコードが作成され、スライスで複数渡した場合は複数のレコードが作成されます。
func insertOne(db *bun.DB) {
user := User{
Name: "太郎",
Password: "パスワード",
Age: 20,
}
_, err := db.NewInsert().Model(&user).Exec(context.Background())
if err != nil {
panic(err)
}
fmt.Println("insertOne")
}
func insertAll(db *bun.DB) {
user2 := User{
Name: "花子",
Password: "パスワード",
Age: 25,
}
user3 := User{
Name: "りょう",
Password: "パスワード",
Age: 30,
}
users := []User{user2, user3}
_, err := db.NewInsert().Model(&users).Exec(context.Background())
if err != nil {
panic(err)
}
fmt.Println("insertAll")
}
結果
空のテーブルに3つのレコードが追加されました。
Select
次は取得です。
取得はいろんな書き方があるようです。
以下は一番シンプルな取り方です。
以下の「getOne」「getAll」はどちらもScanを利用しています。
Xormみたいに単体取得と複数取得でメソッドは変わらず、同じScanを利用するようです。
Modelの引数が構造体なのか、スライスなのかと、Whereの条件で指定するようです。
Modelの引数に渡した構造体などに、DBから取得したデータが代入されます。
func getOne(db *bun.DB) {
user := User{}
err := db.NewSelect().Model(&user).Where("id = 1").Scan(context.Background())
if err != nil {
panic(err)
}
fmt.Println("getOne", user)
}
func getAll(db *bun.DB) {
users := []User{}
err := db.NewSelect().Model(&users).OrderExpr("id ASC").Scan(context.Background())
if err != nil {
panic(err)
}
fmt.Println("getAll", users)
}
出力結果
getOne {1 太郎 20 パスワード}
getAll [{1 太郎 20 パスワード} {2 花子 25 パスワード} {3 りょう 30 パスワード}]
Delete
次は削除です。
ここではid = 1のレコードを削除します。
func delete(db *bun.DB) {
user := User{}
_, err := db.NewDelete().Model(&user).Where("id = 1").Exec(context.Background())
if err != nil {
panic(err)
}
fmt.Println("delete")
}
結果
id = 1のレコードが削除されました。
Update
次は更新です。
id = 2のレコードの年齢を更新します。
func update(db *bun.DB) {
user := User{}
_, err := db.NewUpdate().Model(&user).Set("age = 40").Where("id = ?", 2).Exec(context.Background())
if err != nil {
panic(err)
}
fmt.Println("update")
}
結果
id = 2のレコードが更新され、
花子の年齢が40に変更になりました。
さいごに
Bunを使ってみましたが、Xormをあまり変わらず使えそうという所感です。
慣れたら使いやすそう。
サブクエリとかもやってみたかったのですが、思ったより時間かかったので、また次回にします。
参考
Discussion