gorm 创建和更新高级用法

创建记录

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

result := db.Create(&user) // 将数据指针传递给 Create

user.ID              // 返回插入数据的主键
result.Error         // 返回错误
result.RowsAffected // 返回插入记录数

使用选定字段创建记录

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

创建记录并忽略传递给 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")

批量插入

要有效地插入大量记录,请将切片传递给该Create方法。GORM 将生成一条 SQL 语句来插入所有数据并回填主键值,也会调用钩子方法。

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

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

您可以在创建时指定批量大小CreateInBatches,例如:

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

// 批量大小 100
 db.CreateInBatches(users, 100 )
使用 UpsertCreate With Associations时也支持批量插入
注意使用 CreateBatchSize选项初始化 GORM, 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)

创建钩子

GORM 允许为 BeforeSaveBeforeCreateAfterSave、实现用户定义的钩子 AfterCreate。这些钩子方法将在创建记录时调用,有关生命周期的详细信息,请参阅 钩子
 
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会话模式,例如:

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 )

从地图创建

GORM 支持从 map[string]interface{}和创建 []map[string]interface{}{},例如:
db.Model(&User{}).Create( map [ string ] interface {}{ "Name" : "jinzhu" , "Age" : 18 , })
 

// 从 `[]map[string]interface{}{}`
 db.Model(&User{}).Create([] map [ string ] interface {}{ 
  { "Name" : "jinzhu_1" , "Age" : 18 }, 
  { "姓名" : "jinzhu_2" , "Age" : 20 }, 
})

从 SQL 表达式/上下文赋值器创建

GORM 允许使用 SQL 表达式插入数据,有两种方法可以实现这一目标, create frommap[string]interface{}Customized Data Types,例如:
// 从地图创建
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
}

// Scan 实现了 sql.Scanner interface 
func  (loc *Location)  Scan (v interface {})  error { // 从数据库驱动程序扫描一个值到 struct }
  
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 (“点(100 100)”))

先进的

用关联创建

当创建一些带有关联的数据时,如果它的关联值不是零值,这些关联将被更新,并且它的Hooks方法将被调用。

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

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

db.Create(用户{
  Name       :“jinzhu”,
  CreditCard :CreditCard{Number:“411111111111” } 
})
// INSERT INTO`users` ... 
// INSERT INTO`credit_cards` ...

您可以跳过保存与Select, 的关联Omit,例如:

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

// 跳过所有关联
db.Omit(clause.Associations).Create(&user)

默认值

您可以为带有 tag 的字段定义默认值default,例如:

type User struct { 
  ID    int64
   Name string  `gorm:"default:galeone"`
   Age   int64   `gorm:"default:18"`
 }
然后在插入到数据库的零值字段时将使用默认值
注意任何像0'',一样的零值false都不会被保存到那些定义了默认值的字段的数据库中,您可能希望使用指针类型或扫描器/值器来避免这种情况,例如:
type User struct { 
  gorm.Model 
  Name string
   Age * int            `gorm:"default:18"`
   Active sql.NullBool `gorm:"default:true"`
 }

注意您必须为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 ));默认值:(-);"`
 }
使用虚拟/生成值时,您可能需要禁用其创建/更新权限,请查看字段级权限
GORM 为不同的数据库提供兼容的 Upsert 支持
导入 “gorm.io/gorm/clause”

// 对冲突不做任何处理
db.Clauses(clause.OnConflict{DoNothing: true }).Create(&user)

// 在 `id` 冲突时将列更新为默认值
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

// 使用 SQL 表达式
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) );

// 在 `id` 冲突时将列更新为新值
db.Clauses(clause.OnConflict{ 
  Columns: []clause.Column{{Name: "id" }}, 
  DoUpdates: clause.AssignmentColumns([] string { "name" , "age" }), 
}).Create(&users) 
// 合并到 "users" 使用 *** 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), `年龄=值(年龄); MySQL


// 将除主键之外的所有列更新为冲突时的新值
db.Clauses(clause.OnConflict{ 
  UpdateAll: true , 
}).Create(&users) 
// INSERT INTO "users" *** ON CONFLICT ("id" ) DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age", ...;

<think>嗯,用户问的是GORM动态创建表,我得先回想一下GORM的相关知识。GORM是Go语言的ORM库,主要用于数据库操作,常规用法是在模型定义好后用AutoMigrate来创建表。但动态创建表可能是指运行时根据需求生成不同的表,比如分表或者多租户的情况。 首先,用户可能需要动态生成表名。GORM默认的表名是结构体名的复数形式,但可以通过Table方法指定。比如,根据时间分表,每个月一张表,表名类似orders_2023_10。这时候需要在运行时拼接表名,并用Table方法指定。 然后,结构体模型可能需要动态生成。比如根据不同的字段需求创建表,这时候可能需要使用反射或者生成结构体的代码。不过Go是静态语言,运行时创建结构体比较困难,可能需要用interface{}或者map来处理,但这会影响类型安全。或者,用户可能预先定义好多个结构体,根据条件选择使用哪个。 另外,动态创建表的时候要考虑数据库的兼容性,不同数据库的语法可能不同,GORM虽然做了抽象,但某些细节还是要注意,比如字段类型、索引等。 还有,使用AutoMigrate的时候,如果表已经存在,GORM会修改表结构,添加缺失的列索引。但动态创建表可能需要先检查表是否存在,不存在再创建,这时候可能需要用Database的元数据查询,比如在MySQL中检查information_schema。 用户可能有分表的需求,比如数据量大的时候按时间分表,这时候动态创建表可以避免手动建表。或者多租户场景,每个租户单独的表,这时候表名可能包含租户ID。 另外,动态创建表后,后续的CRUD操作都需要指定表名,比如每次操作前调用Table方法,或者使用Scopes来封装逻辑。 需要注意,频繁创建表可能会影响数据库性能,表数量过多也可能导致管理困难。需要根据实际场景评估是否真的需要动态创建表,或者是否有其他方案比如分区表。 可能还需要考虑事务,在创建表的过程中如果出错是否需要回滚,但DDL语句通常自动提交,事务可能不适用。 最后,给出一个示例代码会比较有帮助,比如定义一个模型,动态生成表名,用AutoMigrate创建表,然后演示如何插入数据。同时提醒注意事项,比如表名管理、性能、设计权衡等。</think>在Go语言中,GORM是一个常用的ORM(Object-Relational Mapping)库。动态创建表通常用于需要根据运行时条件生成不同表结构的场景,例如分表存储、多租户系统等。以下是实现动态创建表的关键步骤示例: --- ### 一、核心实现思路 1. **动态表名** 通过重写GORM的`TableName`方法或使用`Table()`方法指定表名 2. **动态模型结构** 使用反射或代码生成技术创建不同的结构体类型 3. **执行迁移** 调用`AutoMigrate()`方法创建表结构 --- ### 二、基础代码示例 ```go package main import ( "gorm.io/driver/mysql" "gorm.io/gorm" ) // 基础模型结构 type DynamicModel struct { ID uint `gorm:"primaryKey"` Name string // 可动态添加其他字段 } func main() { dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic("数据库连接失败") } // 动态表名生成(示例:按月份分表) tableName := "dynamic_table_202310" // 动态创建表 err = db.Table(tableName).AutoMigrate(&DynamicModel{}) if err != nil { panic("表创建失败: " + err.Error()) } } ``` --- ### 三、高级用法示例 #### 1. 通过结构体标签动态定义字段 ```go type DynamicTable struct { gorm.Model ExtraFields map[string]interface{} `gorm:"-"` // 动态字段容器 } // 动态生成表结构 func (d *DynamicTable) BeforeCreate(tx *gorm.DB) error { if len(d.ExtraFields) > 0 { stmt := tx.Statement for field, value := range d.ExtraFields { stmt.Schema.AddField(field, schema.Field{ Name: field, DataType: "VARCHAR(255)", // 根据实际类型调整 }) stmt.SetColumn(field, value) } } return nil } ``` #### 2. 动态模型注册 ```go func CreateDynamicModel(fields map[string]string) interface{} { structFields := []reflect.StructField{ { Name: "ID", Type: reflect.TypeOf(uint(0)), Tag: `gorm:"primaryKey"`, }, } for name, dataType := range fields { structFields = append(structFields, reflect.StructField{ Name: strings.ToUpper(name[:1]) + name[1:], Type: reflect.TypeOf(""), Tag: reflect.StructTag(`gorm:"column:` + name + `;type:` + dataType + `"`), }) } dynamicType := reflect.StructOf(structFields) return reflect.New(dynamicType).Interface() } ``` --- ### 四、注意事项 1. **表名管理** 动态表名需要自行维护命名规则存在性检查 2. **性能影响** 频繁创建新表可能影响数据库性能 3. **字段类型兼容性** 不同数据库对字段类型的支持存在差异 4. **索引管理** 动态生成的表需单独处理索引优化 5. **设计权衡** 评估是否真的需要动态表,考虑分区表等替代方案 --- ### 五、典型应用场景 1. **日志分表存储** 按日期自动创建日志表 2. **多租户系统** 为每个租户创建独立表 3. **动态表单系统** 根据用户自定义字段生成表结构 4. **大数据存储优化** 按特定规则分散数据存储 建议在实际使用前进行以下验证: 1. 测试目标数据库的DDL执行性能 2. 验证字段类型的跨数据库兼容性 3. 实现表存在性检查机制 4. 制定旧表清理策略
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值