はじめに
この前、あじ太郎は複数のプログラミング言語で配列宣言する記事を書いてみたら楽しかったので、今度はORMの比較記事も書いてみようと思いました。
ORMをテーマに記事を書くと分量が多くなりそうなので、一言語づつ取り上げます。
第一弾として今回はGo言語のORM「GORM」です。
サンプルコードと実行環境など
サンプルコードを以下にアップしました。
https://github.com/aji-taro/gorm-introduction
Dockerで試せるようにしています。
使い方はREADME.mdに記載したのでご参照ください。
データベースへの接続
GORMのページを参考にしつつ、サンプルコードでは以下のようにしてみました。
var db *gorm.DB
// データベースに接続する
// https://gorm.io/ja_JP/docs/connecting_to_the_database.html#MySQL
func connectDb() error {
dataSourceName := os.Getenv("DB_USER") + ":" + os.Getenv("DB_PASSWORD") +
"@tcp(" + os.Getenv("DB_HOST") + ":" + os.Getenv("DB_PORT") + ")/" +
os.Getenv("DB_NAME") +
"?charset=utf8mb4&parseTime=True&loc=Asia%2FTokyo"
var err error
db, err = gorm.Open(mysql.Open(dataSourceName), &gorm.Config{})
if err != nil {
return fmt.Errorf("gorm.Open error", err)
}
〜略〜
return nil
}
あじ太郎はPHPを使いますが、Laravel(Eloquent)ではIDやパスワードを設定ファイルに記述するのみで、明示的にOpenする実装とかはしたことがないような気がする。
こういうところは、プログラミング言語やフレームワークの設計者の考えが反映される部分かな。
モデルの定義
以下を定義しました。
// User テーブルのモデル
// ※参照:モデルを宣言する(https://gorm.io/ja_JP/docs/models.html)
// ※参照:Belongs To(https://gorm.io/ja_JP/docs/belongs_to.html#Belongs-To)
// ※ここでは、上記ページの構造体をコピペしたけどgorm.Modelを使ってもよいかも
type User struct {
ID uint // Standard field for the primary key
Name string // A regular string field
Email *string // A pointer to a string, allowing for null values
Age uint8 // An unsigned 8-bit integer
Birthday *time.Time // A pointer to time.Time, can be null
MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings
ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields
CompanyId uint
Belong Company `gorm:"foreignKey:CompanyId"`
CreatedAt time.Time // Automatically managed by GORM for creation time
UpdatedAt time.Time // Automatically managed by GORM for update time
ignored string // fields that aren't exported are ignored
}
type Company struct {
ID uint
Name string
}
以下、基本的なルールなど
- 構造体の名称はテーブル名をもとにする(usersテーブルの場合、User構造体にする。PHPのEloquentもクラス名からテーブルを判別するし、こういう仕様がトレンドなのかな。)
- idはIDにする
- その他のフィールドはパスカルケースにする(最初の文字が大文字なのはGORMの仕様というより、Go言語では大文字じゃないと他のパッケージからアクセスできないからか。)
- NULLを許容するカラムの場合、ポインタにする場合とsql.NullStringのように明確にNULLに対応した型もある
テーブル定義
Go言語のマイグレーションといえば、GORMのAutoMigrate、あとはgolang-migrateもありますが、サンプルはMySQLの機能でSQLを実行するようにしました。(SQLにしておくと他の記事でも流用しやすいかなと。)
SQLの内容は、モデルの定義をもとに考えています。
CREATE TABLE companies (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(15) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
INSERT INTO companies (id, name) VALUES (1, 'company-1');
INSERT INTO companies (id, name) VALUES (2, 'company-2');
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(15) NOT NULL,
email VARCHAR(255), /* UNIQUE にしてもいいかも */
age SMALLINT NOT NULL,
birthday DATE,
member_number VARCHAR(10),
activated_at DATETIME,
company_id BIGINT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
INSERT INTO users (id, name, age, company_id) VALUES (1, 'yamada taro', 20, 2);
各項目の桁数は適当です。
レコードの作成
いよいよクエリを実行!
レコードを挿入してみます。
参照:https://gorm.io/ja_JP/docs/create.html
// レコードの作成
// https://gorm.io/ja_JP/docs/create.html
fmt.Println("- Create")
now := time.Now()
newUser := User{Name: "Jinzhu", Age: 18, Birthday: &now}
createResult := db.Create(&newUser);
if createResult.Error != nil {
fmt.Println(createResult.Error)
return
}
fmt.Println("inserted data's primary key", newUser.ID)
fmt.Println("inserted records count", createResult.RowsAffected)
レコードの取得
GORMのページに記載されている例からいくつか試しました。
参照:https://gorm.io/ja_JP/docs/query.html
// レコードの取得 その 1
// https://gorm.io/ja_JP/docs/query.html
fmt.Println("- First")
user01 := User{}
if result := db.First(&user01, newUser.ID); result.Error != nil {
fmt.Println(result.Error)
return
}
fmt.Println("user01", user01)
// レコードの取得 その 2
fmt.Println("- Where")
user02 := User{}
if result := db.Where("name = ?", "jinzhu").First(&user02); result.Error != nil {
fmt.Println(result.Error)
return
}
fmt.Println("user02", user02)
レコードの更新
トランザクションも使ってみる。
参照(レコードの更新):https://gorm.io/ja_JP/docs/update.html
参照(トランザクション):https://gorm.io/ja_JP/docs/transactions.html#%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3
// トランザクション
// https://gorm.io/ja_JP/docs/transactions.html#%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3
fmt.Println("- Transaction")
db.Transaction(func(tx *gorm.DB) error {
// レコードの更新
// https://gorm.io/ja_JP/docs/update.html
fmt.Println("- Save")
user02.Name = "jinzhu 2"
user02.Age = 100
if result := tx.Save(&user02); result.Error != nil {
fmt.Println("error", result.Error)
return errors.New("rollback user2")
}
fmt.Println("update user02")
return nil
})
この記事では試してないけど、トランザクションのネストにも対応している。あれ?そもそもMySQL自体がトランザクションのネストに対応しているのかな?と思ったらSAVEPOINTが使えそう。(別の勉強になった。)
Eager Loading
Eloquent(Laravel)のリレーションはメソッドで定義するけど、GORMの場合は構造体にアソシエーションを含めて定義する。
参照:https://gorm.io/ja_JP/docs/preload.html
// Eager Loading(Preload)
// https://gorm.io/ja_JP/docs/preload.html#Preload
fmt.Println("- Preload")
preloadUser := User{}
if result := db.Preload("Belong").First(&preloadUser, 1); result.Error != nil {
fmt.Println("error", result.Error)
return
}
fmt.Println("preloadUser.Name", preloadUser.Name )
fmt.Println("preloadUser.Belong", preloadUser.Belong )
そういえば、過去にLaravelでEager Loadingでメモリを使いすぎるケースがあったので、GORMのPreloadにも注意することもあるのかなと思っていたら、カミナシさんがブログで Preload を使わずに平行処理にした事例をブログに掲載されてました。
https://kaminashi-developer.hatenablog.jp/entry/202204/backend-kaizen
ジョイン
実は、ジョインもアソシエーションを指定してdb.Joins("Company").Find(&users)
みたいな書き方ができるけど、あえて別の書き方を試してみました。
参照:https://gorm.io/ja_JP/docs/query.html#Joins
// ジョイン
// https://gorm.io/ja_JP/docs/query.html#Joins
fmt.Println("- Joins")
type AliasModel struct {
UserName string
CompanyName string
}
aliasModel := AliasModel{}
if result := db.Model(&User{}).Select("users.name as user_name, companies.name as company_name").Joins("JOIN companies ON users.company_id = companies.id").Scan(&aliasModel); result.Error != nil {
fmt.Println("error", result.Error)
return
}
fmt.Println("aliasModel", aliasModel )
取り組んでみて
ORMを比較するというテーマで作業開始したけど、なんとなくGORMのサイトに記載されている内容を実際に試せる環境を用意したという記事になったなと感じたあじ太郎なのでした。