LoginSignup
0
0

gormのメモ

Posted at

はじめに

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)を使用してデータベースのテーブルを表現します。この際、構造体のフィールド名は通常パスカルケース(またはキャメルケースの最初の文字が大文字)で記述されます。例えば、UserIDFirstName のようになります。

一方、データベースのテーブル名やカラム名には、スネークケースが一般的に使用されます。スネークケースでは、すべて小文字で単語の間をアンダースコア(_)で区切ります。例えば、user_idfirst_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.dbtest_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は実行時によって異なる可能性があります)。

参考記事

0
0
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
0
0