はじめに
Go 言語を使った DB 処理に ORM として GORM/Gen を入れてみたので、その作業時に備忘録として記事にしました。
これから GORM/Gen を使った開発環境を構築する方の一助になれば幸いです。
なお GORM は Go 言語 における ORM。 Gen は GORM をサポートしたコードジェネレーターです。
環境と各ツール
バージョン | 備考 | |
---|---|---|
Windows11 Home | 22H2 | |
WSL | v2.0.9.0 |
wsl --version で確認 |
Rancher Desktop | v1.11.1 | メニュー > Help > About Rancher Desktop で確認 |
MySQL | v8.3.0 | mysql に接続時の出力メッセージで確認 |
Go | v1.22.1 |
go version で確認 |
GORM | v1.25.9 | go.mod で確認 |
Gen | v0.3.26 | go.mod で確認 |
導入
GORM のインストール
公式サイトのガイド に従いインストール。
GORM のインストール。
% go get -u gorm.io/gorm
MySQL 用ドライバのインストール。
% go get -u gorm.io/driver/mysql
Gen のインストール
公式サイトのガイド に従いインストール。
Gen のインストール。
% go get -u gorm.io/gen
query と model のコードを自動生成する
ジェネレータの作成
Gen の QuickStart に従いコード生成処理 (以降、ジェネレータ) を作成します。
この処理を実行することによって model
と query
のコードが自動生成されます。
なお本記事の処理対象である Contents
と Articles
は親子関係にあるので、ジェネレータにはこれらのリレーションを定義する設定を盛り込みます。
package main
import (
// ご自身の環境にあわせてドライバを変更してください
"gorm.io/driver/mysql"
"gorm.io/gen"
"gorm.io/gen/field"
"gorm.io/gorm"
)
func main() {
// コード生成ジェネレータの設定です
// 各パラメータはこちらをご参照ください
// https://gorm.io/gen/dao.html#gen-Config
g := gen.NewGenerator(gen.Config{
OutPath: "./gen/query", // 出力先
// 各モードのリファレンスはこちらです
// https://gorm.io/gen/dao.html#Generator-Modes
Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface,
})
// ご自身の環境に応じて Open の引数は変更してください
dbconf := "mysql:mysqladmin@tcp(172.30.10.100:3306)/mydb?charset=utf8mb4"
gormdb, _ := gorm.Open(mysql.Open(dbconf))
g.UseDB(gormdb)
// リレーションを張りたいテーブルを指定し親子関係を設定します
// Contents はArticles テーブルを子テーブルに持ちます
article := g.GenerateModel("Articles")
// Artilcles はContents テーブルを親テーブルに持ちます
content := g.GenerateModel("Contents",
gen.FieldRelate(field.HasMany, "Articles", article, &field.RelateConfig {
GORMTag: field.GormTag{
"foreignKey": []string{"ContentID"},
"references": []string{"ID"},
},
}),
)
// コードを生成する対象のモデルを設定して...
g.ApplyBasic(article, content)
// コード生成を実行します
g.Execute()
}
コードコメントにも記載しましたが、ジェネレータの各設定については下記をご参照ください。
またコード中に
// ご自身の環境に応じて Open の引数は変更してください
dbconf := "mysql:mysqladmin@tcp(172.30.10.100:3306)/mydb?charset=utf8mb4"
gormdb, _ := gorm.Open(mysql.Open(dbconf))
とありますとおり、上記はあくまで本記事で扱った環境を対象にした設定と処理です。
お試しになる際はご自身の環境に合わせた修正を行ってください。
ジェネレータ実行の前準備( docker 起動 )
model
と query
のジェネレータを作りましたのでこれを実行します。
が、その前にやることがあります。
今回題材に扱った環境 では docker 上で DB を起動していますので、ジェネレータ実行のためにまず docker を起動します。
# docker 起動. -d オプションで実行するのでプロセスはバックグラウンド起動します
% docker-compose -f docker-compose-backend.yml up -d
[+] Building 0.0s (0/0) docker:default
[+] Running 3/3
✔ Network react-and-echo-work_backend-work-net Created 0.0s
✔ Container restapi Started 0.0s
✔ Container storage Started 0.0s
ジェネレータ実行
docker コンテナが起動しましたら、ジェネレータのある path まで移動します。
( docker で起動した mysql に対するアクセスは docker コンテナに入らずとも行えるようにしてありますので、以降のコマンドも dokcer コンテナに入らずに実行できます )
# ジェネレータの配置 path に移動する
% cd ${repository_root}/restapi/api/orm # ${repository_root} はリポジトリルートです
% ls -l
total 8
-rw-r--r-- 1 user user 1570 Apr 13 20:16 orm_generator.go
これでコンテナ上でジェネレータを実行する準備ができました。
次のコマンドで実行してコードを生成します。
% go run orm_generator.go
go: downloading gorm.io/driver/mysql v1.5.6
go: downloading gorm.io/gen v0.3.25
go: downloading gorm.io/gorm v1.25.9
go: downloading golang.org/x/tools v0.20.0
go: downloading gorm.io/datatypes v1.2.0
go: downloading gorm.io/hints v1.1.2
go: downloading gorm.io/plugin/dbresolver v1.5.1
go: downloading github.com/jinzhu/now v1.1.5
go: downloading github.com/jinzhu/inflection v1.0.0
go: downloading golang.org/x/sync v0.7.0
go: downloading golang.org/x/mod v0.17.0
2024/04/14 11:57:02 got 8 columns from table <Articles>
2024/04/14 11:57:02 got 8 columns from table <Contents>
2024/04/14 11:57:02 Start generating code.
2024/04/14 11:57:02 generate model file(table <Articles> -> {model.Article}): /api/orm/gen/model/articles.gen.go
2024/04/14 11:57:02 generate model file(table <Contents> -> {model.Content}): /api/orm/gen/model/contents.gen.go
2024/04/14 11:57:02 generate query file: /api/orm/gen/query/articles.gen.go
2024/04/14 11:57:02 generate query file: /api/orm/gen/query/contents.gen.go
2024/04/14 11:57:02 generate query file: /api/orm/gen/query/gen.go
2024/04/14 11:57:02 Generate code done.
生成されたファイル
コマンドは正常に実行されました。ファイルが出力されているかを確認します。
% pwd
${repository_root}/restapi/api/orm/gen # ${repository_root} はリポジトリルートです
# user は実際にはログインしているユーザIDになります
% ls -l
合計 8
drwxr-xr-x 2 user user 4096 4月 9 22:18 model/
drwxr-xr-x 2 user user 4096 4月 9 22:18 query/
ディレクトリはできていますね。では、次のコマンドでファイルを確認します。
% ls -lR
.:
合計 8
drwxr-xr-x 2 user user 4096 4月 9 23:05 model/
drwxr-xr-x 2 user user 4096 4月 9 23:05 query/
./model:
合計 8
-rw-r----- 1 user user 1115 4月 9 23:05 articles.gen.go
-rw-r----- 1 user user 985 4月 9 23:05 contents.gen.go
./query:
合計 28
-rw-r----- 1 user user 11518 4月 9 23:05 articles.gen.go
-rw-r----- 1 user user 11336 4月 9 23:05 contents.gen.go
-rw-r----- 1 user user 2123 4月 9 23:05 gen.go
model, query にそれぞれのファイルが出力されていることが確認できました。
モデルとテーブルを比較してみる
DB 上のテーブル
さて、出力されたのは Contents
テーブル と Articles
テーブルに対するファイルです。
これらが実際のテーブルと合っているかをテーブル情報から確認します。
次のコマンドでコンテナに入ります。 69ffd527734c
は 前準備 で確認した mysql のコンテナID です。
% docker exec -it 69ffd527734c bash
bash-4.4#
コンテナにはいったので mysql に接続します。
bash-4.4# mysql -umysql -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 8.3.0 MySQL Community Server - GPL
Copyright (c) 2000, 2024, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
データベースを確認し、
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mydb |
| performance_schema |
+--------------------+
3 rows in set (0.02 sec)
処理対象の DB である mydb
に切り替えたら、
mysql> use mydb
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
テーブルの確認を行います。
mysql> show tables;
+----------------+
| Tables_in_mydb |
+----------------+
| Articles |
| Contents |
+----------------+
2 rows in set (0.00 sec)
ジェネレータで作成されたファイルと同名のテーブルとして Contents
テーブルと Articles
テーブルの存在が確認できました。
生成されたモデルと実際のテーブルとの比較
テーブルの確認ができたので、生成されたモデルと実際のテーブルの構成を確認します。
( 本記事作成時の状況です。今後 GitHub 上のテーブル定義が更新されることで本記事と齟齬がでる可能性があります )
Contents
まずは親である Contents
から見ていきます。
モデル
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
)
const TableNameContent = "Contents"
// Content mapped from table <Contents>
type Content struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
Title string `gorm:"column:title;not null" json:"title"`
Author string `gorm:"column:author" json:"author"`
Summary string `gorm:"column:summary" json:"summary"`
Deleted bool `gorm:"column:deleted;not null" json:"deleted"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
Articles []Article `gorm:"foreignKey:ContentID;references:ID" json:"articles"`
}
// TableName Content's table name
func (*Content) TableName() string {
return TableNameContent
}
テーブル
mysql> desc Contents;
+------------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-----------------+------+-----+---------+----------------+
| id | bigint unsigned | NO | PRI | NULL | auto_increment |
| title | varchar(255) | NO | | NULL | |
| author | varchar(255) | YES | | NULL | |
| summary | text | YES | | NULL | |
| deleted | tinyint(1) | NO | | NULL | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
+------------+-----------------+------+-----+---------+----------------+
7 rows in set (0.00 sec)
子テーブルである Articles
との関係性を示すものとして以下に注目です。
- 構造体のメンバとして
Articles
が定義されている -
Ariticles
は複数行持ち得るのでリストで表現されている - 両者のリレーションを示すべく
Contents
テーブルのID
が 外部キー としてArticles
のContentID
に設定されている
Articles
次に Articles
を見ます。
モデル
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
)
const TableNameArticle = "Articles"
// Article mapped from table <Articles>
type Article struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
ContentID int64 `gorm:"column:content_id;not null" json:"content_id"`
Subtitle string `gorm:"column:subtitle;not null" json:"subtitle"`
Body string `gorm:"column:body;not null" json:"body"`
Remarks string `gorm:"column:remarks" json:"remarks"`
Deleted bool `gorm:"column:deleted;not null" json:"deleted"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
}
// TableName Article's table name
func (*Article) TableName() string {
return TableNameArticle
}
テーブル
mysql> desc Articles;
+------------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-----------------+------+-----+---------+----------------+
| id | bigint unsigned | NO | PRI | NULL | auto_increment |
| content_id | bigint unsigned | NO | MUL | NULL | |
| subtitle | varchar(10245) | NO | | NULL | |
| body | text | NO | | NULL | |
| remarks | text | YES | | NULL | |
| deleted | tinyint(1) | NO | | NULL | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
+------------+-----------------+------+-----+---------+----------------+
8 rows in set (0.00 sec)
こちらは Contents
と違って特にみるべき点はありません。
子テーブルとしての Articles
が素直にモデルとして表現されています。
補足(boolean について)
本記事では RDBMS として mysql を使用していますが、mysql では boolean
を tinyint(1)
で表します。(MySQL 8.0 リファレンスマニュアル > 11.1.1 数値データ型の構文)
今回の例ではテーブル定義において deleted
を tinyint(1)
で定義していますが、生成されたモデルでは当該カラムがしっかりと bool
型で定義されています。
まとめにかえて
本記事では GORM と Gen を使った ORM のモデル定義・クエリの出力まで行いました。
公式のリファレンス、また先達の方々の記事のおかげで躓きながらも自分の環境で実現できましたこと、感謝申し上げます。
次はこの記事で作成したモデル・クエリを利用した CRUD について扱ってみます。
ソースコードについて
本記事であつかったコードは下記にあります。よろしければご参照ください。