10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Golangの新しいORM「Bun」を触ってみた

Last updated at Posted at 2023-12-24

はじめに

本記事は フューチャー 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テーブルを作成したいと思います。カラムはidcontentで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テーブルになる構造体です。

data.yaml
- 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されてインサートされるようになっています。実際にテーブルの中身を見てみます。

  • Userテーブル
    image.png

  • Orgテーブル
    image.png

データが挿入されていることが確認できました。
フィクスチャを使用すれば、開発やテスト中に一貫したデータセットを簡単に再利用できることで簡単にテストできるようになると感じました。

最後に

フィクスチャを使って一貫したテストデータセットを再利用できることでテスト用データをテスト毎に書かなく済むのではないかと思いました。
サイズが小さいのでgormのようなORMよりサイズが小さいのはORM選定の一つの判断基準になると思います。
余談ですが、「Bun」で検索するとJavaScriptの「Bun」関連のサイトがひっかかるので、「Bun Golang」や「Bun ORM」で検索するのがおすすめです。

明日は @wsysuper さんの記事です。

10
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?