はじめに
gormはGo言語のORMライブラリーです。
gormで、最初見たとき、gormのcrud処理の前に、把握しておいた方がよかったことをメモりました。
・データベース接続箇所(シャーディングによるDBの振り分け例)
・&gorm.Config{}
について
・structの定義箇所について
・gorm.Model
の有無
コード例
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// User モデル
type User struct {
gorm.Model
Name string
}
// シャードされたデータベースへの接続を管理する
var shardMap = map[int]*gorm.DB{}
func init() {
// シャードデータベースの初期化
for i := 1; i <= 2; i++ {
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("test_shard_%d.db", i)), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
db.AutoMigrate(&User{})
shardMap[i] = db
}
}
// ユーザーIDに基づいてシャードを選択
func getShard(userID uint) *gorm.DB {
// ここでは単純化のため、IDが奇数の場合はシャード1、偶数の場合はシャード2を選択
if userID%2 == 0 {
return shardMap[2]
}
return shardMap[1]
}
func main() {
// ユーザーの作成
user1 := User{Name: "Alice"}
user2 := User{Name: "Bob"}
// ユーザーを適切なシャードに挿入
shard1 := getShard(1)
shard2 := getShard(2)
shard1.Create(&user1)
fmt.Println("Inserted User ID:", user1.ID)
var user User
shard1.First(&user, user1.ID) // 修正: ユーザー1の情報をシャード1から取得
fmt.Println(user)
shard2.Create(&user2)
fmt.Println("Inserted User ID:", user2.ID)
shard2.First(&user, user2.ID) // 修正: ユーザー2の情報をシャード2から取得
fmt.Println(user)
}
データベースへの接続
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("test_shard_%d.db", i)), &gorm.Config{})
この行は、GORMを使用してSQLiteデータベースに接続するためのコードです。具体的には、以下のステップに分けて解説できます。
1. データベースファイル名の生成
fmt.Sprintf("test_shard_%d.db", i)
この部分は、データベースファイル名を動的に生成しています。fmt.Sprintf
関数を使用することで、文字列のフォーマット(この場合は "test_shard_%d.db"
)に変数 i
の値を埋め込みます。例えば、i
が 1 なら "test_shard_1.db"
、2 なら "test_shard_2.db"
というファイル名が生成されます。これにより、異なるシャード(ここでは異なるデータベースファイル)に接続する準備をします。
2. SQLiteデータベースへの接続の開始
sqlite.Open(fmt.Sprintf("test_shard_%d.db", i))
ここでは、ステップ1で生成されたファイル名を使ってSQLiteデータベースファイルを開くための指示を行っています。sqlite.Open
関数は、指定されたファイル名のSQLiteデータベースに接続するためのドライバを返します。
3. GORMを介したデータベース接続の設定と開始
gorm.Open(sqlite.Open(fmt.Sprintf("test_shard_%d.db", i)), &gorm.Config{})
最終的に、gorm.Open
関数を使用して、ステップ2で得られたドライバ(つまり、特定のSQLiteデータベースへの接続情報)と、GORMの設定(&gorm.Config{}
)をもとにデータベース接続を開始します。&gorm.Config{}
は、GORMの動作をカスタマイズするための設定オブジェクトで、ここではデフォルト設定を使用しています。
この行を実行することで、変数 db
にはデータベースへの接続ハンドルが格納され、err
には接続時に発生したエラー(もし何か問題があれば)が格納されます。これにより、以降のコードで db
を通じてデータベース操作を行うことができるようになります。
&gorm.Config{}
について
以下で使用しています。
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("test_shard_%d.db", i)), &gorm.Config{})
&gorm.Config{}
は、GORM でデータベース接続を初期化する際に、設定オプションを指定するために使用されます。この設定オプションを通じて、GORM の内部動作をカスタマイズすることができます。
GORM の Open
関数を使用してデータベースに接続する際には、第二引数として &gorm.Config{}
を指定することが一般的です。この引数は、データベース接続時の設定をカスタマイズするために用います。もし特に設定を変更する必要がない場合でも、デフォルト設定を適用するために空の &gorm.Config{}
を指定することが推奨されます。
&gorm.Config{}
を明示的に指定することが、カスタマイズ可能性と明示性の観点から推奨されます。これにより、開発者はよりコントロールしやすい環境を得られ、将来的な設定変更やトラブルシューティングが容易になります。
struct箇所
GORMでは、Goの構造体(Struct)を使用してデータベースのテーブルを表現します。この際、構造体のフィールド名は通常パスカルケース(またはキャメルケースの最初の文字が大文字)で記述されます。例えば、UserID
や FirstName
のようになります。
一方、データベースのテーブル名やカラム名には、スネークケースが一般的に使用されます。スネークケースでは、すべて小文字で単語の間をアンダースコア(_
)で区切ります。例えば、user_id
や first_name
のようになります。
GORMはデフォルトで、Goの構造体のフィールド名からテーブルのカラム名を導き出す際に、パスカルケースからスネークケースへの変換を自動的に行います。つまり、構造体で定義された UserID
フィールドはデータベースの user_id
カラムに対応します。
カスタムのテーブル名やカラム名を使用したい場合は、GORMの TableName
インターフェイスを実装するか、column
タグを構造体フィールドに追加することで指定できます。例えば:
type User struct {
gorm.Model
FirstName string `gorm:"column:custom_first_name"`
}
// カスタムテーブル名を指定
func (User) TableName() string {
return "custom_users"
}
gorm.Model
について
以下で使用しています。
type User struct {
gorm.Model
Name string
}
gorm.Model
は、GORMが提供する組み込みの基本モデルです。これは、ほとんどのデータベーステーブルで共通して使用されるフィールドを含んでおり、次のようなフィールドが定義されています:
- ID: レコードのプライマリキー
- CreatedAt: レコードの作成日時
- UpdatedAt: レコードの最終更新日時
- DeletedAt: レコードの削除日時(ソフトデリートをサポート)
これらのフィールドを各モデルに自動的に追加することで、GORMはデータベース操作時にこれらの共通フィールドを利用できるようにします。例えば、レコードの作成日時や更新日時を自動的に管理することが可能です。
gorm.Model
を含むモデル定義は次のようになります:
type User struct {
gorm.Model // 組み込みの基本モデルを埋め込み
Name string
}
このように、gorm.Model
をモデルに埋め込むことで、開発者はこれらの共通フィールドを意識することなく、モデル固有のフィールドに集中してデータベース設計を行うことができます。これは、開発の手間を省き、一般的なデータベース操作を簡素化するための便利なショートカットです。
省略可
gorm.Model
は便利なショートカットであり、多くのテーブルに共通する基本フィールド(ID
, CreatedAt
, UpdatedAt
, DeletedAt
)を自動的にモデルに追加します。しかし、これらのフィールドが必要ない場合や、よりカスタマイズされたフィールド名や挙動を望む場合は、gorm.Model
をモデル定義から省略し、必要なフィールドを自分で定義することができます。
gorm.Model
を使用しない場合
gorm.Model
を使用せずに、必要なフィールドを自分で定義する例を以下に示します:
type User struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
Name string
}
この例では、ID
, CreatedAt
, UpdatedAt
, DeletedAt
フィールドを自分で定義しています。ID
フィールドには primaryKey
タグを使用して、これがプライマリキーであることを GORM に伝えています。DeletedAt
フィールドには index
タグを使用しており、ソフトデリートの機能を利用するためにはこのフィールドのタイプを gorm.DeletedAt
と指定する必要があります。
カスタマイズの利点
- 柔軟性: モデルの各フィールドを完全にコントロールできるため、データベースのスキーマとアプリケーションのモデルをより細かく調整することができます。
-
明確性:
gorm.Model
を使用せずにフィールドを個別に定義することで、モデルの構造がより明確になり、新しい開発者がコードベースを理解しやすくなります。
注意点
-
手動での定義が必要:
gorm.Model
を省略すると、その便利な機能(自動的なフィールド定義など)を手動で実装する必要があります。 -
ソフトデリートの管理: ソフトデリートを使用する場合、
DeletedAt
フィールドを適切に定義し、そのタイプをgorm.DeletedAt
として設定する必要があります。
結局のところ、gorm.Model
の使用または非使用は、プロジェクトの要件と開発者の好みによって決まります。より多くのコントロールが必要な場合や、特定のフィールド名を使用したい場合は、gorm.Model
を省略することが適切かもしれません。
切り出し
Go言語でのプロジェクト構成においては、モデル(またはエンティティ)定義をmodel
フォルダ(またはmodels
フォルダ)に分離して管理することは一般的なアプローチの一つです。これにより、コードの整理が容易になり、モデル定義を他のコンポーネントから再利用しやすくなります。
具体的には、プロジェクトのルートディレクトリにmodel
フォルダを作成し、その中に各テーブルに対応するGoのファイル(例えばuser.go
)を作成します。user.go
ファイル内には、User
構造体の定義を含めます。
// model/user.go
package model
import (
"time"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
Name string
}
そして、このモデルを他の場所(例えば、データベースとのインタラクションを扱うロジックなど)で使用するには、適切にインポートして利用します。
package main
import (
"fmt"
"log"
"your_project_path/model" // 適切にパスを設定してください
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
log.Fatalf("failed to connect database: %v", err)
}
// モデルの自動マイグレーション(テーブルがない場合は作成)
db.AutoMigrate(&model.User{})
// データの挿入
db.Create(&model.User{Name: "John Doe"})
// データの取得
var user model.User
db.First(&user, 1) // IDに基づいて取得
fmt.Println(user)
}
このように、model
フォルダを使ってモデル定義を管理することで、プロジェクトの構造を清潔に保ち、再利用性とメンテナンス性を高めることができます。モデルがアプリケーションの他の部分から独立しているため、将来的にデータベースのスキーマを変更する必要がある場合でも、変更が容易になります。
このように、GORMは柔軟にテーブル名やカラム名のカスタマイズをサポートしています。
コード例の確認例
SQLiteコマンドラインツールの起動
SQLiteでデータベースファイルを確認するためのコマンドを以下に示します。sample2.go
スクリプトを実行した結果、User
エンティティが2つの異なるシャードデータベース(test_shard_1.db
と test_shard_2.db
)に挿入されたようです。各データベースファイルに格納されているデータを確認するためには、それぞれに対して SQLite コマンドラインツールを使用します。
各シャードデータベースにアクセスするには、ターミナルから以下のコマンドを実行します。最初に test_shard_1.db
を確認し、次に test_shard_2.db
を確認します。
sqlite3 test_shard_1.db
これにより、test_shard_1.db
データベースに対する SQLite コマンドラインインターフェースが開きます。
テーブルの一覧表示
データベース内に存在するテーブルの一覧を表示するには、次のコマンドを使用します:
.tables
データの確認
特定のテーブル(このケースでは users
テーブル)の内容を確認するには、次のコマンドを実行します:
SELECT * FROM users;
SQLiteコマンドラインツールの終了
作業が完了したら、SQLiteコマンドラインツールから退出するには、次のコマンドを使用します:
.quit
上記の手順で test_shard_1.db
の内容を確認した後、同様の手順で test_shard_2.db
に対しても行います:
sqlite3 test_shard_2.db
そして .tables
および SELECT * FROM users;
コマンドを使用して、test_shard_2.db
内のデータを確認します。
これらの手順を通じて、各シャードデータベースに格納されているデータを確認できます。sample2.go
スクリプトの実行結果によると、各データベースには ID
が 2 の User
エンティティが存在するはずです(ただし、これは自動採番されたIDに依存しますので、実際のIDは実行時によって異なる可能性があります)。
参考記事