GORM

快速使用

导包

1
2
3
4
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)

模型相关

一言以蔽之,GORM中,使用模型所实现的效果,就相当于 Java Mybatis 中,自动把对象中的属性变成 数据库字段 /把数据库字段变成属性

名称相关

  1. 属性名 / 字段名

GORM中也实现了 结构体字段驼峰命名 <——> 数据库字段 小写+下划线 命名的相互转化

如果结构体中的命名和数据库字段命名不只是这种差异,则默认无法映射

在Java中,对于这样的字段我们使用@Param注解,而在go中,我们为结构体参数配置Tag:

1
CreateTime int64 `gorm:"column:createtime"` //必须把column加上

而go的Tag还不止这一个作用,因为gorm还可以创建表,所以后面还可以跟建表属性

1
gorm:"column:id; PRIMARY_KEY"

此外,有的时候结构体和数据库并不是完全一样的,这时候就可以借助权限控制:(虽然不用默认也不报错)

<- 允许字段被写入(默认值:create,update gorm:"<-:create"(仅允许创建时写入)
<-:create 只写(仅在创建时允许写入) Password string gorm:"<-:create"
<-:update 只写(仅在更新时允许写入) Token string gorm:"<-:update"
<-:false 禁止写入(完全不可修改) CreatedAt time.Time gorm:"<-:false"
-> 允许字段被读取(默认值:true gorm:"->:false"(禁止读取)
->:false 只写字段(可存入数据库,但查询时不返回) Secret string gorm:"->:false"
- 完全忽略(不参与读写和迁移) TempField string gorm:"-"

自带时间更新

GORM 自带一个结构体长这样:

1
2
3
4
5
6
7
// gorm.Model 的定义
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}

我们想使用的话直接把他嵌入我们自己的结构体即可

1
2
3
4
type User struct {
gorm.Model // 嵌入gorm.Model的字段
Name string
}

GORM 约定使用 CreatedAt、UpdatedAt 追踪创建/更新时间。如果定义了这种字段,GORM 在创建、更新时会自动填充当前时间,要使用不同名称的字段,可以用tag自己指定配置 autoCreateTime、autoUpdateTime 标签

连接数据库

DSN格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//  charset=utf8 客户端字符集为utf8
// parseTime=true 支持把数据库datetime和date类型转换为golang的time.Time类型
// loc=Local 使用系统本地时区

//开发的时候经常需要设置数据库连接超时参数,gorm是通过dsn的timeout参数配置,例如,设置10秒后连接超时,timeout=10s

// readTimeout - 读超时时间,0代表不限制,writeTimeout - 写超时时间,0代表不限制

root:123456@tcp(localhost:3306)/tizi365?charset=utf8&parseTime=True&loc=Local&timeout=10s&readTimeout=30s&writeTimeout=60s

//完整链接示意:

package main

import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)

func main() {
//配置MySQL连接参数
username := "root" //账号
password := "123456" //密码
host := "127.0.0.1" //数据库地址,可以是Ip或者域名
port := 3306 //数据库端口
Dbname := "tizi365" //数据库名
timeout := "10s" //连接超时,10秒

//拼接下dsn参数, dsn格式可以参考上面的语法,这里使用Sprintf动态拼接dsn参数,因为一般数据库连接参数,我们都是保存在配置文件里面,需要从配置文件加载参数,然后拼接dsn。
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local&timeout=%s", username, password, host, port, Dbname, timeout)
//连接MYSQL, 获得DB类型实例,用于后面的数据库读写操作。
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("连接数据库失败, error=" + err.Error())
}
//延时关闭数据库连接
defer db.Close()
}

debug

直接在语句中加上.Debug() 即可:

1
result := db.Debug().Where("username = ?", "edwardNygma").First(&u)

连接池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//定义一个工具包,用来管理gorm数据库连接池的初始化工作。
package tools

//定义全局的db对象,我们执行数据库操作主要通过他实现,这个很像routor 那个返回值,也是指针类型
var _db *gorm.DB

//包初始化函数,golang特性,每个包初始化的时候会自动执行init函数,这里用来初始化gorm。
func init() {
...忽略dsn配置,请参考上面例子...

// 声明err变量,下面不能使用:=赋值运算符,否则_db变量会当成局部变量,导致外部无法访问_db变量
var err error
//连接MYSQL, 获得DB类型实例,用于后面的数据库读写操作。
_db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) //open函数返回一个db实例的指针
if err != nil {
panic("连接数据库失败, error=" + err.Error())
}

sqlDB, _ := _db.DB()


//设置数据库连接池参数
sqlDB.SetMaxOpenConns(100) //设置数据库连接池最大连接数
sqlDB.SetMaxIdleConns(20) //连接池最大允许的空闲连接数,如果没有sql任务需要执行的连接数大于20,超过的连接会被连接池关闭。
}

//获取gorm db对象,其他包需要执行数据库查询的时候,只要通过tools.getDB()获取db对象即可。
//不用担心协程并发使用同样的db对象会共用同一个连接,db对象在调用他的方法的时候会从数据库连接池中获取新的连接
func GetDB() *gorm.DB {
return _db
}

难以理解的地方

  1. ConnPool 是一个接口,同时作为gorm的一个字段

  2. *sql.DB 类型实现了 gorm.ConnPool 接口的所有方法

  3. .DB() 方法:

1
2
3
func (db *DB) DB() (*sql.DB, error) {
return db.ConnPool.(*sql.DB) // 类型断言
}

实际上就是 *通过类型断言从接口字段中提取出具体的 sql.DB 实例

  1. 因为 sqlDB和 _db.ConnPool指向同一个 *sql.DB 实例,所以通过 sqlDB 修改参数会影响 _db 的所有后续操作

    image-20250915225753827

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main
//导入tools包
import tools

func main() {
//获取DB
db := tools.GetDB()

//执行数据库查询操作
u := User{}
//自动生成sql: SELECT * FROM `users` WHERE (username = 'tizi365') LIMIT 1
db.Where("username = ?", "tizi365").First(&u)
}

附:Go 的方法集规则

接收者类型 可调用的方法
T(值类型) 所有 func (t T) Method() 的方法
*T(指针) 所有 func (t T) Method() func (t *T) Method() 的方法

使用连接池技术后,千万不要使用完db后调用db.Close关闭数据库连接,这样会导致整个数据库连接池关闭,导致连接池没有可用的连接

插入数据

单条数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//定义一个用户,并初始化数据
u := User{
Username:"tizi365",
Password:"123456",
CreateTime:time.Now().Unix(),
}

//插入一条用户数据
//下面代码会自动生成SQL语句:INSERT INTO `users` (`username`,`password`,`createtime`) VALUES ('tizi365','123456','1540824823')

db.Create(&u)

//一般项目中我们会类似下面的写法,通过Error对象检测,插入数据有没有成功,如果没有错误那就是数据写入成功了。
if err := db.Create(&u).Error; err != nil {
fmt.Println("插入失败", err)
return


//gorm 2.0版本以后的,默认会自动返回主键Id值
u.ID // 返回主键id,默认主键名为ID,也可以通过gorm标签定义
u.Error // 返回 error
u.RowsAffected // 返回插入记录的条数

如果gorm设置了数据库连接池,那么每次执行数据库查询的时候都会从数据库连接池申请一个数据库连接,那么上述代码必须使用数据库事务,确保插入数据和查询自增id两条sql语句是在同一个数据库连接下执行,否则在高并发场景下,可能会查询不到自增id,或者查询到错误的id

批量数据

如果要批量插入数据的话,直接定义切片,依旧传递引用即可,也可以分批插入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
users := []User{
{
Username: "user1",
Password: "pwd1",
CreateTime: time.Now().Unix(),
},
{
Username: "user2",
Password: "pwd2",
CreateTime: time.Now().Unix(),
},
// 可以继续添加更多...
}

// 批量插入数据(推荐传递切片指针,保持与单条插入风格一致)
// 技术上允许直接传值(users),但统一传指针(&users)更符合GORM惯例
result := db.Create(&users)

// 大批量数据建议分批插入(每批100条)
// 注意:CreateInBatches同样支持传值或指针,此处省略&仅为演示两种写法
batchSize := 100
err := db.CreateInBatches(users, batchSize).Error

查询数据

  1. gorm查询数据本质上就是提供一组函数,帮我们快速拼接sql语句,尽量减少编写sql语句的工作量。
  2. gorm查询结果我们一般都是保存到结构体(struct)变量,所以在执行查询操作之前需要根据自己想要查询的数据定义结构体类型
  3. gorm库是协程安全的,gorm提供的函数可以并发的在多个协程安全的执行

gorm查询主要由以下几个部分的函数组成,这些函数可以串起来组合sql语句,使用起来类似编写sql语句的习惯:

1.query

执行查询的函数,gorm提供下面几个查询函数:

Take ——> 查询一条记录
1
2
3
4
5
//定义接收查询结果的结构体变量
food := Food{}

//等价于:SELECT * FROM `foods` LIMIT 1
db.Take(&food)
**First **/ Last ——> 查询一条记录,根据主键ID排序(正序),返回第一条/最后一条记录
1
2
3
4
5
//等价于:SELECT * FROM `foods`   ORDER BY `foods`.`id` ASC LIMIT 1    
db.First(&food)

//等价于:SELECT * FROM `foods` ORDER BY `foods`.`id` DESC LIMIT 1
db.Last(&food)
Find ——> 查询多条记录,Find函数返回的是一个数组
1
2
//等价于:SELECT * FROM `foods`
db.Find(&foods)
Pluck ——> 查询一列值
1
2
3
4
5
6
7
8
9
10
//商品标题数组
var titles []string

//返回所有商品标题
//等价于:SELECT title FROM `foods`

// 1. Model(&Food{}) 通过模型结构体确定表名(foods表)
// 2. Pluck() 将查询结果映射到非结构体变量(如基础类型切片)
// 3. 必须传递 &titles 指针,否则无法回写查询结果
db.Model(&Food{}).Pluck("title", &titles)

有关错误处理

查询不到数据,我们不一定会当成错误处理,有可能数据确实没有,所以要把它(ErrRecordNotFound)分出来

1
2
3
4
5
6
7
err := db.Take(&food).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
fmt.Println("查询不到数据")
} else if err != nil {
//如果err不等于record not found错误,又不等于nil,那说明sql执行失败了。
fmt.Println("查询失败", err)
}

where

db.Where(query interface{}, args …interface{})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//等价于: SELECT * FROM `foods`  WHERE (id = '10') LIMIT 1
//这里问号(?), 在执行的时候会被10替代
db.Where("id = ?", 10).Take(&food)

// in 语句
//等价于: SELECT * FROM `foods` WHERE (id in ('1','2','5','6')) LIMIT 1
//args参数传递的是数组
db.Where("id in (?)", []int{1,2,5,6}).Take(&food)

//等价于: SELECT * FROM `foods` WHERE (create_time >= '2018-11-06 00:00:00' and create_time <= '2018-11-06 23:59:59')
//这里使用了两个问号(?)占位符,后面传递了两个参数替换两个问号。
db.Where("create_time >= ? and create_time <= ?", "2018-11-06 00:00:00", "2018-11-06 23:59:59").Find(&foods)

//like语句
//等价于: SELECT * FROM `foods` WHERE (title like '%可乐%')
db.Where("title like ?", "%可乐%").Find(&foods)

//复杂查询:
// 使用 :name 作为命名参数
//参数顺序无关,可复用变量
db.Where("id = :id OR name = :name",
map[string]interface{}{
"id": 10,
"name": "可乐",
}).Find(&foods)

select

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//等价于: SELECT id,title FROM `foods`  WHERE `foods`.`id` = '1' AND ((id = '1')) LIMIT 1  
db.Select("id,title").Where("id = ?", 1).Take(&food)

//这种写法是直接往Select函数传递数组,数组元素代表需要选择的字段名
db.Select([]string{"id", "title"}).Where("id = ?", 1).Take(&food)



//可以直接书写聚合语句
//等价于: SELECT count(*) as total FROM `foods`
total := []int{}

//Model函数,用于指定绑定的模型,这里生成了一个Food{}变量。目的是从模型变量里面提取表名,Pluck函数我们没有直接传递绑定表名的结构体变量,gorm库不知道表名是什么,所以这里需要指定表名
//Pluck函数,主要用于查询一列值
db.Model(&Food{}).Select("count(*) as total").Pluck("total", &total)

fmt.Println(total[0])

order

1
2
//等价于: SELECT * FROM `foods`  WHERE (create_time >= '2018-11-06 00:00:00') ORDER BY create_time desc
db.Where("create_time >= ?", "2018-11-06 00:00:00").Order("create_time desc").Find(&foods)

limit / offset

1
2
//等价于: SELECT * FROM `foods` ORDER BY create_time desc LIMIT 10 OFFSET 0 
db.Order("create_time desc").Limit(10).Offset(0).Find(&foods)

count

1
2
3
4
5
var total int64 = 0
//等价于: SELECT count(*) FROM `foods`
//这里也需要通过model设置模型,让gorm可以提取模型对应的表名
db.Model(Food{}).Count(&total)
fmt.Println(total)

group by ( 通常结合 自己新定义一个结构体+scan() )

1
2
3
4
5
6
7
8
9
10
11
12
13
/统计每个商品分类下面有多少个商品
//定一个Result结构体类型,用来保存查询结果
type Result struct {
Type int
Total int
}

var results []Result
//等价于: SELECT type, count(*) as total FROM `foods` GROUP BY type HAVING (total > 0)
db.Model(Food{}).Select("type, count(*) as total").Group("type").Having("total > 0").Scan(&results)

//scan类似Find都是用于执行查询语句,然后把查询结果赋值给结构体变量,区别在于scan不会从传递进来的结构体变量提取表名.
//这里因为我们重新定义了一个结构体用于保存结果,但是这个结构体并没有绑定foods表,所以这里只能使用scan查询函数。

直接执行sql语句

1
2
3
4
sql := "SELECT type, count(*) as  total FROM `foods` where create_time > ? GROUP BY type HAVING (total > 0)"
//因为sql语句使用了一个问号(?)作为绑定参数, 所以需要传递一个绑定参数(Raw第二个参数).
//Raw函数支持绑定多个参数
db.Raw(sql, "2018-11-06 00:00:00").Scan(&results) //依旧scan

更新数据

save 先拿出来,改字段,再塞回去

1
2
3
4
5
6
7
8
//等价于: SELECT * FROM `foods`  WHERE (id = '2') LIMIT 1
db.Where("id = ?", 2).Take(&food)

//修改food模型的值
food.Price = 100

//等价于: UPDATE `foods` SET `title` = '可乐', `type` = '0', `price` = '100', `stock` = '26', `create_time` = '2018-11-06 11:12:04' WHERE `foods`.`id` = '2'
db.Save(&food)

update 直接改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//更新food模型对应的表记录
//等价于: UPDATE `foods` SET `price` = '25' WHERE `foods`.`id` = '2'
db.Model(&food).Update("price", 25)
//通过food模型的主键id的值作为where条件,更新price字段值。 (food里面有id字段)


//上面的例子只是更新一条记录,如果我们要更全部记录怎么办?
//等价于: UPDATE `foods` SET `price` = '25'
db.Model(&Food{}).Update("price", 25)
//注意这里的Model参数,使用的是Food{},新生成一个空白的模型变量,没有绑定任何记录。
//因为Food{}的id为空,gorm库就不会以id作为条件,where语句就是空的
//根据自定义条件更新记录,而不是根据主键id
//等价于: UPDATE `foods` SET `price` = '25' WHERE (create_time > '2018-11-06 20:00:00')
db.Model(&Food{}).Where("create_time > ?", "2018-11-06 20:00:00").Update("price", 25)




//改多个字段:

//通过结构体变量设置更新字段
updataFood := Food{
Price:120,
Title:"柠檬雪碧",
}

//根据food模型更新数据库记录
//等价于: UPDATE `foods` SET `price` = '120', `title` = '柠檬雪碧' WHERE `foods`.`id` = '2'
//Updates会忽略掉updataFood结构体变量的零值字段, 所以生成的sql语句只有price和title字段。
db.Model(&food).Updates(&updataFood)



//根据自定义条件更新记录,而不是根据模型id
updataFood := Food{
Stock:120,
Title:"柠檬雪碧",
}

//设置Where条件,Model参数绑定一个空的模型变量
//等价于: UPDATE `foods` SET `stock` = '120', `title` = '柠檬雪碧' WHERE (price > '10')
db.Model(&Food{}).Where("price > ?", 10).Updates(&updataFood)

总结model的作用:

场景1:通过模型实例更新(带ID条件)

1
2
3
4
5
6
food := Food{ID: 2} // 包含ID的模型实例
updataFood := Food{
Price: 120,
Title: "柠檬雪碧",
}
db.Model(&food).Updates(&updataFood)

生成SQL

1
UPDATE `foods` SET `price`=120, `title`='柠檬雪碧' WHERE `id`=2

关键点

  • Model(&food) 中的 food 必须包含主键字段(如ID),否则可能全表更新
  • Updates() 自动忽略结构体零值字段(如 0/""/false
  • 风险:若 food 未设置ID,会导致全表更新(危险!)

场景2:自定义条件更新(空模型+WHERE)

1
2
3
4
db.Model(&Food{}).Where("price > ?", 10).Updates(Food{
Stock: 120,
Title: "柠檬雪碧",
})

生成SQL

1
UPDATE `foods` SET `stock`=120, `title`='柠檬雪碧' WHERE price > 10

关键点

  • &Food{} 是空模型实例,仅用于提取表名
  • 安全机制:必须配合 Where() 使用,避免全表更新
  • 仍会忽略零值字段(如 Stock: 0 不会被更新)

场景3:强制更新零值(使用map)

1
2
3
4
5
data := map[string]interface{}{
"stock": 0, // 零值会被更新
"price": 35,
}
db.Model(&Food{}).Where("id = ?", 2).Updates(data)

生成SQL

1
UPDATE `foods` SET `price`=35, `stock`=0 WHERE id=2

优势

  • 通过 map 强制更新所有字段(包括零值)
  • 更灵活的数据结构(支持混合类型字段)
  • 避免结构体零值忽略问题

删除数据

1
2
3
4
5
6
7
8
9
10
11
12
13
food := Food{}
//先查询一条记录, 保存在模型变量food
//等价于: SELECT * FROM `foods` WHERE (id = '2') LIMIT 1
db.Where("id = ?", 2).Take(&food)

//删除food对应的记录,通过主键Id标识记录
//等价于: DELETE from `foods` where id=2;
db.Delete(&food)


//等价于:DELETE from `foods` where (`type` = 5);
db.Where("type = ?", 5).Delete(&Food{})
//这里Delete函数需要传递一个空的模型变量指针,主要用于获取模型变量绑定的表名。 不能传递一个非空的模型变量,否则就变成删除指定的模型数据,自动在where语句加上类似id = 2这样的主键约束条件

事务处理

自动事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
db.Transaction(func(tx *gorm.DB) error {
// 在事务中执行一些 db 操作(从这里开始,应该使用 'tx' 而不是 'db')
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
// 返回任何错误都会回滚事务
return err
}

if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
return err
}

// 返回 nil 提交事务
return nil
})

手动事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 开启事务
tx := db.Begin()

//在事务中执行数据库操作,使用的是tx变量,不是db。

//库存减一
//等价于: UPDATE `foods` SET `stock` = stock - 1 WHERE `foods`.`id` = '2' and stock > 0
//RowsAffected用于返回sql执行后影响的行数
rowsAffected := tx.Model(&food).Where("stock > 0").Update("stock", gorm.Expr("stock - 1")).RowsAffected
if rowsAffected == 0 {
//如果更新库存操作,返回影响行数为0,说明没有库存了,结束下单流程
//这里回滚作用不大,因为前面没成功执行什么数据库更新操作,也没什么数据需要回滚。
//这里就是举个例子,事务中可以执行多个sql语句,错误了可以回滚事务
tx.Rollback()
return
}
err := tx.Create(保存订单).Error

//保存订单失败,则回滚事务
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}

关联查询

一对一可以分为 Belongs ToHas One

Belongs To

顾名思义,意为 “属于”

使用场景:当子表必须关联父表,当子表没有父表,子表就没有意义(如 Comment必须属于 Post

定义类似:

1
2
3
4
5
6
7
8
9
10
11
type User struct {
gorm.Model
Name string
}

type Profile struct {
gorm.Model
Name string
UserID uint // 外键存储在 Profile 表
User User `gorm:"foreignKey:UserID"` // Profile 属于 User ,且必须含有此属性(grom会默认使用User 的id字段)
}

Has One

拥有,和属于不同的是,has one是父表拥有子表,但也可以不拥有,因为这时候父表也是有意义的

1
2
3
4
5
6
7
8
9
10
11
type User struct {
gorm.Model
Name string
Profile Profile `gorm:"foreignKey:UserID"` // User 拥有一个 Profile
}

type Profile struct {
gorm.Model
Name string
UserID uint // 外键仍然在 Profile 表,但关系方向不同
}

总结

  1. 无论是 belongs to 还是 has one,外键都在子表

  2. belongs to 子表含有父表结构体 ,has one 父表含有子表结构体

  3. tag是打在结构体上的

  4. 使用场景:当表a可以包含表b,且b不一定要有一个a的时候,可以用has one,如果b必须关联a,那就要用belongs to

关联外键

什么时候需要自定义外键?

  • 当想用非ID字段(如 UUIDEmailRefer)作为关联依据时。
  • 当数据库设计不允许使用默认外键命名时。
  • 当需要更灵活的关联逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
//belongs to
type User struct {
gorm.Model
Refer string // 自定义关联外键
Name string
}

type Profile struct {
gorm.Model
Name string
User User `gorm:"foreignKey:UserRefer;references:Refer"` // 使用 Refer 作为关联外键
UserRefer string // 外键字段
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//has one
type User struct {
gorm.Model
Refer string // 自定义关联外键
Name string
Profile Profile `gorm:"foreignKey:UserRefer;references:Refer"` // 使用 Refer 作为关联外键
}

type Profile struct {
gorm.Model
Name string
UserRefer string // 外键字段
}

查询举例

  • 想根据子表字段查询父表中的数据,只要父表,子表定义好了外键,就能像查一张表一样,直接查询
  • 想要根据父表字段查询子表数据,就要model传入空父表指针,调用Association指定子表子段,然后再调用quary方法
1
2
3
4
5
6
7
8
9
10
user := User{}
// 查询用户数据
//自动生成sql: SELECT * FROM `users` WHERE (username = 'tizi365') LIMIT 1
db.Where("username = ?", "tizi365").First(&user)


var card CreditCard
////自动生成SQL: SELECT * FROM credit_cards WHERE user_id = 123; // 123 自动从user的ID读取
// 关联查询的结果会填充到card变量
db.Model(&user).Association("CreditCard").Find(&card)

Migrate建表/操作表

1
2
3
4
5
// 根据User结构体,自动创建表结构.
db.AutoMigrate(&User{})

// 一次创建User、Product、Order三个结构体对应的表结构
db.AutoMigrate(&User{}, &Product{}, &Order{})
1
2
3
4
5
// 检测User结构体对应的表是否存在
db.Migrator().HasTable(&User{})

// 检测表名users是否存在
db.Migrator().HasTable("users")
1
2
// 根据User结构体建表
db.Migrator().CreateTable(&User{})
1
2
3
4
5
// 删除User结构体对应的表
db.Migrator().DropTable(&User{})

// 删除表名为users的表
db.Migrator().DropTable("users")
1
2
// 删除User结构体对应表中的description字段
db.Migrator().DropColumn(&User{}, "Name")