目录
1.简介
特定:
全功能 ORM
关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
Create,Save,Update,Delete,Find 中钩子方法
支持 Preload、Joins 的预加载
事务,嵌套事务,Save Point,Rollback To Saved Point
Context、预编译模式、DryRun 模式
批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询
复合主键,索引,约束
数据迁移 实例化数据表数据
自定义 Logger
灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus
支持MySQL, PostgreSQL, SQLite, SQL Server 和 TiDB
2. 实例
# 包下载
go get -u gorm.io/gorm
# 按需下载驱动
go get -u gorm.io/driver/sqlite # sqlite
go get -u gorm.io/driver/mysql # mysql
go get -u gorm.io/driver/postgres # postgresql
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
type Product struct {
gorm.Model // GORM 定义一个 gorm.Model 结构体,其包括字段 ID、CreatedAt、UpdatedAt、DeletedAt
Code string
Price uint
}
func main() {
dsn := "username:password@tcp(host:port)/database?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true, // 表名非复数即不带s
//TablePrefix: "t_", // 表名前缀
},
})
if err != nil {
panic("数据库连接失败")
}
// 迁移 schema
db.AutoMigrate(&Product{})
// 新增
tx := db.Create(&Product{Code: "D42", Price: 100})
fmt.Printf("受影响行数=%v\n", tx.RowsAffected)
// 查询
var product Product
tx = db.First(&product, 1).Scan(&product) // 根据整型主键查找
fmt.Printf("受影响行数=%v;结果:%v\n", tx.RowsAffected, product)
// 更新 - 将 product 的 price 更新为 200
tx = db.Model(&product).Update("Price", 200)
fmt.Printf("受影响行数=%v\n", tx.RowsAffected)
//// 更新 - 更新多个字段
//db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段
//db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})
// 删除 - 删除 product(软删除-存在DeletedAt字段 每次删除只会更新DeletedAt时间 不过常规方式查询不会输出已标记删除的数据)
tx = db.Delete(&product, 1)
fmt.Printf("受影响行数=%v\n", tx.RowsAffected)
}
3.增删改查
新增
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
"time"
)
type MyUser struct {
ID uint64 `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
Name string `gorm:"column:name;type:string;size:8;not null;default:'';comment:姓名"`
Age uint8 `gorm:"column:age;not null;default:0;comment:年龄"`
CreateTime time.Time `gorm:"column:create_time;type:timestamp;not null;default:current_timestamp;comment:创建时间"`
UpdateTime time.Time `gorm:"column:update_time;type:timestamp;not null;default:current_timestamp on update current_timestamp;comment:更新时间"`
}
/**
CREATE TABLE `my_user` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(8) NOT NULL DEFAULT '' COMMENT '姓名',
`age` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '年龄',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/
// BeforeCreate钩子
func (u *MyUser) BeforeCreate(tx *gorm.DB) (err error) {
fmt.Printf("BeforeCreate...")
return
}
func main() {
dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true, // 表名非复数即不带s
//TablePrefix: "t_", // 表名前缀
NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
//NameReplacer: strings.NewReplacer("ID", "id"), // 替换数据表字段ID为id
},
NowFunc: func() time.Time {
tmp := time.Now().Local().Format("2006-01-02 15:04:05")
now, _ := time.ParseInLocation("2006-01-02 15:04:05", tmp, time.Local)
return now
}, // 字段存在时间的精确到秒
})
if err != nil {
panic("数据库连接失败")
}
// 迁移 schema
err = db.AutoMigrate(&MyUser{})
if err != nil {
fmt.Println("数据表迁移异常:" + err.Error())
return
}
database := db.Migrator().CurrentDatabase()
hasTableFlag := db.Migrator().HasTable(&MyUser{})
tables, _ := db.Migrator().GetTables()
fmt.Printf("数据表迁移成功;db:%v;hasTable:%v;tables:%v\n", database, hasTableFlag, tables)
// 新增-单个
u0 := MyUser{
Name: "王一",
Age: 10,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
result := db.Create(&u0) // 需要传递指针对象
fmt.Printf("插入数据的主键:%v;插入的数据量:%v;异常:%v\n", u0.ID, result.RowsAffected, result.Error)
// 新增-特定字段
u1 := MyUser{
Name: "李二",
Age: 11,
}
result = db.Select("Name", "Age").Create(&u1)
fmt.Printf("插入数据的主键:%v;插入的数据量:%v;异常:%v\n", u1.ID, result.RowsAffected, result.Error)
// 新增-动态指定字段(Omit声明的字段无论结构体的字段是否已赋值均忽略)
u2 := MyUser{
Name: "张三",
}
result = db.Omit("Age", "CreateTime", "UpdateTime").Create(&u2)
fmt.Printf("插入数据的主键:%v;插入的数据量:%v;异常:%v\n", u2.ID, result.RowsAffected, result.Error)
// 新增-批量-Create
us := []MyUser{
{Name: "p1"},
{Name: "p2"},
{Name: "p3"},
}
result = db.Create(&us)
ids := [3]uint64{}
for i, u := range us {
ids[i] = u.ID
}
fmt.Printf("批量插入数据的主键:%v;插入的数据量:%v;异常:%v\n", ids, result.RowsAffected, result.Error)
// 新增-批量-CreateInBatches
us2 := []MyUser{
{Name: "pp1"},
{Name: "pp2"},
{Name: "pp3"},
}
result = db.CreateInBatches(us2, len(us2))
ids = [3]uint64{}
for i, u := range us2 {
ids[i] = u.ID
}
fmt.Printf("批量插入数据的主键:%v;插入的数据量:%v;异常:%v\n", ids, result.RowsAffected, result.Error)
// 新增-单个或批量-map数据形式
result = db.Model(&MyUser{}).Create(map[string]interface{}{
"Name": "m1", "Age": 12,
}) // 单个
fmt.Printf("map-插入的数据量:%v;异常:%v\n", result.RowsAffected, result.Error)
result = db.Model(&MyUser{}).Create([]map[string]interface{}{
{"Name": "m2", "Age": 12},
{"Name": "m3", "Age": 12},
}) // 多个
fmt.Printf("map-插入的数据量:%v;异常:%v\n", result.RowsAffected, result.Error)
/**
支持在创建链接时配置批量处理的数据量且支持关联表的创建
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)
*/
// Insert ... ON DUPLICATE KEY UPDATE 有则更新、没有则新增(默认根据主键id或唯一键)
/**
type OnConflict struct {
Columns []Column // 相关字段参与判断 是新增还是更新
Where Where
TargetWhere Where
OnConstraint string
DoNothing bool
DoUpdates Set // 特定字段参与更新clause.AssignmentColumns([]string{"name", "age"})
UpdateAll bool // 所有字段参与更新
}
*/
us3 := []MyUser{
{Name: "m1_", Age: 10, ID: 10},
{Name: "m2_", Age: 11, ID: 11},
{Name: "m4", Age: 13},
}
result = db.Clauses(clause.OnConflict{
Columns: []clause.Column{
{Name: "id"},
},
DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
}).Create(&us3)
fmt.Printf("clause.OnConflict-受影响的数据量:%v;异常:%v\n", result.RowsAffected, result.Error)
// hook钩子
/**
BeforeSave, BeforeCreate, AfterSave, AfterCreate
可在事务操作前后过程中对数据预处理
*/
gu := MyUser{
Name: "g1",
}
result = db.Session(&gorm.Session{SkipHooks: false}).Create(&gu)
fmt.Printf("hook-新增主键id:%v;受影响行数:%v;异常:%v\n", gu.ID, result.RowsAffected, result.Error)
gus := []MyUser{
{Name: "g2"},
{Name: "g3"},
}
result = db.Session(&gorm.Session{SkipHooks: true}).CreateInBatches(gus, len(gus)) // SkipHooks跳过hooks方法
fmt.Printf("hook-受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
}
更新
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"time"
)
type myUser struct {
ID uint64 `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
Name string `gorm:"column:name;type:string;size:8;not null;default:'';comment:姓名"`
Age uint8 `gorm:"column:age;not null;default:0;comment:年龄"`
CreateTime time.Time `gorm:"column:create_time;type:timestamp;not null;default:current_timestamp;comment:创建时间"`
UpdateTime time.Time `gorm:"column:update_time;type:timestamp;not null;default:current_timestamp on update current_timestamp;comment:更新时间"`
}
/**
CREATE TABLE `my_user` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(8) NOT NULL DEFAULT '' COMMENT '姓名',
`age` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '年龄',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/
func main() {
dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true, // 表名非复数即不带s
//TablePrefix: "t_", // 表名前缀
NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
//NameReplacer: strings.NewReplacer("ID", "id"), // 替换数据表字段ID为id
},
NowFunc: func() time.Time {
tmp := time.Now().Local().Format("2006-01-02 15:04:05")
now, _ := time.ParseInLocation("2006-01-02 15:04:05", tmp, time.Local)
return now
}, // 字段存在时间的精确到秒
})
if err != nil {
panic("数据库连接失败")
}
// 迁移 schema
err = db.AutoMigrate(&myUser{})
if err != nil {
fmt.Println("数据表迁移异常:" + err.Error())
return
}
database := db.Migrator().CurrentDatabase()
hasTableFlag := db.Migrator().HasTable(&myUser{})
tables, _ := db.Migrator().GetTables()
fmt.Printf("数据表迁移成功;db:%v;hasTable:%v;tables:%v\n", database, hasTableFlag, tables)
// 修改-默认查询条件以ID主键
//1.修改-Save(尽量避免使用)
//save方式只有在传递ID主键且ID主键数据存在才是更新数据
//save不传ID则为新增数据、传递了ID但不存在依旧为新增数据
result := db.Save(&myUser{Name: "小红", Age: 23, CreateTime: time.Now(), UpdateTime: time.Now(), ID: 20})
fmt.Printf("save-修改影响的行数:%v;异常:%v\n", result.RowsAffected, result.Error)
// 2.1修改-Update单列
u0 := myUser{ID: 100}
result = db.Model(&u0).Update("name", "小红2") // 针对结构体对象明确的 默认以主键id为查询条件
fmt.Printf("update单列-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
result = db.Model(&myUser{}).Where("id=?", 20).Update("name", "小红-21") // 针对结构体对象不明确的 需要携带Where查询条件
fmt.Printf("update单列-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
// 2.2 修改-Update多列
u1 := myUser{ID: 21}
result = db.Model(&u1).Updates(myUser{Name: "小红-22", Age: 22})
fmt.Printf("update多列(struct)-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
result = db.Model(&u1).Updates(map[string]interface{}{
"name": "小红22",
"age": 22,
})
fmt.Printf("update多列(map)-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
result = db.Model(&myUser{}).Where("id=?", 21).Updates(myUser{Age: 23})
fmt.Printf("update多列-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
// 2.3 修改-动态字段数量Select&Omit
// Select指定参与更新的字段、Omit指定不参与更新的字段、2者可搭配一起使用
result = db.Model(&myUser{}).Select("name").Where("id=?", 22).Updates(map[string]interface{}{
"name": "小红22",
"age": 25,
}) // 等价于单列 只更新name即select指定的字段
fmt.Printf("update-select(单列)-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
u2 := myUser{
ID: 22,
}
result = db.Model(&u2).Omit("age", "create_time", "update_time").Updates(map[string]interface{}{
"name": "小红_22",
"age": 26,
}) // omit指定的字段不参与更新
fmt.Printf("update-omit(单列)-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
result = db.Model(&u2).Select("name", "age").Updates(myUser{Name: "小红-22", Age: 22}) // 等价于多列参与更新
fmt.Printf("update-select(多列)-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
result = db.Model(&u2).Select("*").Updates(myUser{
Name: "小红-222", Age: 222, CreateTime: time.Now(), UpdateTime: time.Now(), ID: u2.ID,
}) // 所有字段参与更新 字段非空需要赋值 ID也需要赋值(后续可以过滤处理)
fmt.Printf("update-select(所有列)-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
result = db.Model(&u2).Select("*").Omit("update_time", "create_time", "id").Updates(
myUser{Name: "小红-22", Age: 22})
fmt.Printf("update-select&omit-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
// 2.4 修改-批量更新
result = db.Model(myUser{}).Where("id in ?", []int{19, 20, 21, 22}).Updates(myUser{
Name: "xh",
Age: 18,
})
fmt.Printf("update-批量Model-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
result = db.Table("my_user").Where("id in ?", []int{19, 20, 21, 22}).Updates(map[string]interface{}{
"name": "xh1",
"age": 19,
})
fmt.Printf("update-批量Table-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
// 获取修改后的数据
var u myUser
result = db.Model(myUser{}).Where("id=?", 10).Updates(myUser{Name: "m1", Age: 11}).Scan(&u)
fmt.Printf("%v-%v-%v\n", result.RowsAffected, result.Error, u)
// 其它高级用法
/**
// 1. 嵌套sql表达式
// product's ID is `3`
db.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
// UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;
db.Model(&product).Updates(map[string]interface{}{"price": gorm.Expr("price * ? + ?", 2, 100)})
// UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;
db.Model(&product).UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3;
db.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3 AND quantity > 1;
// 2. 子查询
db.Model(&user).Update("company_name", db.Model(&Company{}).Select("name").Where("companies.id = users.company_id"))
// UPDATE "users" SET "company_name" = (SELECT name FROM companies WHERE companies.id = users.company_id);
db.Table("users as u").Where("name = ?", "jinzhu").Update("company_name", db.Table("companies as c").Select("name").Where("c.id = u.company_id"))
db.Table("users as u").Where("name = ?", "jinzhu").Updates(map[string]interface{}{"company_name": db.Table("companies as c").Select("name").Where("c.id = u.company_id")})
// 3. 全局更新相关
db.Model(&User{}).Update("name", "jinzhu").Error // gorm.ErrMissingWhereClause
db.Model(&User{}).Where("1 = 1").Update("name", "jinzhu")
// UPDATE users SET `name` = "jinzhu" WHERE 1=1
db.Exec("UPDATE users SET name = ?", "jinzhu")
// UPDATE users SET name = "jinzhu"
db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "jinzhu")
// UPDATE users SET `name` = "jinzhu"
// 4. hook钩子函数-BeforeSave, BeforeUpdate, AfterSave, AfterUpdate
通过实现对应的结构体的方法 可对在修改前后的数据做预处理
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {return}
*/
}
删除
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
"time"
)
//type myUser struct {
// ID uint64 `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
// Name string `gorm:"column:name;type:string;size:8;not null;default:'';comment:姓名"`
// Age uint8 `gorm:"column:age;not null;default:0;comment:年龄"`
// CreateTime time.Time `gorm:"column:create_time;type:timestamp;not null;default:current_timestamp;comment:创建时间"`
// UpdateTime time.Time `gorm:"column:update_time;type:timestamp;not null;default:current_timestamp on update current_timestamp;comment:更新时间"`
//}
/**
CREATE TABLE `my_user` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(8) NOT NULL DEFAULT '' COMMENT '姓名',
`age` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '年龄',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/
func main() {
dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true, // 表名非复数即不带s
//TablePrefix: "t_", // 表名前缀
NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
//NameReplacer: strings.NewReplacer("ID", "id"), // 替换数据表字段ID为id
},
NowFunc: func() time.Time {
tmp := time.Now().Local().Format("2006-01-02 15:04:05")
now, _ := time.ParseInLocation("2006-01-02 15:04:05", tmp, time.Local)
return now
}, // 字段存在时间的精确到秒
})
if err != nil {
panic("数据库连接失败")
}
// 迁移 schema
err = db.AutoMigrate(&myUser{})
if err != nil {
fmt.Println("数据表迁移异常:" + err.Error())
return
}
database := db.Migrator().CurrentDatabase()
hasTableFlag := db.Migrator().HasTable(&myUser{})
tables, _ := db.Migrator().GetTables()
fmt.Printf("数据表迁移成功;db:%v;hasTable:%v;tables:%v\n", database, hasTableFlag, tables)
// 1.1 删除-单条记录
result := db.Delete(&myUser{}, 0)
fmt.Printf("删除指定对象(指定主键)-受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
// 1.2 删除-指定主键
result = db.Delete(&myUser{}, 1) // delete from xx where id=10;
fmt.Printf("删除(指定主键)-受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
result = db.Delete(&myUser{}, []int{1, 2, 3}) // delete from xx where id in (1,2,3);
fmt.Printf("删除(指定多个主键)-受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
// 1.3 删除-批量(根据条件)
result = db.Where("name=?", "p1").Delete(&myUser{}) // name='p1'
fmt.Printf("删除(批量)-受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
result = db.Delete(&myUser{}, "name=?", "p1") // 等价于上面的db.Where("name=?", "p1").Delete(&myUser{})
fmt.Printf("删除(批量)-受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
us := []myUser{
{ID: 4},
{ID: 5},
{ID: 6},
}
result = db.Delete(&us) // 等价于 db.Delete(&us, "id in ?", []int{4,5,6})
fmt.Printf("删除(批量-指定对象)-受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
// 1.4 返回删除行的数据(回显被删除的数据)-需要数据库支持回写功能-待验证
var us2 []myUser
result = db.Clauses(clause.Returning{}).Where("name=?", "xh1").Delete(&us2)
fmt.Printf("删除(回显删除数据)-受影响行数:%v;删除数据:%v;异常:%v\n", result.RowsAffected, us2, result.Error)
// 回显指定列名
//result = db.Clauses(clause.Returning{Columns: []clause.Column{{Name: "name"}, {Name: "age"}}}).Where("name=?", "xh1").Delete(&us2)
var uu myUser
result = db.Clauses(clause.Returning{}).Where("name=?", "pp2").Delete(&uu)
fmt.Printf("删除(回显删除数据)-受影响行数:%v;删除数据:%v;异常:%v\n", result.RowsAffected, uu, result.Error)
// 删除(回显删除数据)-受影响行数:1;删除数据:{0 0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC};异常:<nil>
/**
1. 阻止全局删除
当执行不带任何条件的批量删除时,GORM将不会运行并返回ErrMissingWhereClause 错误
如果一定要这么做,必须添加一些条件Where,或者使用原生SQL,或者开启AllowGlobalUpdate 模式,如下例:
db.Delete(&User{}).Error // gorm.ErrMissingWhereClause
db.Delete(&[]User{{Name: "jinzhu1"}, {Name: "jinzhu2"}}).Error // gorm.ErrMissingWhereClause
db.Where("1 = 1").Delete(&User{}) // DELETE FROM `users` WHERE 1=1
db.Exec("DELETE FROM users") // DELETE FROM users
db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{}) // DELETE FROM users
//2. 软删除(结构体字段包含 gorm.DeletedAt 存在该字段则调用删除不会进行物理删除 而是更新该字段的时间)
// 2.1 根据id主键删除 user's ID is `111`
db.Delete(&user) // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;
// 2.2 批量删除
db.Where("age = ?", 20).Delete(&User{}) // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
// 2.3.1 软删除-数据查询
db.Where("age = 20").Find(&user) // 软删除的记录将被忽略 这种方式不会有输出 SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
db.Unscoped().Where("age = 20").Find(&users) // 这种方式有输出 SELECT * FROM users WHERE age = 20;
// 2.3.2 软删除-永久删除形式
db.Unscoped().Delete(&order) // DELETE FROM orders WHERE id=10;
// 3. 删除操作相关的钩子函数-BeforeDelete、AfterDelete 当调用Delete触发该钩子函数
func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
if u.Role == "admin" {
return errors.New("admin user not allowed to delete")
}
return
}
*/
}
查询
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"time"
)
//type myUser struct {
// ID uint64 `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
// Name string `gorm:"column:name;type:string;size:8;not null;default:'';comment:姓名"`
// Age uint8 `gorm:"column:age;not null;default:0;comment:年龄"`
// CreateTime time.Time `gorm:"column:create_time;type:timestamp;not null;default:current_timestamp;comment:创建时间"`
// UpdateTime time.Time `gorm:"column:update_time;type:timestamp;not null;default:current_timestamp on update current_timestamp;comment:更新时间"`
//}
/**
CREATE TABLE `my_user` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(8) NOT NULL DEFAULT '' COMMENT '姓名',
`age` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '年龄',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/
func main() {
dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true, // 表名非复数即不带s
//TablePrefix: "t_", // 表名前缀
NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
//NameReplacer: strings.NewReplacer("ID", "id"), // 替换数据表字段ID为id
},
NowFunc: func() time.Time {
tmp := time.Now().Local().Format("2006-01-02 15:04:05")
now, _ := time.ParseInLocation("2006-01-02 15:04:05", tmp, time.Local)
return now
}, // 字段存在时间的精确到秒
})
if err != nil {
panic("数据库连接失败")
}
// 迁移 schema
err = db.AutoMigrate(&myUser{})
if err != nil {
fmt.Println("数据表迁移异常:" + err.Error())
return
}
database := db.Migrator().CurrentDatabase()
hasTableFlag := db.Migrator().HasTable(&myUser{})
tables, _ := db.Migrator().GetTables()
fmt.Printf("数据表迁移成功;db:%v;hasTable:%v;tables:%v\n", database, hasTableFlag, tables)
// 1. 查询-单个对象(First|Take|Last)
// First|Take|Last 查询未有记录会抛出异常 建议使用db.Find()形式
// 相关Model没有定义主键 会按照Model(myUser)的第一个字段排序
var u0 myUser
result := db.First(&u0) // select * from my_user order by id limit 1; 需要保证Model有ID主键字段
fmt.Printf("查询-单个对象First-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u0, result.Error)
var u1 myUser
result = db.Take(&u1) // select * from my_user limit 1;
fmt.Printf("查询-单个对象Take-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u1, result.Error)
var u2 myUser // 需要指定不同的变量(*)
result = db.Last(&u2) // select * from my_user order by id desc limit 1;
fmt.Printf("查询-单个对象Last-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u2, result.Error)
u3 := map[string]interface{}{}
result = db.Model(&myUser{}).First(&u3)
fmt.Printf("查询-单个对象Map-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u3, result.Error)
var u4 myUser
//result = db.Find(&u4, 9) // select * from my_user where id=9 <=>db.Find(&u4, "id=?", 9)
result = db.Find(&u4, "id=?", 9)
fmt.Printf("查询-单个对象Find-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u4, result.Error)
// 2. 查询-获取所有对象
var us []myUser
result = db.Find(&us)
fmt.Printf("查询-多个对象Find-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, len(us), result.Error)
// 3.1 条件查询-string
// var user = User{ID: 10} (❌) 不建议使用这种方式
//db.Where("id = ?", 20).First(&user) // SELECT * FROM users WHERE id = 10 and id = 20 ORDER BY id ASC LIMIT 1
var u5 myUser
result = db.Where("name=?", "g3").Find(&u5)
//result = db.Where("name=? and id > ?", "g3", 1).First(&u5) // 可多个条件 参考sql条件查询
fmt.Printf("查询-Where-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u5, result.Error)
// 3.2 条件查询-struct&map
var u6 myUser
/**
1.下面的结构体方式查询 若字段值为0|''|false|或者其它标识0值的都不会参与查询 可使用map方式携带0值字段 或者指定结构体查询的字段
db.Where(&myUser{Name: "g3"}).Find(&u6)
2. 通过这种方式指定结构体查询的字段
db.Where(&myUser{Name:"g3"}, "name", "Age").Find(&u6) <=>select * from my_user where name='g3' and age=0;
db.Where(&myUser{Name:"g3"}, "Age").Find(&u6) <=>select * from my_user where age=0;
*/
result = db.Where(&myUser{Name: "g3"}).Find(&u6)
fmt.Printf("查询-Where(struct)-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u6, result.Error)
var u7 myUser
result = db.Where(map[string]interface{}{"name": "g3"}).First(&u7)
fmt.Printf("查询-Where(map)-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u7, result.Error)
// 3.3 条件查询-内联条件
var u8 myUser
/**
db.Find(&u8, myUser{Name: "g3"}) <=> db.Find(&u8, map[string]interface{}{"name": "g3"}) <=> db.Find(&u8, "name=?", "g3")
*/
result = db.Find(&u8, myUser{Name: "g3"}) // select * from my_user where name='g3';
fmt.Printf("查询-Find内联条件-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u8, result.Error)
// 3.4 条件查询-not、or
var uu myUser
/**
db.Not("name=?", "g3").First(&uu)
<=>
db.Not(map[string]interface{}{"name": "g3"}).First(&uu)
<=>
db.Not(myUser{Name: "g3"}).First(&uu)
ps:
db.Not(myUser{Name: "g3", Age: 10}).First(&uu)=>select * from my_user where name != g3 and age !=10;
*/
result = db.Not("name=?", "g3").First(&uu)
fmt.Printf("查询-Not-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, uu, result.Error)
var uus []myUser
/**
db.Where("name=?", "g3").Or("name=?", "g2").Find(&uus)
<=>
db.Where("name=?", "g3").Or(myUser{Name: "g2"}).Find(&uus)
<=>
db.Where("name='g3'").Or(map[string]interface{}{"name":"g2"}).Find(&uus)
*/
result = db.Where("name=?", "g3").Or("name=?", "g2").Find(&uus)
fmt.Printf("查询-OR-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, uus, result.Error)
// 3.5 查询-输出特定字段Select
var u9 myUser
/**
只有指定的特定字段有实际的值 其它字段均为0标识值int=0 bool=false time.time=0001-01-01 00:00:00 +0000 UTC
db.Select("name", "age").Find(&u9, myUser{Name: "g3"})<=>db.Select([]string{"name", "age"}).Find(&u9, myUser{Name: "g3"})
*/
result = db.Select("name", "age").Find(&u9, myUser{Name: "g3"})
//result = db.Select([]string{"name", "age"}).Find(&u9, myUser{Name: "g3"})
fmt.Printf("查询-Select-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u9, result.Error)
// 3.6 查询-排序Order
var up myUser
result = db.Order("age desc, name").Find(&up).Limit(1) // <=> db.Order("age desc").Order("name").Find(&up).Limit(1)
fmt.Printf("查询-Order-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, up, result.Error)
// 3.7 查询-分页Limit&Offset
// offset:偏移量 limit: 输出量
var up1 myUser
result = db.Limit(1).Offset(1).Find(&up1)
fmt.Printf("查询-Limit&Offset-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, up1, result.Error)
// 3.8 查询-分组group by + having
var ug1 myUser
// select name, sum(age) as total from my_user group by name having name='g3';
result = db.Model(&myUser{}).Select("name, sum(age) as total").Group("name").Having("name=?", "g3").Find(&ug1).Limit(1)
fmt.Printf("查询-Modle-groupby&having-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, ug1, result.Error)
var ug2 myUser
result = db.Table("my_user").Select("name, sum(age) as total").Group("name").Having("name=?", "g3").Limit(1).Scan(&ug2)
fmt.Printf("查询-Table-groupby&having-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, ug2, result.Error)
// 3.9 查询-原生sql
var su myUser
/**
db.Table("my_user").Select("name", "age").Where("name=?", "g3").Scan(&su)
<=>
db.Raw("select name, age from my_user where name=?", "g3").Scan(&su)
*/
result = db.Raw("select name, age from my_user where name=?", "g3").Scan(&su)
fmt.Printf("查询-Raw-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, su, result.Error)
/**
1. 关联查询JOIN
type result struct {
Name string
Email string
}
// SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id
db.Model(&User{}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{})
<=>
rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows()
for rows.Next() {
...
}
<=>
db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)
2. 多表关联查询
db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "[email protected]").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "1").Find(&user)
3. 左连接
// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id`;
db.Joins("Company").Find(&users) // type Company struct{}
4. 内连接
// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` INNER JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id`;
db.InnerJoins("Company").Find(&users) // type Company struct{}
5. 连接查询附带条件
// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id` AND `Company`.`alive` = true;
db.Joins("Company", db.Where(&Company{Alive: true})).Find(&users)
*/
}
高级查询
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
"time"
)
//type myUser struct {
// ID uint64 `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
// Name string `gorm:"column:name;type:string;size:8;not null;default:'';comment:姓名"`
// Age uint8 `gorm:"column:age;not null;default:0;comment:年龄"`
// CreateTime time.Time `gorm:"column:create_time;type:timestamp;not null;default:current_timestamp;comment:创建时间"`
// UpdateTime time.Time `gorm:"column:update_time;type:timestamp;not null;default:current_timestamp on update current_timestamp;comment:更新时间"`
//}
/**
CREATE TABLE `my_user` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(8) NOT NULL DEFAULT '' COMMENT '姓名',
`age` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '年龄',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/
func main() {
dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true, // 表名非复数即不带s
//TablePrefix: "t_", // 表名前缀
NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
//NameReplacer: strings.NewReplacer("ID", "id"), // 替换数据表字段ID为id
},
NowFunc: func() time.Time {
tmp := time.Now().Local().Format("2006-01-02 15:04:05")
now, _ := time.ParseInLocation("2006-01-02 15:04:05", tmp, time.Local)
return now
}, // 字段存在时间的精确到秒
})
if err != nil {
panic("数据库连接失败")
}
// 迁移 schema
err = db.AutoMigrate(&myUser{})
if err != nil {
fmt.Println("数据表迁移异常:" + err.Error())
return
}
database := db.Migrator().CurrentDatabase()
hasTableFlag := db.Migrator().HasTable(&myUser{})
tables, _ := db.Migrator().GetTables()
fmt.Printf("数据表迁移成功;db:%v;hasTable:%v;tables:%v\n", database, hasTableFlag, tables)
// 1. 查询-智能选择字段(通过定义结构体控制查询字段)
/**
&gorm.Config{
QueryFields: true, // gorm配置 实际会对所有字段进行select
}
var user myUser
db.Session(&gorm.Session{QueryFields: true}).Find(&user)
<=>
db.Find(&user)
*/
type ApiUser struct {
ID uint
Name string
}
var u myUser
result := db.Model(&myUser{}).Limit(1).Find(&ApiUser{}).Scan(&u)
fmt.Printf("查询-结构体控制字段输出-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, u, result.Error)
// 2. 查询-锁
/**
gorm支持多种类型锁
s锁:共享锁=>加锁的记录 允许其他事务加s锁 不允许加x锁 =>select ... lock in share mode
x锁:排他锁=>加锁的记录 不允许其他事务加s锁或x锁 =>select ... from update
is锁:意向共享锁=>事务在请求s锁前 需要先获取is锁
ix锁:意向排他锁=>事务在请求x锁前 需要先获取ix锁
兼容性:
x和所有锁冲突
ix兼容ix、is
s兼容s、is
is兼容is、ix、s
lock in share: 给表加is 给行加s 多个事务允许同时持有一行的读锁 其他事务可以读取 不可修改 需要等待持有锁的事务提交后才能进行写入操作
for update: 给表加ix 给行加x 事务锁定行|表后 会阻塞其他事务对行|表的读写操作
*/
var u0 myUser
result = db.Clauses(clause.Locking{
Strength: "update", // update or share
Table: clause.Table{Name: clause.CurrentTable}, // 数据表
Options: "", // 可选参数
}).Where("id=?", 18).Find(&u0)
fmt.Printf("查询-for update-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, u0, result.Error)
var u1 myUser
result = db.Clauses(clause.Locking{
Strength: "share",
Table: clause.Table{Name: clause.CurrentTable}}).Where("id=?", 18).Find(&u1)
fmt.Printf("查询-for share-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, u1, result.Error)
// 3. 查询-子查询
/**
子查询其他形式
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)
// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users")
db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results)
// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18
db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{})
// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
subQuery1 := db.Model(&User{}).Select("name")
subQuery2 := db.Model(&Pet{}).Select("name")
db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})
*/
var u2 myUser
// select * from (select name,age from my_user) as u where id=1;
// select 确定了 where可选的查询字段 where用其它字段会出现异常
result = db.Table("(?) as u", db.Model(&myUser{}).Select("id", "name")).Where("id=?", 18).Find(&u2)
fmt.Printf("查询-子查询-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, u2, result.Error)
// 4. 分组查询-条件
/**
// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge")
db.Where(
db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
).Or(
db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"),
).Find(&Pizza{}).Statement
*/
// 5. 带多个列的in查询
/**
// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user"));
db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users)
*/
// 6. 查询-命名参数
// gorm 支持sql.NamedArg和map[string]interface{}{}形式的命令参数
var u3 myUser
//result = db.Where("name=@n1", sql.Named("n1", "王一")).Find(&u3)
// <=>等价于下面的语句
result = db.Where("name=@n1", map[string]interface{}{"n1": "g3"}).Find(&u3)
fmt.Printf("查询-命令参数-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, u3, result.Error)
// 7. 查询结果-结构化(map)
r := map[string]interface{}{}
result = db.Model(&myUser{}).Find(&r)
fmt.Printf("查询-map结果结构化-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, r, result.Error)
rs := []map[string]interface{}{}
result = db.Model(&myUser{}).Limit(2).Find(&rs) // <=>db.Table("my_user").Limit(2).Find(&rs)
fmt.Printf("查询-map结果结构化-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, rs, result.Error)
// 8. 查询-返回结果的对象(struct|map)初始化 FirstOrInit
// 获取第一条匹配的记录 或者根据给定的条件初始化一个实例(仅支持 struct 和 map 条件)
// 使用Attrs对struct或map初始化属性 Attrs内部的字段不参与sql查询 查询到的对象会替换Attrs内部字段的值
// 使用Assign对struct或map初始化属性 Assign内部的字段不参与sql查询 无论是否查询到对象 Assign都会把字段值作为初始化值
var fu myUser
result = db.FirstOrInit(&fu, myUser{Name: "haha"}) // 未查询到数据则初始化一个实例 name=haha的myUser结构体
fmt.Printf("查询-firstOrInit-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, fu, result.Error)
var fu2 myUser
result = db.Where(myUser{Name: "王一"}).FirstOrInit(&fu2) // 找到数据则返回并初始化myUser
//<=>
//result = db.FirstOrInit(&fu2, map[string]interface{}{"name": "王一"})
fmt.Printf("查询-firstOrInit-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, fu2, result.Error)
var au myUser
result = db.FirstOrInit(&au, myUser{Name: "g3"}).Attrs("age", 20) // 查询到对象则Attrs-age字段值以对象值为准
//<=>等价于
//result = db.Where(myUser{Name: "g3"}).Attrs(myUser{Age: 20}).FirstOrInit(&au)
fmt.Printf("查询-Attrs-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, au, result.Error)
var au2 myUser
result = db.Where(myUser{Name: "g3"}).Assign(myUser{Age: 20}).FirstOrInit(&au2) // 无论是否查询到对象 Assign内部的字段的值都会赋给au2
fmt.Printf("查询-Assign-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, au2, result.Error)
// 9. 查询-FirstOrCreate(对返回对象-map|struct 初始化赋值)
// 获取匹配的第一条记录或者根据给定条件创建一条新纪录(仅 struct, map 条件有效) RowsAffected 返回创建、更新的记录数
// 若未找到记录 则会新建一条记录
// Attrs 和 Assign 修饰的字段不会参与sql查询 Attrs修饰的字段值只在未找到结果时生效 Assign修饰的字段值无论是否查询到都生效
var cu myUser
result = db.FirstOrCreate(&cu, myUser{Name: "g4"}) // 未找到则会新增 否则返回记录结果并赋值给cu
//<=>
//result = db.Where(myUser{Name: "g4"}).FirstOrCreate(&cu)
fmt.Printf("查询-FirstOrCreate-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, cu, result.Error)
// 10. hints使用
// 优化器、索引提示 注解提示
// go get -u gorm.io/hints
/**
var hus []myUser
result = db.Clauses(hints.New("max_execution_time(10)")).Find(&hus)
result = db.Clauses(hints.UseIndex("create_time")).Find(&hus) // create_time需要为索引名 可多个
result = db.Clauses(hints.ForceIndex("create_time", "update_time").ForJoin()).Find(hus)
fmt.Printf("查询-hints-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, hus, result.Error)
*/
//11. 查询-迭代返回查询对象(查询结果处理)
rows, _ := db.Model(&myUser{}).Where("age=?", 0).Rows()
defer rows.Close()
for rows.Next() {
var u myUser
db.ScanRows(rows, &u)
fmt.Printf("rows查询的对象:%v\n", u)
}
// 12. 查询-批量处理(batchSize=?个)查询结果
var myBatch []myUser
result = db.Where("age=?", 0).FindInBatches(&myBatch, 2, func(tx *gorm.DB, batch int) error {
for _, r := range myBatch {
// 批量处理
fmt.Printf("FindInBatches-对象:%v\n", r)
}
//tx.Save(&myBatch) // 批量数据入库修改|新增或其它操作
//affected := tx.RowsAffected // 输出受影响的记录行数
fmt.Printf("myBatch=%v\n", myBatch)
return nil
})
// 13. 查询钩子函数 AfterFind
/**
func (u *myUser) AfterFind(tx *gorm.DB)(err error){return}
*/
// 14. 查询-输出单列-pluck
// 超过单列字段的输出需要使用Select搭配Scan或Find
//db.Select("name", "age").Scan(&p)
//db.Select("name", "age").Find(&p)
var p myUser
result = db.Model(&myUser{}).Where("id=?", 18).Pluck("name", &p)
fmt.Printf("查询-pluck-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, p, result.Error)
// 15. 查询-Scopes 通过定义常用查询方法 再使用Scopes引用该方法 该方法可以多个
var myF myUser
result = db.Scopes(func(db *gorm.DB) *gorm.DB {
return db.Where("id=?", 18)
}).Find(&myF)
fmt.Printf("查询-Scopes-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, myF, result.Error)
// 16. 查询-统计计数
var count int64
result = db.Model(&myUser{}).Where("id > ?", 1).Or("age > ?", 0).Count(&count)
fmt.Printf("查询-Count-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, count, result.Error)
}
原生sql
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"time"
)
//type myUser struct {
// ID uint64 `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
// Name string `gorm:"column:name;type:string;size:8;not null;default:'';comment:姓名"`
// Age uint8 `gorm:"column:age;not null;default:0;comment:年龄"`
// CreateTime time.Time `gorm:"column:create_time;type:timestamp;not null;default:current_timestamp;comment:创建时间"`
// UpdateTime time.Time `gorm:"column:update_time;type:timestamp;not null;default:current_timestamp on update current_timestamp;comment:更新时间"`
//}
/**
CREATE TABLE `my_user` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(8) NOT NULL DEFAULT '' COMMENT '姓名',
`age` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '年龄',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/
func main() {
dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true, // 表名非复数即不带s
//TablePrefix: "t_", // 表名前缀
NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
//NameReplacer: strings.NewReplacer("ID", "id"), // 替换数据表字段ID为id
},
NowFunc: func() time.Time {
tmp := time.Now().Local().Format("2006-01-02 15:04:05")
now, _ := time.ParseInLocation("2006-01-02 15:04:05", tmp, time.Local)
return now
}, // 字段存在时间的精确到秒
})
if err != nil {
panic("数据库连接失败")
}
// 迁移 schema
err = db.AutoMigrate(&myUser{})
if err != nil {
fmt.Println("数据表迁移异常:" + err.Error())
return
}
database := db.Migrator().CurrentDatabase()
hasTableFlag := db.Migrator().HasTable(&myUser{})
tables, _ := db.Migrator().GetTables()
fmt.Printf("数据表迁移成功;db:%v;hasTable:%v;tables:%v\n", database, hasTableFlag, tables)
// 1. gorm支持使用原生sql
// Raw方式执行原生sql-可返回对象Scan
// Exec方式执行原生sql gorm.Expr扩展sql表达式 (select语句不生效) 无返回对象
var u myUser
tx := db.Raw("select id, name, age from my_user where id=?", 18).Scan(&u)
fmt.Printf("原生sql-Raw-受影响行数:%v;结果:%v;异常:%v\n", tx.RowsAffected, u, tx.Error)
tx = db.Exec("update my_user set age=? where id=?", gorm.Expr("age * ? + ?", 1, 1), 18)
fmt.Printf("原生sql-Exec-受影响行数:%v;异常:%v\n", tx.RowsAffected, tx.Error)
// 2.1 调试模式-DryRun
// DryRun模式-生成sql但不执行
// 可获取sql语句和对应的参数 会根据不同的数据库驱动生成不同的sql语句
stmt := db.Session(&gorm.Session{DryRun: true}).First(&myUser{}, 1).Statement
fmt.Printf("DryRun-sql语句:%v;参数:%v\n", stmt.SQL.String(), stmt.Vars)
// 2.2 调试模式-ToSQL
// ToSQL模式-返回生成的sql但不执行
sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
return tx.Where("id=?", 1).First(&myUser{})
})
fmt.Printf("ToSQL-sql语句:%v\n", sql)
// 3. 获取结果
// 3.1 获取*sql.Row结果
var name string
var age uint
//row := db.Table("my_user").Where("name=?", "g2").Select("name, age").Row()
// <=>等价于
row := db.Raw("select name,age from my_user where name=?", "g2").Row()
row.Scan(&name, &age)
fmt.Printf("Row-name=%v;age=%v;model=%v\n", name, age)
// 3.2 获取*sql.Rows结果
//rows, err := db.Model(&myUser{}).Where("id between ? and ?", 17, 18).Select("name", "age").Rows()
// <=>等价于
rows, err := db.Raw("select name, age from my_user where id between ? and ?", 17, 18).Rows()
defer rows.Close()
var us myUser
for rows.Next() {
rows.Scan(&name, &age) // 获取特定字段值
db.ScanRows(rows, &us) // 初始化赋值到model
fmt.Printf("Rows-name=%v;age=%v;model=%v\n", name, age, us)
}
// 3.3 连接(非事务)
// 在一条tcp DB连接中运行多条sql
db.Connection(func(tx *gorm.DB) error {
var d1 myUser
tx.Model(&myUser{}).Where("id >= ?", 16).Scan(&d1)
fmt.Printf("scan=%v\n", d1) // tx的结果已缓存
var d2 myUser
tx.Last(&d2) // 获取上次tx结果的最后一个结果
fmt.Printf("scan=%v\n", d2)
return nil
})
}
4.模型类
约定
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"time"
)
// 2.2 表名-实现Tabler接口来更改默认表名
type MyUU struct {
gorm.Model
}
func (MyUU) TableName() string {
return "myuu"
}
func main() {
/**
GORM倾向于约定优于配置
默认情况下GORM 使用 ID 作为主键
使用结构体名的蛇形复数作为表名
字段名的蛇形作为列名 并使用 CreatedAt、UpdatedAt 字段追踪创建、更新时间
如果遵循GORM的约定 您就可以少写的配置、代码
如果约定不符合您的实际要求 GORM允许你配置它们
*/
dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
//SingularTable: true, // 表名非复数即不带s
//TablePrefix: "t_", // 表名前缀
//NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
//NameReplacer: strings.NewReplacer("ID", "id"), // 替换数据表字段ID为id
},
})
if err != nil {
panic("数据库连接失败")
}
// 1.1 主键-默认约定ID作为主键
type MyUser struct {
ID uint64 // 默认情况下 即便未声明primaryKey标签 约定ID作为主键
Name string
}
// 1.2 主键-自定义主键
type MyUser2 struct {
MyID uint64 `gorm:"primaryKey"` // 自定义相关字段为主键 若不需要整型自增情况 需要声明标签autoIncrement:false
MyName string
}
// 1.3 主键-复合主键(声明多个字段为主键)
type MyUser3 struct {
PA string `gorm:"primaryKey"`
PB string `gorm:"primaryKey"`
PName string
}
// 2.表名-结构体名
//type MyUU struct {
// gorm.Model
//}
// 2.1 表名-默认为复数形式 MyUser4=>my_user4 | MyUU=>my_uus
err = db.AutoMigrate(&MyUU{})
if err != nil {
fmt.Println("数据表迁移异常:" + err.Error())
return
}
database := db.Migrator().CurrentDatabase()
tables, _ := db.Migrator().GetTables()
fmt.Printf("数据表迁移成功;db:%v;tables:%v\n", database, tables)
//2.2 表名-实现Tabler接口来更改默认表名
//type MyUU struct {
// gorm.Model
//}
//
//func (MyUU) TableName() string {
// return "myuu" // 指定MyUU-结构体并返回指定表名
//}
// 2.3 表名-指定临时表名(Table指定表名)
// TableName 不支持动态变化 它会被缓存下来以待后续使用 若想使用动态表名 需使用Scopes
db.Table("myuuu").AutoMigrate(&MyUU{})
tables, _ = db.Migrator().GetTables()
fmt.Printf("数据表迁移成功;db:%v;tables:%v\n", database, tables)
// 2.4 表名-通过指定Gorm配置-NamingStrategy配置项 &gorm.Config{NamingStrategy: schema.NamingStrategy{SingularTable: true, // 表名非复数即不带s}}
// 3. 列名
// 3.1 默认约定的命名规范
type MyUser5 struct {
ID uint64 // id
Name string // name
MyBirth time.Time // my_birth
}
// 3.2 自定义列名
type MyUser6 struct {
ID uint64 `gorm:"column:my_id"`
Name string `gorm:"column:my_name"`
MyBirth time.Time `gorm:"column:my_birth"`
}
}
字段标签
标签名(大小写不敏感 默认小驼峰) | 说明 |
---|---|
column | 指定 db 列名 |
type | 列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not null 、size , autoIncrement … 像 varbinary(8) 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT |
serializer | 指定将数据序列化或反序列化到数据库中的序列化器, 例如: serializer:json/gob/unixtime |
size | 定义列数据类型的大小或长度,例如 size: 256 |
primaryKey | 将列定义为主键 |
unique | 将列定义为唯一键 |
default | 定义列的默认值 |
precision | 指定列的精度 |
scale | 指定列大小 |
not null | 指定列为 NOT NULL |
autoIncrement | 指定列为自动增长 |
autoIncrementIncrement | 自动步长,控制连续记录之间的间隔 |
embedded | 嵌套字段 |
embeddedPrefix | 嵌入字段的列名前缀 |
autoCreateTime | 创建时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano /milli 来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nano |
autoUpdateTime | 创建/更新时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano /milli 来追踪纳秒、毫秒时间戳,例如:autoUpdateTime:milli |
index | 根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看 索引 获取详情 |
uniqueIndex | 与 index 相同,但创建的是唯一索引 |
check | 创建检查约束,例如 check:age > 13 ,查看 约束 获取详情 |
<- | 设置字段写入的权限, <-:create 只创建、<-:update 只更新、<-:false 无写入权限、<- 创建和更新权限 |
-> | 设置字段读的权限,->:false 无读权限 |
- | 忽略该字段,- 表示无读写,-:migration 表示无迁移权限,-:all 表示无读写迁移权限 |
comment | 迁移时为字段添加注释 |
关联标签
标签 | 描述 |
---|---|
foreignKey | 指定当前模型的列作为连接表的外键 |
references | 指定引用表的列名,其将被映射为连接表外键 |
polymorphic | 指定多态类型,比如模型名 |
polymorphicValue | 指定多态值、默认表名 |
many2many | 指定连接表表名 |
joinForeignKey | 指定连接表的外键列名,其将被映射到当前表 |
joinReferences | 指定连接表的外键列名,其将被映射到引用表 |
constraint | 关系约束,例如:OnUpdate 、OnDelete |
单个声明
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"time"
)
// 1. 模型类定义
type User struct {
gorm.Model // GORM 定义一个 gorm.Model 结构体,其包括字段 ID、CreatedAt、UpdatedAt、DeletedAt
}
// gorm.Model 定义的结构体(自带集成的结构体)
type Model struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
// 2.1 相关字段约束
// gorm migrate 创建表时 不会创建忽略的字段
type User2 struct {
Name1 string `gorm:"<-:create"` // 允许读和创建
Name2 string `gorm:"<-:update"` // 允许读和更新
Name3 string `gorm:"<-"` // 允许读和写(创建和更新)
Name4 string `gorm:"<-:false"` // 允许读,禁止写
Name5 string `gorm:"->"` // 只读(除非有自定义配置,否则禁止写)
Name6 string `gorm:"->;<-:create"` // 允许读和写
Name7 string `gorm:"->:false;<-:create"` // 仅创建(禁止从 db 读)
Name8 string `gorm:"-"` // 通过 struct 读写会忽略该字段
Name9 string `gorm:"-:all"` // 通过 struct 读写、迁移会忽略该字段
Name0 string `gorm:"-:migration"` // 通过 struct 迁移会忽略该字段
}
// 2.2 相关字段约束-时间
// GORM 约定使用 CreatedAt、UpdatedAt 追踪创建/更新时间 如果定义了这种字段,GORM 在创建、更新时会自动填充 当前时间
//
type User3 struct {
CreatedAt time.Time // 在创建时,如果该字段值为零值,则使用当前时间填充
UpdatedAt int // 在创建时该字段值为零值或者在更新时,使用当前时间戳秒数填充
Updated1 int64 `gorm:"autoUpdateTime:nano"` // 使用时间戳纳秒数填充更新时间
Updated2 int64 `gorm:"autoUpdateTime:milli"` // 使用时间戳毫秒数填充更新时间
Created int64 `gorm:"autoCreateTime"` // 使用时间戳秒数填充创建时间
}
// 3.1 嵌套结构体-gorm.Model自带的结构体
/**
下面例子等价于
type User4 struct{
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
Name string
}
*/
type User4 struct {
gorm.Model
Name string
}
// 3.2 嵌套结构体-标签指定嵌套的结构体embedded
// 同时可针对嵌套的结构体内的字段使用标签embeddedPrefix进行结构体内字段前缀声明
type User5 struct {
Name string
Age uint
}
type UserInfo struct {
ID uint //
MyUser User5 `gorm:"embedded;embeddedPrefix:t_"`
}
/**
等价于
type UserInfo struct{
ID int
TName string
TAge uint
}
创建user_info表
CREATE TABLE `user_info` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`t_name` longtext,
`t_age` bigint unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/
func main() {
dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true, // 表名非复数即不带s
//TablePrefix: "t_", // 表名前缀
NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
//NameReplacer: strings.NewReplacer("ID", "id"), // 替换数据表字段ID为id
},
})
if err != nil {
panic("数据库连接失败")
}
// 迁移 schema
db.AutoMigrate(&UserInfo{})
}
Belongs To 一对一
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
//type Stu struct {
// ID uint64 `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
// Name string `gorm:"column:name;type:string;size:8;not null;default:'';comment:姓名"`
// Age uint8 `gorm:"column:age;not null;default:0;comment:年龄"`
// CreateTime time.Time `gorm:"column:create_time;type:timestamp;not null;default:current_timestamp;comment:创建时间"`
// UpdateTime time.Time `gorm:"column:update_time;type:timestamp;not null;default:current_timestamp on update current_timestamp;comment:更新时间"`
//}
type CU struct {
CID uint64 `gorm:"column:c_id;primaryKey;autoIncrement;not null;comment:主键id"`
Name string `gorm:"column:name;type:varchar(8);not null;default:'';comment:姓名"`
}
type CC struct {
ID uint64 `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
Name string `gorm:"column:name;type:varchar(8);not null;default:'';comment:公司名称"`
// gorm 默认使用关联的struct名+主键名(ID)方式指定外键 且需要指定struct
// 也可采用struct + 列名指定外键
// 默认方式 会采用CU + ID方式指定为外键 即CUID为外键 CUID需要声明
//CU CU
// 可对外键添加更多约束(关联的<c_id>更新则更新该外键|关联的<c_id>删除则设置外键值为NULL)
CU CU `gorm:"foreignKey:c_uid;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
CUID uint64
}
/**
CREATE TABLE `cu` (
`c_id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(8) NOT NULL DEFAULT '' COMMENT '姓名',
PRIMARY KEY (`c_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `cc` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(8) NOT NULL DEFAULT '' COMMENT '公司名称',
`c_uid` bigint unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk_cc_cu` (`c_uid`),
//CONSTRAINT `fk_cc_cu` FOREIGN KEY (`c_uid`) REFERENCES `cu` (`c_id`) // 第一版默认
CONSTRAINT `fk_cc_cu` FOREIGN KEY (`c_uid`) REFERENCES `cu` (`c_id`) ON DELETE SET NULL ON UPDATE CASCADE // 第二版对外键添加约束
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/
func main() {
dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true, // 表名非复数即不带s
//TablePrefix: "t_", // 表名前缀
//NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
//NameReplacer: strings.NewReplacer("ID", "id"), // 替换数据表字段ID为id
},
})
if err != nil {
panic("数据库连接失败")
}
// 迁移 schema
err = db.AutoMigrate(&CC{}, &CU{})
if err != nil {
fmt.Println("数据表迁移异常:" + err.Error())
return
}
database := db.Migrator().CurrentDatabase()
tables, _ := db.Migrator().GetTables()
fmt.Printf("数据表迁移成功;db:%v;tables:%v\n", database, tables)
// 对于需要建立belongs to 关系 数据表中需要存在外键
// 1. 新增
c := CC{ID: 1, Name: "c1", CU: CU{Name: "u1"}}
tx := db.Create(&c) // 关联数据CU也会创建
//tx := db.Omit("CU").Create(&c) // 忽略CU关联数据创建
//tx := db.Omit(clause.Associations).Create(&c) // 忽略所有关联数据创建
fmt.Printf("新增-影响行数:%v;异常:%v\n", tx.RowsAffected, tx.Error)
// 2. 查询
var c0 CC
//db.Preload("CU").Find(&c0, "id=?", 1)
// <=>
//db.Preload("CU", "c_id=?", 1).Find(&c0)
// <=>
//db.Joins("CU").Find(&c0, "id=?", 1)
// <=>
db.Joins("CU", db.Where(&CU{CID: 1})).Find(&c0)
sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
return tx.Joins("CU", db.Where(&CU{CID: 1})).Find(&c0)
})
fmt.Printf("关联查询sql=%v\n", sql)
fmt.Printf("关联查询-数据:%v\n", c0)
}
Has One一对一
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
type AU struct {
AID uint64 `gorm:"column:a_id;primaryKey;autoIncrement;not null;comment:主键id"`
Name string `gorm:"column:name;type:varchar(8);not null;default:'';comment:姓名"`
}
type AC struct {
ID uint64 `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
Name string `gorm:"column:name;type:varchar(8);not null;default:'';comment:公司名称"`
// gorm 默认使用关联的struct名+主键名(ID)方式指定外键 且需要指定struct
// 也可采用struct + 列名指定外键
// 默认方式 会采用AU + ID方式指定为外键 即AUID为外键 AUID需要声明
// 可对外键添加更多约束(关联的<a_id>更新则更新该外键|关联的<c_id>删除则设置外键值为NULL)
AU AU `gorm:"foreignKey:a_uid;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
AUID uint64 `gorm:"unique"` // 一对一需要设置外键为唯一
}
/**
CREATE TABLE `ac` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(8) NOT NULL DEFAULT '' COMMENT '公司名称',
`a_uid` bigint unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `a_uid` (`a_uid`),
CONSTRAINT `fk_ac_au` FOREIGN KEY (`a_uid`) REFERENCES `au` (`a_id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `au` (
`a_id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(8) NOT NULL DEFAULT '' COMMENT '姓名',
PRIMARY KEY (`a_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/
func main() {
dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true, // 表名非复数即不带s
//TablePrefix: "t_", // 表名前缀
//NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
//NameReplacer: strings.NewReplacer("ID", "id"), // 替换数据表字段ID为id
},
})
if err != nil {
panic("数据库连接失败")
}
// 迁移 schema
err = db.AutoMigrate(&AC{}, &AU{})
if err != nil {
fmt.Println("数据表迁移异常:" + err.Error())
return
}
database := db.Migrator().CurrentDatabase()
tables, _ := db.Migrator().GetTables()
fmt.Printf("数据表迁移成功;db:%v;tables:%v\n", database, tables)
// 1. 新增
c := AC{ID: 1, Name: "c1", AU: AU{Name: "u1"}}
tx := db.Create(&c) // 关联数据AU也会创建
//tx := db.Omit("AU").Create(&c) // 忽略CU关联数据创建
//tx := db.Omit(clause.Associations).Create(&c) // 忽略所有关联数据创建
fmt.Printf("新增-影响行数:%v;异常:%v\n", tx.RowsAffected, tx.Error)
// 2. 查询
var c0 AC
//db.Preload("AU").Find(&c0, "id=?", 1)
// <=>
//db.Preload("AU", "c_id=?", 1).Find(&c0)
// <=>
//db.Joins("AU").Find(&c0, "id=?", 1)
// <=>
db.Joins("AU", db.Where(&AU{AID: 1})).Find(&c0)
sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
return tx.Joins("AU", db.Where(&AU{AID: 1})).Find(&c0)
})
fmt.Printf("关联查询sql=%v\n", sql)
fmt.Printf("关联查询-数据:%v\n", c0)
}
一对多
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
type Novel struct {
ID uint64 `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
Name string `gorm:"column:name;type:varchar(8);not null;default:'';comment:小说名称"`
Person []Person `gorm:"foreignKey:novel_id;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
type Person struct {
ID uint64 `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
Name string `gorm:"column:name;type:varchar(8);not null;default:'';comment:姓名"`
// gorm 默认使用关联的struct名+主键名方式指定外键 且需要指定struct
// 也可采用struct + 列名指定外键
NovelID uint64
}
/**
CREATE TABLE `novel` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(8) NOT NULL DEFAULT '' COMMENT '小说名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `person` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(8) NOT NULL DEFAULT '' COMMENT '姓名',
`novel_id` bigint unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk_novel_person` (`novel_id`),
CONSTRAINT `fk_novel_person` FOREIGN KEY (`novel_id`) REFERENCES `novel` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/
func main() {
dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true, // 表名非复数即不带s
//TablePrefix: "t_", // 表名前缀
//NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
//NameReplacer: strings.NewReplacer("ID", "id"), // 替换数据表字段ID为id
},
})
if err != nil {
panic("数据库连接失败")
}
// 迁移 schema
err = db.AutoMigrate(&Novel{}, &Person{})
if err != nil {
fmt.Println("数据表迁移异常:" + err.Error())
return
}
database := db.Migrator().CurrentDatabase()
tables, _ := db.Migrator().GetTables()
fmt.Printf("数据表迁移成功;db:%v;tables:%v\n", database, tables)
// 1. 新增
c := Novel{ID: 1, Name: "笑傲江湖", Person: []Person{{Name: "令狐冲"}, {Name: "任盈盈"}}}
tx := db.Create(&c) // 关联数据MAU也会创建
//tx := db.Omit("Novel").Create(&c) // 忽略NOVEL关联数据创建
//tx := db.Omit(clause.Associations).Create(&c) // 忽略所有关联数据创建
fmt.Printf("新增-影响行数:%v;异常:%v\n", tx.RowsAffected, tx.Error)
// 2. 查询(查询novel并输出关联的person信息)
var ns []Novel
err = db.Model(&Novel{}).Preload("Person").Find(&ns).Error
fmt.Printf("关联查询-数据:%v\n", ns)
}
多对多
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
// 不使用中间表
type novel struct {
ID uint64 `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
Name string `gorm:"column:name;type:varchar(8);not null;default:'';comment:小说名称"`
Persons []*person `json:"persons" gorm:"many2many:novel_person;foreignKey:ID;joinForeignKey:novelId;joinReferences:personId"`
/**
many2many: 定义中间表名
foreignKey: 指定表字段为外键
joinForeignKey: 当前表关联到中间表的字段名
joinReferences: 反向引用的字段 用于查询关联表的信息
PS:相当于有个中间表 表名为novel_person;字段名有person_id和novel_id
*/
}
type person struct {
ID uint64 `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
Name string `gorm:"column:name;type:varchar(8);not null;default:'';comment:姓名"`
Novels []*novel `json:"novels" gorm:"many2many:novel_person;foreignKey:ID;joinForeignKey:personId;joinReferences:novelId"`
}
func main() {
dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true, // 表名非复数即不带s
//TablePrefix: "t_", // 表名前缀
//NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
//NameReplacer: strings.NewReplacer("ID", "id"), // 替换数据表字段ID为id
},
})
if err != nil {
panic("数据库连接失败")
}
// 迁移 schema
err = db.AutoMigrate(&novel{}, &person{})
if err != nil {
fmt.Println("数据表创建异常:" + err.Error())
panic("数据表创建异常:" + err.Error())
}
database := db.Migrator().CurrentDatabase()
tables, _ := db.Migrator().GetTables()
fmt.Printf("数据表创建成功;db:%v;tables:%v\n", database, tables)
// 1. 新增
ps := []*person{{Name: "令狐冲"}, {Name: "任盈盈"}}
db.Create(&ps)
n := novel{ID: 1, Name: "笑傲江湖", Persons: ps}
tx := db.Create(&n)
fmt.Printf("新增-影响行数:%v;异常:%v\n", tx.RowsAffected, tx.Error)
// 2. 查询 preload后跟结构体关联的字段名称
var ns []novel
err = db.Model(&novel{}).Preload("Persons").Find(&ns).Error
fmt.Printf("关联查询-数据:%v\n", ns)
var pss []person
//err = db.Preload("Novels", "name=?", "笑傲江湖").Find(&ps).Error
err = db.Model(&person{}).Preload("Novels").Find(&pss).Error
fmt.Printf("关联查询-数据:%v\n", pss)
}
文档
[1] https://gorm.io/zh_CN/docs/index.html
[2] https://pkg.go.dev/gorm.io/driver/mysql#section-readme
[3] https://learnku.com/docs/gorm
标签:name,fmt,db,gorm,result,id,入门 From: https://www.cnblogs.com/fsh19991001/p/18025620