🍩

【Go】新しいORM「Bun」を使ってみる(MySQL)

5 min read

はじめに

Goの新しいORMを見かけたので、使ってみました。
今回はCRUDだけです。

「Bun」とは

Bunは、Go用のSQLファーストのデータベースクライアントです。SQLファーストとは、ほとんどのSQLクエリを自動的にBun式にコンパイルでき、Bun式はSQLクエリのように見えることを意味します。

Bunの目的は、古き良きSQLを使用してクエリを記述できるようにし、結果を一般的なGo型(構造体、マップ、スライス、スカラー)にスキャンできるようにすることです。

らしいです。
※公式より引用

https://bun.uptrace.dev/guide/#how-it-works

GORMよりも早いみたいです。

Bunの良いところは公式にPlayGroundがあり、そこにSQL文を貼り付けると自動的にBunのコードに変換してくれるところが素晴らしい!(ただし取得のみ)
サブクエリとかJoinしまくりの複雑な取得も一発で解決します!!(これは本当に凄い!)

https://bun.uptrace.dev/playground/

環境

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をあまり変わらず使えそうという所感です。
慣れたら使いやすそう。
サブクエリとかもやってみたかったのですが、思ったより時間かかったので、また次回にします。

参考

https://bun.uptrace.dev/guide/getting-started.html#quick-start
https://pkg.go.dev/github.com/uptrace/bun
https://zenn.dev/tkithrta/articles/c6074e0f09eb90

Discussion

ログインするとコメントできます