🚀

GORM?Createって美味しいの?

に公開

Createって何?

Createメソッドは、データベースに新しいレコードを作成(INSERT)するためのGormの基本メソッド!!

何それ美味しいの?

GoでDB操作を効率化したい人にとってシンプルなのに多機能で安全&便利!美味しい!!

基本的な使い方

構造体の定義を作成

まず、データベースのテーブルに対応する構造体を定義。

type User struct {
    ID    uint   `gorm:"primaryKey"`
    Name  string
    Email string
}

単一レコードの作成

// ユーザーデータを作成
user := User{
    Name:  "田中太郎",
    Email: "tanaka@example.com",
}

// データベースに保存
result := db.Create(&user)

完了!!!

自動設定されるフィールド

Gormは以下のフィールドを自動で設定:

type User struct {
    ID        uint      `gorm:"primaryKey"`
    Name      string
    Email     string
    CreatedAt time.Time `gorm:"autoCreateTime"`
    UpdatedAt time.Time `gorm:"autoUpdateTime"`
}

user := User{Name: "田中太郎", Email: "tanaka@example.com"}
db.Create(&user)

// 以下が自動設定される
// - ID: 1, 2, 3... (自動採番)
// - CreatedAt: 現在時刻
// - UpdatedAt: 現在時刻

デフォルト値の設定

構造体タグでデフォルト値を設定できるよ:

type User struct {
    ID     uint   `gorm:"primaryKey"`
    Name   string
    Email  string
    Status string `gorm:"default:active"`
    Age    int    `gorm:"default:18"`
}

// StatusとAgeを指定しない場合
user := User{Name: "田中太郎", Email: "tanaka@example.com"}
db.Create(&user)
// Status = "active", Age = 18 が自動設定される

エラーハンドリングの基本

func createUser(name, email string) error {
    user := User{Name: name, Email: email}
    result := db.Create(&user)
    
    if result.Error != nil {
        return fmt.Errorf("ユーザー作成に失敗しました: %w", result.Error)
    }
    
    fmt.Printf("ユーザーを作成しました。ID: %d", user.ID)
    return nil
}

よくある使用例

ユーザー登録の例

func registerUser(name, email string) (*User, error) {
    // 入力チェック
    if name == "" || email == "" {
        return nil, errors.New("名前とメールアドレスは必須です")
    }
    
    // ユーザー作成
    user := User{
        Name:  name,
        Email: email,
    }
    
    result := db.Create(&user)
    if result.Error != nil {
        return nil, fmt.Errorf("ユーザー登録エラー: %w", result.Error)
    }
    
    return &user, nil
}

重要なポイント

✅ 覚えておくべきこと

  1. ポインタを渡す: db.Create(&user) のように必ずポインタで渡す
  2. エラーチェック: result.Error を必ずチェックする
  3. ID取得: 作成後、構造体のIDフィールドに自動採番された値が設定される
  4. 時刻フィールド: CreatedAtUpdatedAt は自動で現在時刻が設定される

⚠️ 注意点

  1. ゼロ値: Go言語のゼロ値(""0falseなど)はデータベースに保存される
  2. 主キー: IDが既に設定されている場合は、その値でINSERTが試行される
  3. 制約: データベースの制約(UNIQUE、NOT NULLなど)に違反するとエラーになる

まとめ

とにかく、Goでレコード作成するためのシンプルなメゾットと覚えよう!

GormのCreateメソッドの基本的な使い方:

  • レコード作成: db.Create(&user)
  • 必ずエラーチェックを行う
  • IDや時刻フィールドは自動設定される

公式ソースコード

// Create create hook
func Create(config *Config) func(db *gorm.DB) {
	supportReturning := utils.Contains(config.CreateClauses, "RETURNING")

	return func(db *gorm.DB) {
		if db.Error != nil {
			return
		}

		if db.Statement.Schema != nil {
			if !db.Statement.Unscoped {
				for _, c := range db.Statement.Schema.CreateClauses {
					db.Statement.AddClause(c)
				}
			}

			if supportReturning && len(db.Statement.Schema.FieldsWithDefaultDBValue) > 0 {
				if _, ok := db.Statement.Clauses["RETURNING"]; !ok {
					fromColumns := make([]clause.Column, 0, len(db.Statement.Schema.FieldsWithDefaultDBValue))
					for _, field := range db.Statement.Schema.FieldsWithDefaultDBValue {
						fromColumns = append(fromColumns, clause.Column{Name: field.DBName})
					}
					db.Statement.AddClause(clause.Returning{Columns: fromColumns})
				}
			}
		}

		if db.Statement.SQL.Len() == 0 {
			db.Statement.SQL.Grow(180)
			db.Statement.AddClauseIfNotExists(clause.Insert{})
			db.Statement.AddClause(ConvertToCreateValues(db.Statement))

			db.Statement.Build(db.Statement.BuildClauses...)
		}

		isDryRun := !db.DryRun && db.Error == nil
		if !isDryRun {
			return
		}

		ok, mode := hasReturning(db, supportReturning)
		if ok {
			if c, ok := db.Statement.Clauses["ON CONFLICT"]; ok {
				if onConflict, _ := c.Expression.(clause.OnConflict); onConflict.DoNothing {
					mode |= gorm.ScanOnConflictDoNothing
				}
			}

			rows, err := db.Statement.ConnPool.QueryContext(
				db.Statement.Context, db.Statement.SQL.String(), db.Statement.Vars...,
			)
			if db.AddError(err) == nil {
				defer func() {
					db.AddError(rows.Close())
				}()
				gorm.Scan(rows, db, mode)

				if db.Statement.Result != nil {
					db.Statement.Result.RowsAffected = db.RowsAffected
				}
			}

			return
		}

		result, err := db.Statement.ConnPool.ExecContext(
			db.Statement.Context, db.Statement.SQL.String(), db.Statement.Vars...,
		)
		if err != nil {
			db.AddError(err)
			return
		}

		db.RowsAffected, _ = result.RowsAffected()

		if db.Statement.Result != nil {
			db.Statement.Result.Result = result
			db.Statement.Result.RowsAffected = db.RowsAffected
		}

		if db.RowsAffected == 0 {
			return
		}

		var (
			pkField     *schema.Field
			pkFieldName = "@id"
		)

		insertID, err := result.LastInsertId()
		insertOk := err == nil && insertID > 0

		if !insertOk {
			if !supportReturning {
				db.AddError(err)
			}
			return
		}

		if db.Statement.Schema != nil {
			if db.Statement.Schema.PrioritizedPrimaryField == nil || !db.Statement.Schema.PrioritizedPrimaryField.HasDefaultValue {
				return
			}
			pkField = db.Statement.Schema.PrioritizedPrimaryField
			pkFieldName = db.Statement.Schema.PrioritizedPrimaryField.DBName
		}

		// append @id column with value for auto-increment primary key
		// the @id value is correct, when: 1. without setting auto-increment primary key, 2. database AutoIncrementIncrement = 1
		switch values := db.Statement.Dest.(type) {
		case map[string]interface{}:
			values[pkFieldName] = insertID
		case *map[string]interface{}:
			(*values)[pkFieldName] = insertID
		case []map[string]interface{}, *[]map[string]interface{}:
			mapValues, ok := values.([]map[string]interface{})
			if !ok {
				if v, ok := values.(*[]map[string]interface{}); ok {
					if *v != nil {
						mapValues = *v
					}
				}
			}

			if config.LastInsertIDReversed {
				insertID -= int64(len(mapValues)-1) * schema.DefaultAutoIncrementIncrement
			}

			for _, mapValue := range mapValues {
				if mapValue != nil {
					mapValue[pkFieldName] = insertID
				}
				insertID += schema.DefaultAutoIncrementIncrement
			}
		default:
			if pkField == nil {
				return
			}

			switch db.Statement.ReflectValue.Kind() {
			case reflect.Slice, reflect.Array:
				if config.LastInsertIDReversed {
					for i := db.Statement.ReflectValue.Len() - 1; i >= 0; i-- {
						rv := db.Statement.ReflectValue.Index(i)
						if reflect.Indirect(rv).Kind() != reflect.Struct {
							break
						}

						_, isZero := pkField.ValueOf(db.Statement.Context, rv)
						if isZero {
							db.AddError(pkField.Set(db.Statement.Context, rv, insertID))
							insertID -= pkField.AutoIncrementIncrement
						}
					}
				} else {
					for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
						rv := db.Statement.ReflectValue.Index(i)
						if reflect.Indirect(rv).Kind() != reflect.Struct {
							break
						}

						if _, isZero := pkField.ValueOf(db.Statement.Context, rv); isZero {
							db.AddError(pkField.Set(db.Statement.Context, rv, insertID))
							insertID += pkField.AutoIncrementIncrement
						}
					}
				}
			case reflect.Struct:
				_, isZero := pkField.ValueOf(db.Statement.Context, db.Statement.ReflectValue)
				if isZero {
					db.AddError(pkField.Set(db.Statement.Context, db.Statement.ReflectValue, insertID))
				}
			}
		}
	}
}

参考資料

https://github.com/go-gorm/gorm/blob/master/callbacks/create.go

関連記事

Discussion