생성

레코드 생성

user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}

result := db.Create(&user) // 생성할 데이터의 포인터 넘기기

user.ID // 입력된 데이터의 primary key를 반환합니다
result.Error // 에러를 반환합니다
result.RowsAffected // 입력된 레코드의 개수를 반환합니다.

Create() 함수를 통해 여러 레코드를 동시에 생성할수도 있습니다.

users := []*User{
{Name: "Jinzhu", Age: 18, Birthday: time.Now()},
{Name: "Jackson", Age: 19, Birthday: time.Now()},
}

result := db.Create(users) // 레코드들이 들어갈 슬라이스를 넣어줍니다.

result.Error // 에러를 반환합니다.
result.RowsAffected // 슬라이스에 삽입된 레코드의 숫자를 반환합니다.

주의할점 구조체를 직접 ‘create’ 함수에 넣지 마세요, 대신 포인터를 넣어주세요.

선택한 필드로 레코드 만들기

Create a record and assign a value to the fields specified.

db.Select("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18, "2020-07-04 11:05:21.775")

Create a record and ignore the values for fields passed to omit.

db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")

Batch Insert

효율적으로 많은 레코드를 만들기 위해서, slice를 Create 메소드에 넣어주세요. GORM은 단 하나의 SQL 구문을 작성하여 모든 데이터를 삽입합니다 또한 hook methods, primary key 값 자동 삽입등이 이를 기점으로 하여 실행됩니다. 이를 일정한 배치 단위로 나누어서 트랜잭션으로 처리할 수 있습니다.

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

for _, user := range users {
user.ID // 1,2,3
}

CreateInBatches를 활용하여 배치 사이즈를 결정할 수 있습니다, e.g:

var users = []User{{Name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}}

// batch size 100
db.CreateInBatches(users, 100)

Batch Insert는 UpsetCreate With Associtations에도 지원됩니다.

주의할점: GORM을 CreateBatchSize옵션과 함께 생성할 경우, 모든 INSERT구문은 레코드 생성시 해당 배치 옵션을 따를것입니다.

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
CreateBatchSize: 1000,
})

db := db.Session(&gorm.Session{CreateBatchSize: 1000})

users = [5000]User{{Name: "jinzhu", Pets: []Pet{pet1, pet2, pet3}}...}

db.Create(&users)
// INSERT INTO users xxx (5 batches)
// INSERT INTO pets xxx (15 batches)

Hooks 생성하기

GORM은 BeforeSave,BeforeCreate,AfterSave,AfterCreate등과 같은 사용자가 정의한 hook을 구현하여 사용할 수 있습니다. 이런 hook 메소드들은 레코드를 만들때 호출 됩니다, Hooks를 참고하여 좀 더 자세한 생명주기에 관하여 참고해보세요.

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
u.UUID = uuid.New()

if u.Role == "admin" {
return errors.New("invalid role")
}
return
}

만일 Hooks 메소드들을 생략하고 싶으시다면, SkipHooks를 session mode에서 활용할 수 있습닏, 예시:

DB.Session(&gorm.Session{SkipHooks: true}).Create(&user)

DB.Session(&gorm.Session{SkipHooks: true}).Create(&users)

DB.Session(&gorm.Session{SkipHooks: true}).CreateInBatches(users, 100)

Map으로 생성하기

GORM은 map[string] interface{}[]map[string] interface{}{}를 활용한 레코드 생성을 지원합니다, 예시:

db.Model(&User{}).Create(map[string]interface{}{
"Name": "jinzhu", "Age": 18,
})

// `[]map[string]interface{}{}`를 활용한 batch insert
db.Model(&User{}).Create([]map[string]interface{}{
{"Name": "jinzhu_1", "Age": 18},
{"Name": "jinzhu_2", "Age": 20},
})

주의할점 map 자료구조를 활용한 레코드 생성시, hooks은 실행되지 않으며 기본키가 채워지지 안흥며 연결이 저장되지 않습니다.

SQL Expression/Context Valuer로 생성

GORM은 SQL을 활용한 데이터 삽입을 지원합니다. SQL을 활용하기 위하여는 두 가지 방법을 사용할 수 있습니다. map[string] interface{} 혹은 사용자 정의 데이터 타입을 활용할 수 있습니다. 예시:

// map을 활용한 레코드 생성 
db.Model(User{}).Create(map[string]interface{}{
"Name": "jinzhu",
"Location": clause.Expr{SQL: "ST_PointFromText(?)", Vars: []interface{}{"POINT(100 100)"}},
})
// INSERT INTO `users` (`name`,`location`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"));

// 사용자 정의 타입을 활용한 데이터 생성
type Location struct {
X, Y int
}

// sql.Scanner 인터페이스의 구현을 검사
func (loc *Location) Scan(v interface{}) error {
// Scan a value into struct from database driver
}

func (loc Location) GormDataType() string {
return "geometry"
}

func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
return clause.Expr{
SQL: "ST_PointFromText(?)",
Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
}
}

type User struct {
Name string
Location Location
}

db.Create(&User{
Name: "jinzhu",
Location: Location{X: 100, Y: 100},
})
// INSERT INTO `users` (`name`,`location`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"))

고급

Create With Associations

연관(associations) 이 있는 일부 데이터를 작성할 때 association 값이 0 값이 아닌 경우 해당 association이 upsert 되고 해당 Hooks 메소드가 호출됩니다.

type CreditCard struct {
gorm.Model
Number string
UserID uint
}

type User struct {
gorm.Model
Name string
CreditCard CreditCard
}

db.Create(&User{
Name: "jinzhu",
CreditCard: CreditCard{Number: "411111111111"}
})
// INSERT INTO `users` ...
// INSERT INTO `credit_cards` ...

Select, Omit 를 사용하여 associations를 스킵 할 수 있습니다. 예시:

db.Omit("CreditCard").Create(&user)

// 모든 associations 무시
db.Omit(clause.Associations).Create(&user)

기본 값

default태그를 사용하여 필드의 기본값을 정의 할 수 있습니다. 예를 들면 다음과 같습니다.

type User struct {
ID int64
Name string `gorm:"default:galeone"`
Age int64 `gorm:"default:18"`
}

Then the default value will be used when inserting into the database for zero-value fields

NOTE 0, '', false와 같은 값들은 지정된 기본값으로 데이터베이스에 저장되지 않습니다, 대신에 포인터 타입 혹은 Scanner/Value를 활용해주세요, 예시:

type User struct {
gorm.Model
Name string
Age *int `gorm:"default:18"`
Active sql.NullBool `gorm:"default:true"`
}

NOTE 데이터베이스에 기본 값 또는 가상/생성 값이 있는 필드에 대해default 태그를 설정해야 합니다. 데이터베이스 마이그레이션 할 때 기본 값을 정의하는 부분을 건너뛰려면 다음과 같이 default:(-) 를 사용할 수 있습니다. 예시:

type User struct {
ID string `gorm:"default:uuid_generate_v3()"` // db func
FirstName string
LastName string
Age uint8
FullName string `gorm:"->;type:GENERATED ALWAYS AS (concat(firstname,' ',lastname));default:(-);"`
}

NOTE****SQLite는 특정 레코드에 대하여 batch insert를 실행시 기본값을 지원하지 않습니다. SQLite insert 구문을 참고하세요. For example:

type Pet struct {
Name string `gorm:"default:cat"`
}

// SQLite는 다음과 같은 구문을 지원하지 않으므로 GORM은 잘못된 SQL을 생성하고 에러를 일으킬 것입니다.
// INSERT INTO `pets` (`name`) VALUES ("dog"),(DEFAULT) RETURNING `name`
db.Create(&[]Pet{{Name: "dog"}, {}})

A viable alternative is to assign default value to fields in the hook, e.g.

func (p *Pet) BeforeCreate(tx *gorm.DB) (err error) {
if p.Name == "" {
p.Name = "cat"
}
}

issues#6335를 통해 더 자세한 사항을 볼 수 있습니다.

Virtual/generated 값을 활용하려면 creating/dupdating 권한을 비활성화해야할 수 있습니다, Field-Level Permission을 참고해 주세요.

Upsert / On Conflict

GORM은 다른 데이터베이스와 상호 운용가능한 Upsert를 지원합니다.

import "gorm.io/gorm/clause"

// Do nothing on conflict
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&user)

// Update columns to default value on `id` conflict
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.Assignments(map[string]interface{}{"role": "user"}),
}).Create(&users)
// MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET ***; SQL Server
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE ***; MySQL

// Use SQL expression
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.Assignments(map[string]interface{}{"count": gorm.Expr("GREATEST(count, VALUES(count))")}),
}).Create(&users)
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `count`=GREATEST(count, VALUES(count));

// Update columns to new value on `id` conflict
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
}).Create(&users)
// MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET "name"="excluded"."name"; SQL Server
// INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age"; PostgreSQL
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age`=VALUES(age); MySQL

// Update all columns to new value on conflict except primary keys and those columns having default values from sql func
db.Clauses(clause.OnConflict{
UpdateAll: true,
}).Create(&users)
// INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age", ...;
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age`=VALUES(age), ...; MySQL

Also checkout FirstOrInit, FirstOrCreate on Advanced Query

Checkout Raw SQL and SQL Builder for more details

Platinum Sponsors

Gold Sponsors

Platinum Sponsors

Gold Sponsors