はじめに
本記事は フューチャー Advent Calendar 2023 24日目の記事です。(クリスマスイブですね。)
今回はGolangのORM「Bun」を触ってみたのでその、個人的に気になっていた点である、データの挿入周りついて記事にしたいと思います。
Bunとは
BunはGo言語用のSQLファーストのORM(Object-Relational Mapping)ライブラリで、PostgreSQL、MySQL(MariaDBを含む)、MSSQL、SQLiteなどのデータベースシステムに対応しています。
以下公式サイトです。
Why Not ...? の章や公式サイトからも GORM と比較していることが多いです。
「Bun」は「gorm」と比較してBunは、余計な機能などが少なく、ライブラリのサイズが小さいことや、コードベースでの記述が少なく、シンプルであるため処理が高速です。
環境
go 1.19
github.com/uptrace/bun/dialect/pgdialect v1.1.16
github.com/uptrace/bun/driver/pgdriver v1.1.16
github.com/uptrace/bun/extra/bundebug v1.1.16
psql (PostgreSQL) 14.10
Bunのインストール
go get github.com/uptrace/bun@latest
DB接続
bunはdatabase/sql 上で動作するので、まず最初にsql.DB
を作成する必要があります。
今回はPostgresで実行します。また、データベース名はbunで作成しております。
// Open a PostgreSQL database.
sqldb, err := sql.Open("postgres", "user=postgres host=localhost dbname=bun sslmode=disable")
if err != nil {
panic(err)
}
defer sqldb.Close()
// 使用するデータベースに合わせて、第二引数を変える。
db := bun.NewDB(sqldb, pgdialect.New())
// クエリーフックを追加
db.AddQueryHook(bundebug.NewQueryHook(
bundebug.WithVerbose(true),
))
クエリーフックを追加することで、SQLを実行したクエリーが標準出力されるので追加することをおすすめします。
テーブル作成
Textテーブルを作成したいと思います。カラムはid
とcontent
でpkはid
です。まずはTextの構造体を作成します。
またbunではテーブルにエイリアスを指定することができます。今回は、t
を指定します。
type Text struct {
Id int64 `bun:",pk,autoincrement"`
Text string `bun:"text"`
bun.BaseModel `bun:"table:text,alias:t"`
}
実際テーブルを作成するコードは下記です。
データの中身は空で作成します。
res, err := db.NewCreateTable().Model((*Text)(nil)).Exec(context.TODO())
if err != nil {
panic(err)
}
クエリーフックを追加してあるため、実行すると下記のようなログが出力されます。
[bun] 18:44:56.599 CREATE TABLE 12.65ms CREATE TABLE "text" ("id" BIGSERIAL NOT NULL, "text" VARCHAR, PRIMARY KEY ("id"))
データ挿入
今回データのInsertは二つの方法で試しました。
1つ目がバルクインサートで2つ目がyamlファイルを使ったインサートです。
バルクインサート
Bunのバルクインサートは非常に簡単でスライスを使ってインサートすることができます。
res
には挿入されたデータ件数が渡されます。
texts := []Text{{Text: "hoge"}, {Text: "fuga"}}
res, err := db.NewInsert().Model(&texts).Exec(context.TODO())
if err != nil {
panic("バルクインサートに失敗しました")
}
fmt.Println(res)
実行結果
[bun] 19:14:09.499 INSERT 11.433ms INSERT INTO "text" ("id", "text") VALUES (DEFAULT, 'hoge'), (DEFAULT, 'fuga') RETURNING "id"
2 //resの値
YAMLファイルを使ったデータ挿入
自分が今回一番気になっていた点のYAMLファイルを使ったデータ挿入です。
公式ドキュメントに、Provide initial data for your application with YAML fixtures.
とあるようにYAMLファイルを使って初期データ挿入ができることが「Bun」の特徴の一つとして紹介されています。
構造体とYAMLファイルを以下のように記述しました。
type User struct {
ID int64 `bun:"id,pk,autoincrement"`
Name string `bun:"name"`
Email string `bun:"email"`
CreatedAt time.Time `bun:"created_at"`
}
type Org struct {
Name string `bun:"name"`
OwnerID int64 `bun:"owner_id"`
}
UserとOrgテーブルになる構造体です。
- model: User
rows:
- _id: smith
name: John Smith
email: john@smith.com
created_at: '{{ now }}'
- _id: doe
name: Jonh Doe
email: john@doe.com
created_at: '{{ now }}'
- model: Org
rows:
- name: "{{ $.User.smith.Name }}'s Org"
owner_id: '{{ $.User.smith.ID }}'
- name: "{{ $.User.doe.Name }}'s Org"
owner_id: '{{ $.User.doe.ID }}'
UserテーブルとOrgテーブルに挿入したいデータを記述しております。
テンプレート変数を使用することで簡単に挿入することができます。
実際にインサートする実装を書いていきます。
// モデルをデータベースに登録
db.RegisterModel((*User)(nil), (*Org)(nil))
// フィクスチャインスタンスの作成
fixture := dbfixture.New(db, dbfixture.WithRecreateTables())
// フィクスチャからデータをロード
err = fixture.Load(context.TODO(), os.DirFS("data"), "data.yaml")
if err != nil {
panic(err)
}
カレントディレクトリ配下にdata
を作成し、data.yamlを配置します。
実際に実行してみます。
実行結果
[bun] 19:40:43.084 DROP TABLE 8.783ms DROP TABLE IF EXISTS "users" CASCADE
[bun] 19:40:43.190 CREATE TABLE 106.285ms CREATE TABLE "users" ("id" BIGSERIAL NOT NULL, "name" VARCHAR, "email" VARCHAR, "created_at" TIMESTAMPTZ, PRIMARY KEY ("id"))
[bun] 19:40:43.194 INSERT 2.752ms INSERT INTO "users" ("id", "name", "email", "created_at") VALUES (DEFAULT, 'John Smith', 'john@smith.com', '2023-12-24 10:40:43.191087+00:00') RETURNING "id"
[bun] 19:40:43.195 INSERT 924µs INSERT INTO "users" ("id", "name", "email", "created_at") VALUES (DEFAULT, 'Jonh Doe', 'john@doe.com', '2023-12-24 10:40:43.19418+00:00') RETURNING "id"
[bun] 19:40:43.195 DROP TABLE 459µs DROP TABLE IF EXISTS "orgs" CASCADE
[bun] 19:40:43.219 CREATE TABLE 23.824ms CREATE TABLE "orgs" ("name" VARCHAR, "owner_id" BIGINT)
[bun] 19:40:43.240 INSERT 20.324ms INSERT INTO "orgs" ("name", "owner_id") VALUES ('John Smith''s Org', 1)
[bun] 19:40:43.241 INSERT 883µs INSERT INTO "orgs" ("name", "owner_id") VALUES ('Jonh Doe''s Org', 2)
WithRecreateTables
メソッドを使用したことで、最初にDROP TABLEされてからCREATE TABLEされてインサートされるようになっています。実際にテーブルの中身を見てみます。
データが挿入されていることが確認できました。
フィクスチャを使用すれば、開発やテスト中に一貫したデータセットを簡単に再利用できることで簡単にテストできるようになると感じました。
最後に
フィクスチャを使って一貫したテストデータセットを再利用できることでテスト用データをテスト毎に書かなく済むのではないかと思いました。
サイズが小さいのでgormのようなORMよりサイズが小さいのはORM選定の一つの判断基準になると思います。
余談ですが、「Bun」で検索するとJavaScriptの「Bun」関連のサイトがひっかかるので、「Bun Golang」や「Bun ORM」で検索するのがおすすめです。
明日は @wsysuper さんの記事です。