参考地址: https://gorm.io/zh_CN/docs/index.html
安装包
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
https://gorm.io/zh_CN/docs/gorm_config.html
数据库连接
**注意:**想要正确的处理 time.Time
,您需要带上 parseTime
参数, (更多参数) 要支持完整的 UTF-8 编码,您需要将 charset=utf8
更改为 charset=utf8mb4
查看 此文章 获取详情
db, err := gorm.Open(mysql.New(mysql.Config{
DSN: "root:ts123456@tcp(127.0.0.1:3306)/my_gormv2?charset=utf8mb4&parseTime=True&loc=Local",
DefaultStringSize: 256, // string 类型字段的默认长度
}), &gorm.Config{
// https://gorm.io/zh_CN/docs/gorm_config.html
SkipDefaultTransaction: false, // 跳过默认事务, GORM 会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,您可以在初始化时禁用它
NamingStrategy: schema.NamingStrategy{ // 表名命名策略
TablePrefix: "t_", // 表名前缀,`User`表为`t_users`
SingularTable: true, // 使用单数表名,启用该选项后,`User` 表将是`t_user`
},
DryRun: false, // 生成 SQL 但不执行,可以用于准备或测试生成的 SQL
DisableForeignKeyConstraintWhenMigrating: true, // 在 AutoMigrate 或 CreateTable 时,GORM 会自动创建外键约束,若要禁用该特性,可将其设置为 true
})
链接池
https://gorm.io/zh_CN/docs/connecting_to_the_database.html#%E8%BF%9E%E6%8E%A5%E6%B1%A0
// GORM 使用 database/sql 维护连接池
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(10) // SetMaxIdleConns 设置空闲连接池中连接的最大数量
sqlDB.SetMaxOpenConns(100) // SetMaxOpenConns 设置打开数据库连接的最大数量
sqlDB.SetConnMaxLifetime(time.Hour) // SetConnMaxLifetime 设置了连接可复用的最大时间
完整内容
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"time"
)
// 定义全局变量
var GLOBAL_DB *gorm.DB
func main() {
db, _ := gorm.Open(mysql.New(mysql.Config{
DSN: "root:ts123456@tcp(127.0.0.1:3306)/my_gormv2?charset=utf8mb4&parseTime=True&loc=Local",
}), &gorm.Config{
// https://gorm.io/zh_CN/docs/gorm_config.html
SkipDefaultTransaction: false, // 跳过默认事务, GORM 会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,您可以在初始化时禁用它
NamingStrategy: schema.NamingStrategy{ // 表名命名策略
//TablePrefix: "t_", // 表名前缀,`User`表为`t_users`
SingularTable: true, // 使用单数表名,启用该选项后,`User` 表将是`t_user`
},
DryRun: false, // 生成 SQL 但不执行,可以用于准备或测试生成的 SQL
DisableForeignKeyConstraintWhenMigrating: true, // 在 AutoMigrate 或 CreateTable 时,GORM 会自动创建外键约束,若要禁用该特性,可将其设置为 true
})
// GORM 使用 database/sql 维护连接池
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(10) // SetMaxIdleConns 设置空闲连接池中连接的最大数量
sqlDB.SetMaxOpenConns(100) // SetMaxOpenConns 设置打开数据库连接的最大数量
sqlDB.SetConnMaxLifetime(time.Hour) // SetConnMaxLifetime 设置了连接可复用的最大时间
GLOBAL_DB = db // 把 db 赋值
// https://gorm.io/zh_CN/docs/migration.html
// AutoMigrate 用于自动迁移表
//db.AutoMigrate(&User{})
// 如果存在表则删除(删除时会忽略、删除外键约束)
//M.DropTable(&User{})
}
表迁移
https://gorm.io/zh_CN/docs/migration.html
type User struct {
Name string
Age int
}
// https://gorm.io/zh_CN/docs/migration.html
// AutoMigrate 用于自动迁移表
//db.AutoMigrate(&User{})
// 迁移表
M := db.Migrator()
M.CreateTable(&User{}) // 创建表
// 如果存在表则删除(删除时会忽略、删除外键约束)
//M.DropTable(&User{})
// 检查 `User` 对应的表是否存在
fmt.Println(M.HasTable(&User{}))
https://gorm.io/zh_CN/docs/models.html
模型是标准的 struct,由 Go 的基本数据类型、实现了 Scanner 和 Valuer 接口的自定义类型及其指针或别名组成
GORM 倾向于约定,而不是配置。默认情况下,GORM 使用 ID
作为主键,使用结构体名的 蛇形复数
作为表名,字段名的 蛇形
作为列名,并使用 CreatedAt
、UpdatedAt
字段追踪创建、更新时间
GORM 定义一个 gorm.Model
结构体
// gorm.Model 的定义
type Model struct {
UUID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
将它嵌入到结构体中
type TestUser struct {
Model Model `gorm:"embedded"`
Name string
Email *string
Age uint8
Birthday *time.Time
MemberNumber sql.NullString
ActivatedAt sql.NullTime
}
字段标签
声明model时,tag 是可选的
column : 列名称
primaryKey : 列为主键
unique : 列为唯一
default : 指定列的默认值
not null : 指定列不允许为空
embedded : 嵌套字段
embeddedPrefix:嵌套字段的列名前缀
index : 指定索引
uniqeIndex : 唯一索引
comment : 注释,数据库字段描述
precision : 精度(浮点型)
size : 列大小
示例
type TestUser struct {
Model Model `gorm:"embedded"`
Name string `gorm:"comment:姓名"`
Email *string `gorm:"column:my_email;comment:邮箱"`
}
https://gorm.io/zh_CN/docs/create.html
创建数据
user := TestUser{Name: "vic", Age: 20}
result := GLOBAL_DB.Create(&user) // 通过指针来创建数据
// 返回 error, 影响的条数
fmt.Println(result.Error, "=======", result.RowsAffected)
指定字段创建数据
user := TestUser{Name: "vic", Age: 22}
// 用 Select 指定的字段创建
result := GLOBAL_DB.Select("age").Create(&user) // 通过指针来创建数据
// 返回 error, 影响的条数
fmt.Println(result.Error, "=======", result.RowsAffected)
创建一个记录且一同忽略传递要略去的字段值
user := TestUser{Name: "ccg", Age: 22}
// 用 Omit 指定的字段创建
result := GLOBAL_DB.Omit("age").Create(&user) // 通过指针来创建数据
// 返回 error, 影响的条数
fmt.Println(result.Error, "=======", result.RowsAffected)
批量插入数据
利用数组,将一个 slice
传递给 Create
方法
users := []TestUser{
{Name: "C1", Age: 22},
{Name: "C2", Age: 22},
{Name: "C3", Age: 22},
}
// 批量插入数据
result := GLOBAL_DB.Create(&users) // 通过指针来创建数据
// 返回 error, 影响的条数
fmt.Println(result.Error, "=======", result.RowsAffected)
https://gorm.io/zh_CN/docs/query.html
检索单个对象
// 获取第一条记录(主键升序),
// 方式一: 目标 struct 是指针
var ret1 TestUser
GLOBAL_DB.First(&ret1)
fmt.Println(ret1)
// 方式二:通过 `db.Model()` 指定了 model
ret2 := map[string]interface{}{}
GLOBAL_DB.Model(&TestUser{}).First(&ret2)
fmt.Println(ret2)
用主键检索
// 主键检索数据
// 主键是数字类型
//SELECT * FROM users WHERE uuid = 5;
var user1 TestUser
GLOBAL_DB.First(&user1, 5)
fmt.Println(user1)
// 把返回的数据拿出来,增加判断
var User TestUser
dbRes := GLOBAL_DB.First(&User, 5)
fmt.Println(dbRes.Error) // 返回结果 record not found
fmt.Println(errors.Is(dbRes.Error,gorm.ErrRecordNotFound)) // dbRes.Error 查询返回的错误,gorm.ErrRecordNotFound 系统gorm 返回的错误,结果返回 true;通过判断查询是否成功,然后再进行下一步操作
//主键是字符串
var user2 TestUser
GLOBAL_DB.First(&user2, "name = ?", "C2")
fmt.Println(user2)
检索全部数据
// 检索全部数据, 使用切片接收返回结果
var users []TestUser
GLOBAL_DB.Find(&users)
fmt.Println(users)
String条件
// 获取第一条匹配的记录
var user TestUser
// 忽略不想显示的字段
GLOBAL_DB.Omit("created_at", "updated_at").Where("name = ?", "vic").First(&user)
fmt.Println(user)
// 获取 LIKE 匹配的所有记录
// SELECT * FROM users WHERE name LIKE '%g%';
var users []TestUser
GLOBAL_DB.Where("name LIKE ?", "%g%").Find(&users)
fmt.Println("LIKE: ", users)
// AND
var U1 []TestUser
GLOBAL_DB.Where("name = ? AND age >= ?", "ccg", "21").Find(&U1)
fmt.Println("AND: ", U1)
Struct & Map
// Struct
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;
var user TestUser
GLOBAL_DB.Where(&TestUser{Name: "ccg", Age: 21}).First(&user)
fmt.Println("Struct: ", user)
// Map
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
users := map[string]interface{}{}
GLOBAL_DB.Model(&TestUser{}).Where(map[string]interface{}{"name": "B2", "age": 23}).Find(&users)
fmt.Println("Map: ", users)
https://gorm.io/zh_CN/docs/update.html
Update 更新单列
// Update 更新单个列
// 当使用了 Model 方法,且该对象主键有值
GLOBAL_DB.Model(&TestUser{}).Where("age = ?", 21).Update("name", "A2")
// 根据条件和 model 的值进行更新
var users TestUser
GLOBAL_DB.Model(&users).Where("name = ?", "vic").Update("age", 18)
Save 保存所有字段
// Save 会保存所有的字段,即使字段是零值
// 更新查询的第一条数据
var user TestUser
GLOBAL_DB.First(&user)
user.Name = "CC"
user.Age = 100
GLOBAL_DB.Save(&user)
// 批量更新符合条件的列
var users []TestUser
dbRes := GLOBAL_DB.Where("name LIKE ?", "%2%").Find(&users)
for k := range users {
users[k].Age = 30
}
dbRes.Save(&users)
Updates 更新所有字段 — 常用
// Updates 更新所有字段 struct 和 map[string]interface{} 参数,
// Struct 更新时,GORM 只会更新非零字段
var user TestUser
GLOBAL_DB.First(&user).Updates(TestUser{Name: "AA", Age: 222})
// 如果您想确保指定字段被更新,你应该使用 Select 更新选定字段,或使用 map 来完成更新操作
GLOBAL_DB.First(&user).Updates(map[string]interface{}{"name": "A5", "age": 222})
// 批量更新
// Struct
GLOBAL_DB.Where(&TestUser{}).Where("name = ?", "A2").Updates(TestUser{Age: 55, Address: "北京"})
// 根据 map 更新
GLOBAL_DB.Table("test_user").Where("age IN ?", []int{22, 30}).Updates(map[string]interface{}{"address": "上海"})
https://gorm.io/zh_CN/docs/delete.html
// 如果您的模型包含了一个 gorm.DeletedAt 字段(gorm.Model 已经包含了该字段),它将自动获得软删除的能力!
// 拥有软删除能力的模型调用 Delete 时,记录不会被从数据库中真正删除。但 GORM 会将 DeletedAt 置为当前时间, 并且你不能再通过正常的查询方法找到该记录。
// 根据主键删除,可以使用数字,字符串
GLOBAL_DB.Delete(&TestUser{}, 2)
GLOBAL_DB.Delete(&TestUser{}, "5")
GLOBAL_DB.Delete(&TestUser{}, []int{1, 3, 4})
// 批量删除
// 如果指定的值不包括主属性,那么 GORM 会执行批量删除
GLOBAL_DB.Where("name LIKE ?", "%g%").Delete(TestUser{})
// 另一种写法
GLOBAL_DB.Where(TestUser{}, "name LIKE ?", "%g%").Delete(TestUser{})
// 永久删除
GLOBAL_DB.Unscoped().Delete(&TestUser{}, 10)
// https://gorm.io/zh_CN/docs/sql_builder.html#%E5%8E%9F%E7%94%9F-SQL
type Result struct {
ID int
Name string
Age int
}
var result Result
db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)
db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)
var age int
db.Raw("SELECT SUM(age) FROM users WHERE role = ?", "admin").Scan(&age)
var users []User
db.Raw("UPDATE users SET name = ? WHERE age = ? RETURNING id, name", "jinzhu", 20).Scan(&users)
belongs to 创建的每一个实例都 “属于” 另一个模型的一个实例
示例结构体 https://gorm.io/zh_CN/docs/belongs_to.html
// User 属于 Company,CompanyID 是关联外键,并且每个 User 能且只能被分配一个 Company,
type User struct { // 用户
gorm.Model
Name string `gorm:"unique"`
CompanyID uint
Company Company
}
type Company struct { // 公司
gorm.Model
Name string
}
基本操作
// 创建数据库, 自动创建属于表 Company
//GLOBAL_DB.AutoMigrate(&User{})
// 创建数据
// 创建两家 Company
C1 := Company{
Name: "百度",
}
C2 := Company{
Model: gorm.Model{ID: 2},
Name: "新浪",
}
GLOBAL_DB.Create(&C1)
GLOBAL_DB.Create(&C2)
// 创建用户
// 根据已有的ID进行管理
U1 := User{
Name: "Vic",
CompanyID: 1,
}
GLOBAL_DB.Create(&U1)
// 创建数据,自动关联
C3 := Company{
Name: "拼多多",
}
U2 := User{
Name: "ccg",
Company: C3,
}
GLOBAL_DB.Create(&U2)
// 查询
var company Company
GLOBAL_DB.First(&company, 2)
fmt.Println("查询:", company)
预加载 https://gorm.io/zh_CN/docs/preload.html
GORM 运行通过 Preload 或者 Joins 来主动加载实体的关联关系
// Preload 预加载
var puser User
GLOBAL_DB.Preload("Company").First(&puser, 1)
fmt.Println("Preload预加载查询:", puser)
// Jions
var juser User
GLOBAL_DB.Joins("Company").First(&juser, 2)
fmt.Println("Joins预加载查询:", juser)
Belongs to 的 CURD 使用关联模式进行操作
https://gorm.io/zh_CN/docs/associations.html#%E5%85%B3%E8%81%94%E6%A8%A1%E5%BC%8F
// 关联模式
/*
判断是否下面的二个要求,符合会开始关联模式,否则返回错误
1. user 是源模型,主键不能为空,
2. 关系的字段名称是 Company
*/
var user User
err := GLOBAL_DB.Model(&user).Association("Company").Error
if err != nil {
fmt.Println("该数据没有关联:", err)
}
// 添加关联, 前提表中数据已经存在
u1 := User{
Model: Model{UUID: 3},
}
c1 := Company{Model: Model{UUID: 4}}
GLOBAL_DB.Model(&u1).Association("Company").Append(&c1)
// 替换关联
u2 := User{
Model: Model{
UUID: 2,
},
}
c4 := Company{
Model: Model{
UUID: 4,
},
}
c7 := Company{
Model: Model{
UUID: 7,
},
}
GLOBAL_DB.Model(&u2).Association("Company").Replace(&c4, &c7)
// 删除源模型与关联之间的所有引用
u1 := User{
Model: Model{
UUID: 1,
},
}
GLOBAL_DB.Model(&u1).Association("Company").Clear()
https://gorm.io/zh_CN/docs/has_one.html
基础数据
// Has one 与另外一个模型建立一对一的关联,这种关联表明一个模型的每个实例都包涵或拥有另一个模型的实例
// 每个 User1 只能有一张 Card ,User1 有一张 Caed,UserID 是外键
type User1 struct {
Model
Name string
Card Card
}
type Card struct {
Model
Number string
User1ID uint
}
添加数据
执行预加载
//创建数据库
//GLOBAL_DB.AutoMigrate(&User1{}, &Card{})
// 添加数据
/*
u11 := User1{
Name: "u11",
}
u22 := User1{
Name: "u22",
}
c11 := Card{
Number: "11111",
User1ID: 1,
}
c22 := Card{
Number: "2222",
User1ID: 2,
}
GLOBAL_DB.Create(&u11)
GLOBAL_DB.Create(&u22)
GLOBAL_DB.Create(&c11)
GLOBAL_DB.Create(&c22)
*/
// 查询
var card Card
GLOBAL_DB.First(&card)
fmt.Println(card)
// 预加载
// Preload 预加载
var puser1 User1
GLOBAL_DB.Preload("Card").Find(&puser1, 2)
fmt.Println("Preload预加载查询:", puser1)
// Jions 预加载
var juser1 User1
GLOBAL_DB.Joins("Card").Find(&juser1, 1)
fmt.Println("Joins预加载查询:", juser1)
Has one 的 CURD 使用关联模式进行操作
// 添加关联,前提数据已经在数据库存在
c33 := Card{
Model: Model{
UUID: 3,
},
}
u33 := User1{
Model: Model{
UUID: 3,
},
}
// 添加关联
GLOBAL_DB.Model(&u33).Association("Card").Append(&c33)
// 替换关联
u11 := User1{
Model: Model{
UUID: 1,
},
}
c11 := Card{
Model: Model{
UUID: 1,
},
}
c22 := Card{
Model: Model{
UUID: 2,
},
}
GLOBAL_DB.Model(&u11).Association("Card").Replace(&c11, &c22)
// 删除关联
GLOBAL_DB.Model(&u11).Association("Card").Delete(&u11)
// 清空关联
GLOBAL_DB.Model(&u11).Association("Card").Clear()
https://gorm.io/zh_CN/docs/has_many.html
has many
与另一个模型建立了一对多的连接。 不同于has one
,拥有者可以有零或多个关联模型
创建模型
// 声明 teacher 和 class 模型,且每个 class 可以有多个 teacher
type Class struct { // 班级
Model
Name string
Teachers []Teacher
}
type Teacher struct { // 教师
Model
name string
ClassID uint
}
// 创建数据库,
//GLOBAL_DB.AutoMigrate(&Class{}, &Teacher{})
添加数据
// 添加数据
// 创建二个 Teacher
T1 := Teacher{
Name: "英语老师",
}
T2 := Teacher{
Name: "法语老师",
}
// 创建一个 class, 里面包涵二个 teacher
C1 := Class{
Name: "初中一年级",
Teachers: []Teacher{T1, T2},
}
//使用Create 方法创建
GLOBAL_DB.Create(&C1)
数据查询
https://gorm.io/zh_CN/docs/preload.html
// 查询, 只查询到 class,没有查询到包涵的 teacher
var class Class
GLOBAL_DB.First(&class)
fmt.Println(class)
// 使用 Preload 预加载的模式, 查询关联的 teacher
var class Class
GLOBAL_DB.Preload("Teachers").First(&class)
fmt.Println(class)
// Grom 运行使用带条件的Preload, 查询包涵 英语老师
var class Class
GLOBAL_DB.Preload("Teachers", "name = ?", "英语老师").First(&class)
fmt.Println(class)
// 可以通过 func(db *gorm.DB) *gorm.DB 实现自定义预加载 SQL
var class Class
GLOBAL_DB.Preload("Teachers", func(db *gorm.DB) *gorm.DB {
return db.Where("name = ?", "法语老师")
}).First(&class)
fmt.Println(class)
嵌套预加载
https://gorm.io/zh_CN/docs/preload.html#%E5%B5%8C%E5%A5%97%E9%A2%84%E5%8A%A0%E8%BD%BD
// 增加 Info 模型,和 Teacher 属于 一对一关系
type Info struct {
Model
Money int
TeacherID uint
}
type Teacher struct { // 教师
Model
Name string
ClassID uint
Info Info
}
查询数据
var class Class
GLOBAL_DB.Preload("Teachers.Info").First(&class)
fmt.Println(class)
// 只查询包涵 英语老师
var class Class
GLOBAL_DB.Preload("Teachers.Info").Preload("Teachers", "name = ?", "英语老师").First(&class)
fmt.Println(class)
// 注意:Preload 带条件的预加载查询,只适用于查询当前的预加载语句,如果查询 money > 200 的结果就会报错,返回所有
var class Class
GLOBAL_DB.Preload("Teachers.Info", "money > 200").Preload("Teachers").First(&class)
fmt.Println(class)
使用joins 关联查询解决上述问题
joins Preload 适用于一对一的关系,例如 has one,belongs to
// 使用 joins Preload 查询数据
var class Class
GLOBAL_DB.Preload("Teachers", func(db *gorm.DB) *gorm.DB {
return db.Joins("Info")
}).First(&class)
fmt.Println(class)
// 使用 joins 查询实现 money > 200 的 teacher
var class Class
GLOBAL_DB.Preload("Teachers", func(db *gorm.DB) *gorm.DB {
return db.Joins("Info").Where("money > 200")
}).First(&class)
fmt.Println(class)
https://gorm.io/zh_CN/docs/many_to_many.html
many to Many 会在二个 model 中添加一张链接表
Model 模型
// 创建模型 user 和 address ,一个 user 可以包含多个address; 多个user可以属于一种 address,user包含 info,has one 关系
type InfoR struct {
Model
Money int
PeopleID uint
}
type People struct {
Model
Name string
InfoR InfoR
Addresses []Address `gorm:"many2many:people_address;"`
}
type Address struct {
Model
Name string
Peoples []People `gorm:"many2many:people_address;"`
}
GLOBAL_DB.AutoMigrate(&InfoR{}, &People{}, &Address{})
添加数据
// 添加数据
i1 := Infos{
Money: 2000,
}
a1 := Address{
Name: "北京市",
}
a2 := Address{
Name: "上海市",
}
p := People{
Name: "王小二",
Infos: i1,
Addresses: []Address{a1, a2},
}
// 添加数据, 指定address UUID
i2 := Infos{
Money: 1000,
}
a1 := Address{
Model: Model{
UUID: 1,
},
Name: "北京市",
}
a2 := Address{
Model: Model{
UUID: 2,
},
Name: "上海市",
}
p := People{
Name: "王小三",
Infos: i2,
Addresses: []Address{a1, a2},
}
GLOBAL_DB.Create(&p)
数据查询
// 查询 UUID 为 1 的 people, 没有查询出address 和 infos
p := People{
Model: Model{
UUID: 1,
},
}
GLOBAL_DB.Find(&p)
fmt.Println(p)
// 使用关联模式查询, 只查询出来了 address
var address []Address
GLOBAL_DB.Model(&p).Association("Addresses").Find(&address)
fmt.Println(address)
// 查询出来了 address, 结果想把 people也带上,使用 Preload
GLOBAL_DB.Model(&p).Preload("Peoples").Association("Addresses").Find(&address)
fmt.Println(address)
// 查询出 address people,结果把 infos结果也带着, 使用 Preload joins
GLOBAL_DB.Model(&p).Preload("Peoples", func(db *gorm.DB) *gorm.DB {
return db.Joins("Infos")
}).Association("Addresses").Find(&address)
fmt.Println(address)
// 只带出 money > 1500
GLOBAL_DB.Model(&p).Preload("Peoples", func(db *gorm.DB) *gorm.DB {
return db.Joins("Infos").Where("money > 1500")
}).Association("Addresses").Find(&address)
fmt.Println(address)
https://gorm.io/zh_CN/docs/has_many.html#%E5%A4%9A%E6%80%81%E5%85%B3%E8%81%94
Gorm 为 has one
和 has many
提供了多态关联支持。它会将拥有者实体的表名、主键都保存到多态类型的字段中
`gorm:"polymorphic:Owner;polymorphicValue:master"`
模型示例
// Cat 实体
type Cat struct {
Model
Name string
Toy Toy `gorm:"polymorphic:Owner;polymorphicValue:CatToy"`
}
/*
Dog 实体,多态不可以被多个实体同时拥有,但是可以一个实体对应多个多态
polymorphic: Owner 对应 多态的 OwnerID、OwnerType 前缀名称,类型和主键类型保持一致;
polymorphicValue:DogToy 设置多态类型的值,一般设置成主键的名称和多态名
*/
type Dog struct {
Model
Name string
Toy []Toy `gorm:"polymorphic:Owner;polymorphicValue:DogToy"`
}
// Toy 多态实体
type Toy struct {
Model
Name string
OwnerID uint
OwnerType string
}
表结构
uuid | name |
---|---|
uuid | name |
---|---|
uuid | name | owner_id | owner_type |
---|---|---|---|
添加数据
GLOBAL_DB.Create(&Cat{Name: "小黑猫", Toy: Toy{Name: "小老鼠"}})
GLOBAL_DB.Create(&Dog{Name: "小黑狗", Toy: []Toy{
{Name: "小骨头"},
{Name: "大骨头"},
}})
数据库表数据
uuid | name |
---|---|
1 | 小黑猫 |
uuid | name |
---|---|
1 | 小黑狗 |
uuid | name | owner_id | owner_type |
---|---|---|---|
1 | 小老鼠 | 1 | CatToy |
2 | 小骨头 | 1 | DogToy |
3 | 大骨头 | 1 | DogToy |
https://gorm.io/zh_CN/docs/associations.html
foreignKey:指定当前模型的列作为连接表的外键,即指定外键以自己的哪个key去关联
references:指定引用表名的列名,其将被映射为连接表的外键,指定引用被关联结果的那个key
joinForeignKey:指定连接表的外键列名,其将被映射到当前表
joinReferences:指定连接表的外键列表,其将别映射到引用表
示例代码
package main
/*
Zoo 实体
foreignKey:ZooName 指向连接表(Monkey)的外键的key(Monkey的ZooName列名)
references:Name 引用本结构体的列名(Zoo的Name列名),被映射为连接表的外键名称
*/
type Zoo struct {
Model
Name string
Monkey []Monkey `gorm:"foreignKey:ZooName;references:Name"`
}
// Monkey 实体
type Monkey struct {
Model
Name string
ZooName string
}
func Foreign() {
// 创建表
GLOBAL_DB.AutoMigrate(&Monkey{}, &Zoo{})
// 增加数据
GLOBAL_DB.Create(&Zoo{
Name: "北京动物园",
Monkey: []Monkey{
{Name: "小黑猴"},
{Name: "小白猴"},
}})
}
数据库表内容
Zoo 表
uuid | name |
---|---|
1 | 北京动物园 |
Monkey 表
uuid | name | zoo_name |
---|---|---|
1 | 小黑猴 | 北京动物园 |
2 | 小白猴 | 北京动物园 |
代码示例
package main
/*
Zoo 实体
注意: 某些数据库只允许在唯一索引字段上创建外键,如果您在迁移时会创建外键,则需要指定 unique index 标签。
many2many:zoo_monkey 中间关联表
foreignKey:Name 指向本表的主键列名(Zoo的Name)
joinForeignKey:ZooName 重写中间表关联本表的列名
references:Name 指向关联表的主键列名(Monkey 的 Name)
joinReferences:MonkeyName 重写中间表关联的关联表的列名
*/
type Zoo struct {
Model
Name string `gorm:"primaryKey"`
Monkey []Monkey `gorm:"many2many:zoo_monkey;foreignKey:Name;joinForeignKey:ZooName;References:Name;joinReferences:MonkeyName"`
}
// Monkey 实体
type Monkey struct {
Model
Name string `gorm:"primaryKey"`
}
func Foreign() {
// 创建表
GLOBAL_DB.AutoMigrate(&Zoo{}, &Monkey{})
// 增加数据
GLOBAL_DB.Create(&Zoo{
Name: "北京动物园",
Monkey: []Monkey{
{Name: "小黑猴"},
{Name: "小白猴"},
}})
}
数据库表内容
zoo
uuid | name |
---|---|
1 | 北京动物园 |
monkey
uuid | name |
---|---|
1 | 小黑猴 |
2 | 小白猴 |
zoo_monkey
zoo_name | monkey_name |
---|---|
北京动物园 | 小黑猴 |
北京动物园 | 小白猴 |
https://gorm.io/zh_CN/docs/transactions.html
为了确保数据一致性,GORM 会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,您可以在初始化时禁用它,这将获得大约 30%+ 性能提升。
// 全局禁用
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
SkipDefaultTransaction: true,
})
package main
import "gorm.io/gorm"
// Ts 结构体
type Ts struct {
Model
Name string `gorm:"unique"`
}
func Transaction() {
// 创建表
GLOBAL_DB.AutoMigrate(&Ts{})
// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
GLOBAL_DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&Ts{Name: "事务一"}).Error; err != nil {
// 返回任何错误都会回滚事务,即不执行此事务
return err
}
if err := tx.Create(&Ts{Name: "事务二"}).Error; err != nil {
// 返回任何错误都会回滚事务,即不执行此事务
return err
}
/*
// 因为指定了 Name 列为名为唯一值,如果开启下面的事务,则上面的二条事务不会执行
if err := tx.Create(&Ts{Name: "事务一"}).Error; err != nil {
// 返回任何错误都会回滚事务,即不执行此事务
return err
}
*/
// 返回 nil 提交事务
return nil
})
}
package main
import "gorm.io/gorm"
// Ts 结构体
type Ts struct {
Model
Name string `gorm:"unique"`
}
func Transaction() {
GLOBAL_DB.AutoMigrate(&Ts{})
// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
GLOBAL_DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&Ts{Name: "事务一"}).Error; err != nil {
// 返回任何错误都会回滚事务,即不执行此事务
return err
}
// GORM 支持嵌套事务, 可以回滚较大事务内执行的一部分操作
tx.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&Ts{Name: "事务二"}).Error; err != nil {
return err
}
// 因为指定了 Name 列为名为唯一值,如果开启下面的事务,列名重复会报错,则此嵌套里面的事务都不会执行
err := tx.Create(&Ts{Name: "事务一"})
if err != nil {
return err.Error
}
// 返回 nil 提交事务
return nil
})
// 返回 nil 提交事务
return nil
})
}
Gorm 支持直接调用事务控制方法(commit、rollback)
package main
// Ts 结构体
type Ts struct {
Model
Name string `gorm:"unique"`
}
func Transaction() {
GLOBAL_DB.AutoMigrate(&Ts{})
// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
// 开始事务
tx := GLOBAL_DB.Begin()
// 执行 数据库操作
tx.Create(&Ts{Name: "事务一"})
tx.Create(&Ts{Name: "事务二"})
tx.Create(&Ts{Name: "事务一"})
// 遇到错误时回滚事务; 上面Name是唯一值,出现了多次 事务一 ,所以回滚上面的事务,即不执行
tx.Rollback()
// 提交事务
tx.Commit()
}
SavePoint、
Rollbackto
GORM 提供了 SavePoint
、Rollbackto
方法,来提供保存点以及回滚至保存点功能
tx := db.Begin()
tx.Create(&user1)
tx.SavePoint("sp1")
tx.Create(&user2)
tx.RollbackTo("sp1") // Rollback user2
tx.Commit() // Commit user1
GORM 提供了少量接口,使用户能够为 GORM 定义支持的数据类型
package main
import (
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
)
// CInfo 定义一个数据类型
type CInfo struct {
Sex string
Age int
}
// CUser 实体类引用定义的(CInfo)类型
type CUser struct {
Model
Name string
Info CInfo
}
// Value 实现 driver.Value 接口,Value 返回 json value ;存数据用
func (c CInfo) Value() (driver.Value, error) {
// 把数据加工成 json
str, err := json.Marshal(c)
if err != nil {
return nil, err
}
// 并返回 string 类型的数据
return string(str), nil
}
// Scan 实现 sql.Scanner 接口, 渲染出 value 的值;查数据用
func (c *CInfo) Scan(value interface{}) error {
// 接收value的值,利用类型断言判断值是否为 字节类型
str, ok := value.([]byte)
if !ok {
return errors.New(fmt.Sprint("数据类型不匹配"))
}
// 断言成功,把结果渲染到 c 指针上面
json.Unmarshal(str, c)
return nil
}
func CustomType() {
// 创建数据库
GLOBAL_DB.AutoMigrate(&CUser{})
/*
// 添加数据
GLOBAL_DB.Create(&CUser{
Name: "王小二",
Info: CInfo{
Sex: "男",
Age: 20,
}})
*/
// 查询数据
var c CUser
GLOBAL_DB.First(&c)
fmt.Println(c)
}
package main
import (
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"strings"
)
// CInfo 定义一个数据类型
type CInfo struct {
Sex string
Age int
}
// Args 定义数组类型
type Args []string
// CUser 实体类引用定义的(CInfo)类型
type CUser struct {
Model
Name string
Info CInfo // 使用自定义的 CInfo 类型
Address Args // 使用自定义的 Args 类型
}
// Value 实现 driver.Value 接口,Value 返回 json value ;存json数据用
func (c CInfo) Value() (driver.Value, error) {
// 把数据加工成 json
str, err := json.Marshal(c)
if err != nil {
return nil, err
}
// 并返回 string 类型的数据
return string(str), nil
}
// Scan 实现 sql.Scanner 接口, 渲染出 value 的值;查Json数据用
func (c *CInfo) Scan(value interface{}) error {
// 接收value的值,利用类型断言判断值是否为 字节类型
str, ok := value.([]byte)
if !ok {
return errors.New(fmt.Sprint("数据类型不匹配"))
}
// 断言成功,把结果渲染到 c 指针上面
json.Unmarshal(str, c)
return nil
}
// Value 实现 driver.Value 接口,Value 返回 json value ;存数组数据用
func (a Args) Value() (driver.Value, error) {
// 判断数组的长度
if len(a) > 0 {
// 有数据,就进行数组的拼接
var str string = a[0]
for _, v := range a[1:] {
// 循环拼接 str 字符串
str += "," + v
}
return str, nil
} else {
// 没有数据就存 空
return "", nil
}
}
// Scan 实现 sql.Scanner 接口, 渲染出 value 的值;查 数组数据用
func (a *Args) Scan(value interface{}) error {
// 接收value的值,利用类型断言判断值是否为 字节类型
str, ok := value.([]byte)
if !ok {
return errors.New(fmt.Sprint("数据类型不匹配"))
}
// 断言成功,把结果渲染到 a 指针上面, 利用strings的字符串分割 str的值 返回原始的结果
*a = strings.Split(string(str), ",")
return nil
}
func CustomType() {
// 创建数据库
GLOBAL_DB.AutoMigrate(&CUser{})
// 添加数据
GLOBAL_DB.Create(&CUser{
Name: "王小二",
Info: CInfo{
Sex: "男",
Age: 20,
},
Address: Args{
"北京市",
"上海市",
"杭州市",
},
})
// 查询数据
var c CUser
GLOBAL_DB.First(&c)
fmt.Println(c)
}