go语言:GORM
7.GORM
-
对象关系映射。
数据表 -> 结构体 数据行 -> 结构体实例 字段 -> 结构体字段
-
优点:提高开发效率,缺点牺牲执行性能,灵活性,弱化SQL能力。
-
gorm下载: 官网
go get -u github.com/jinzhu/gorm
7.1数据库的连接
// 引包
import (
_ "github.com/jinzhu/gorm/dialects/mysql"
"github.com/jinzhu/gorm"
)
// 结构体创建
type UserInfo struct {
Id uint
Name string
Gender string
Hobby string
}
- 连接数据库
// 连接数据库 这里指定编码格式,可解析时间类型,时间取当地时间
db, err :=gorm.Open("mysql", "root:123@(ip:3306)/db1?charset=utf8mb4&parseTime=True&loc=Local")
if err != nil{
panic(err)
}
// 关闭连接
defer db.Close()
7.2数据库迁移和最简单CURD
- 创建表 自动迁移。让结构体和数据表形成对应关系
db.AutoMigrate(&UserInfo{})
- 插入一条数据
/ 创建数据行
user1 := UserInfo{1,"jk","man","篮球"}
db.Create(&user1)
- 查询第一条数据
var u UserInfo
db.First(&u)
fmt.Println(u)
- 更新一条数据
var u UserInfo
// 查询到第一条数据,然后更新
db.First(&u)
db.Model(&u).Update("hobby","running")
- 删除操作
db.Delete(&u)
7.3 GORM模型定义
- GORM 内置一个gorm.Model结构体,gorm.Model是一个包含了ID,CreteAt,UpdateAt,DeleteAt四个字端的Golang结构体。
type Model struct {
ID uint `gorm:"primary_key"`
CreateAt time.Time
UpdateAt time.Time
DeleteAt *time.Time
}
- 你可以自行将它嵌入到你自己模型中:
type User struct {
gorm.Model
Name string
}
- 模型定义示例
type User struct{
gorm.Model// 内嵌Model
Name string
Age sql.NullInt64//零值类型
Birthday *time.Time
Email string `gorm:"type:varchar(100);unique_index"`
Role string `gorm:"size:255"`// 设置字端大小255
MemberNumber string `gorm:"unique;not null"`// 设置会员号,唯一且不为空
Num int `gorm:"AUTO_INCREMENT"`//设置num为自增类型
Address string `gorm:"index:addr"`// 给address设置索引为addr
IgnoreMe int `gorm:"-"`//忽略本字端
}
- 自定义字段为主键
type Animal struct {
AnimalID int64 `gorm:"primary_key"`
Name string
}
- 表的名字默认是结构体复数,你也可以自定义表的名字
type Animal struct {
AnimalID int64 `gorm:"primary_key"`
Name string
}
// 自定义表名字,因唯一指定表名,不会受方法影响
func (Animal) TableName() string{
return "self_animal"
}
- 禁用表的复数形式
db.SingularTable(true)
- 强行指定表名
// 使User结构体创建名叫 mysql_self_name表
db.Table("mysql_self_name").CreateTable(&User{})
- 通过自定义方法设计表名
gorm.DefaultTableNameHandle = func (db *gorm.DB, defaultTableName string) string{
return "SMS_" + defaultTableName
}
// 给创建的默认表名,加前缀SMS
- 自定义列的名称
type User struct {
Age sql.NullInt64 `gorm:"column:user_age"`
}
// 会新增一个user_age字端。
-
时间类型
CreatedAt
db.Create(&user)
db.Model(&user).Update("CreatedAt", time.Now())
UpdatedAt
db.Save(&user)
db.Model(&user).Update("name","xjk")
DeletedAt
- 调用删除记录,将会设置DeletedAt字端为当前时间,而不是直接将记录从数据库中删除。
7.4 GORM 的CURD
- 首先定义一个模型
type uGroup struct {
Id int64
Name string `gorm:"defalut:'wang'"`
Age int64
}
7.4.1创建记录
- NewRecord
var u = type uGroup struct {
Id int64
Name string `gorm:"defalut:'wang'"`
Age int64
}{Name:"laobing2", Age:77}
pk := db.NewRecord(&u)
fmt.Println(pk)// true
// NewRecord 查询当主键为空返回true
// 创建用户
db.Create(&u)
pk2 := db.NewRecord(u)
// 此时用户已创建,已经有主键
fmt.Println(pk2)// false
7.4.2 默认值
- 通过tag定义字段的默认值。
type uGroup struct {
Id int64
Name string `gorm:"default:'ss'"`
Age int64
}
- 值得注意是,在创建记录生成SQL语句会排除没有值或值为零的字段。在将记录插入到数据库后,Gorm会从数据库加载到那些字端默认值。
var u = uGroup{Name:"",Age:99}
db.Create(&u)
- 所有字段零值,如: 0 "" false 或者其他零值。都不会保存到数据库中,但会使用他们默认值。若果想要避免此情况,可以考虑使用指针.
- 方式1:指针方式实现
type uGroup struct {
Id int64
Name *string `gorm:"default:'ss'"`
Age int64
}
var u = uGroup{Name:new(string), Age:77}
db.Create(&u)
- 方式2:Scanner/Valuer接口方式
type uGroup struct {
Id int64
Name sql.NullString `gorm:"default:'ss'"`
Age int64
}
var u = uGroup{Name:sql.NullString{"", true}, Age:55}
db.Create(&u)
7.4.3普通查询
- 一般查询
var u uGroup
// 根据主键查询
db.First(&u)
// 获取第一条记录
db.Take(&u)
// 根据主键查询最后一条记录
db.Last(&u)
// 查询所有记录
var us []uGroup
db.Find(&us)
// 查询主键id=3记录
var u uGroup
db.First(&u, 3)
7.4.4 Where查
- 普通SQL查询
var u uGroup
// 查询name=ss 的第一条记录
db.Where("name=?","ss").First(&u)
// 查询 所有满足name=ss的记录
var us []uGroup
db.Where("name=?","ss").Find(&us)
// 查询 name <>
db.Where("name <> ?","ss").Find(&us)
// IN 查询
db.Where("name IN (?)", []string{"ss","jj"}).Find(&us)
// LIKE
db.Where("name LIKE ?","%ss%").Find(&us)
// AND
db.Where("name = ? AND age >= ?", "ss", "25").Find(&us)
// 时间Time
db.Where("updated_at > ?", lastWeek).Find(&us)
db.Where("created_at BETWEEN ? AND ?",last, yest).Find(&us)
7.4.5 struct & Map查询
- Struct demo
var u uGroup
// 查询name=ss,age=20
db.Where(&uGroup{Name:"ss",Age:20}).First(&u)
- Map查询
// Map查询
var us []uGroup
db.Where(map[string]interface{}{"name":"ss","age":77}).Find(&us)
// SELECT * FROM u_groups WHERE name = "ss" AND age = 77;
- 主键的切片
db.Where([]int64{1,2,5}).Find(&us)
// SELECT * FROM u_groups WHERE id IN (1, 2, 5)
- 当通过结构体进行查询时,GORM将会只通过非零值字段查询。这意味着如果你的字段值为0,"",false或者其他零值时,将不会被用于构建查询条件。
db.Where(&User{name:"ss",Age:0}).Find(&us)
- 不过你可以通过上面提到2个方法。通过指针或是Scanner/Valuer
7.4.6Not条件查询
db.Not("name","ss").First(&u)
// SELECT * FROM u_groups WHERE name <> "ss" LIMIT 1
// Not In
db.Not("name", []string{"ss","1234"}).Find(&us)
// 主键不在切片中
db.Not([]int64{1,2,3}).First(&u)
// 查询第一个
db.Not([]int64{}).First(&u)
// Struct
db.Not(uGroup{Name:"xx"}).First(&u)
7.4.7 Or条件查询
db.Where("name=?","ss").Or("age=?","43").Find(&us)
//SELECT * FROM u_groups WHERE name = 'ss' OR age = 43;
// Struct
db.Where("name='jk'").Or(uGroup{Name:"ming"}).Find(&us)
// Map
db.Where("name='jk'").Or(map[string]interface{}{"name":"ming"}).Find(&us)
//SELECT * FROM u_groups WHERE name = 'ss' OR name = 'ming';
7.4.8内联条件
- 作用与Where查询类似,当内联条件与多个立即执行方法一起使用时,内联条件不会传递给后面立即执行。
db.First(&u,5)//适用整形
db.First(&u,"id=?","string_primary_key")//适用菲整形主键
db.Find(&u,"name <> ? AND age > ?","jk", 20)
// struct
db.Find(&us,uGroup{Age:20})
// SELECT * FROM u_groups WHERE age=20;
// Map
db.Find(&u,map[string]interface{}{"age":20})
// SELECT * FROM u_groups WHERE age=20;
7.4.9额外查询选项
- 为查询SQL添加额外的SQL操作
db.Set("gorm:query_option","FOR UPDATE").First(&u,5)
// SELECT * FROM u_groups WHERE id=5 FOR UPDATE;
7.4.10FirstOrInit
- 获取匹配的第一条记录,否则根据给定的条件初始化一个新的对象(仅支持struct 和 map 条件)
// 当没有找到
db.FirstOrInit(&u, uGroup{Name:"not exist"})
fmt.Println(u)//返回结果:{0 not exist 0}
// 当找到了
db.Where(uGroup{Name: "ss"}).FirstOrInit(&u)
fmt.Println(u)//返回结果: {1 ss 77}
// 通过map获取
db.FirstOrInit(&u,map[string]interface{}{"name":"ss"})
- Attrs
- 如果记录未找到,将使用参数初始化struct
// 1.未找到:
db.Where(uGroup{Name:"not exist"}).Attrs(uGroup{Age:22}).FirstOrInit(&u)
fmt.Println(u)// {0 not exist 22}
// 当你查询用户名: not exist不存在时候会 默认返回User{Name: "not exist", Age: 20}
// 当然你也可以这么写
db.Where(uGroup{Name:"not exist"}).Attrs("age", 20).FirstOrInit(&u)
// 2.找到了
相同写法。
- Assign
- 无论是否查询到,都将参数赋值给struct
// 未找到
db.Where(uGroup{Name:"not exist"}).Assign(uGroup{Age:22}).FirstOrInit(&u)
fmt.Println(u)// {0 not exist 22}
// 找到
db.Where(uGroup{Name:"ss"}).Assign(uGroup{Age:77}).FirstOrInit(&u)
fmt.Println(u)// {1 ss 77}
7.4.11FirstOrCreate
- 获取匹配的第一条记录,否则根据给定的条件创建一个新的记录(仅支持struct和map条件)
// 未找到
db.FirstOrCreate(&u, uGroup{Name:"not exist"})
fmt.Println(u)// 没有找到,直接创建了:{15 not exist 0}
// 找到
db.Where(uGroup{Name: "ss"}).FirstOrCreate(&u)
fmt.Println(u)//{1 ss 77}
- Attrs
- 如果记录未找到,将使用参数创建struct 和记录
db.Where(uGroup{Name:"Tom"}).Attrs(uGroup{Age:20}).FirstOrCreate(&u)
fmt.Println(u) //未找到会创建: {16 Tom 20},找到了会返回结果
- Assign
- 不管记录是否找到,豆浆参数赋值给struct并保存数据库
db.Where(uGroup{Name:"Lucy"}).Assign(uGroup{Age:30}).FirstOrCreate(&u)
fmt.Println(u) // 未找到创建:{17 Lucy 30}
db.Where(uGroup{Name:"Lucy"}).Assign(uGroup{Age:32}).FirstOrCreate(&u)// 找到了,更改用户信息,age改为32
7.5GORM高级查询
7.5.1子查询
- 通过*gorm.expr的子查询
type Order struct {
DeletedAt interface{}
State string
Amount int
}
var orders Order
// SELECT * FROM "orders" WHERE "orders"."deleted_at" IS NULL AND (amount > (SELECT AVG(amount) FROM "orders" WHERE (state = 'paid')));
db.Where("amount > ?",db.Table("orders").Select("AVG(amount)").Where("state = ?","sk2").SubQuery()).Find(&orders)
fmt.Println(rest)
fmt.Println(orders)
7.5.2选择字段
- Select 指定你想从数据库中检索的字段,默认会选择全部字段
var userinfo []uGroup
// 方式1
db.Select("name,age").Find(&userinfo)
fmt.Println(userinfo)
//// SELECT name, age FROM users;
// 方式2
db.Select([]string{"name","age"}).Find(&userinfo)
fmt.Println(userinfo)
// 方式3
db.Table("users").Select("COALESCE(age,?)", 42).Rows()
//// SELECT COALESCE(age,'42') FROM users;
7.5.3排序
- Order 指定从数据库中检索出记录顺序。设置第二个参数reorder为true ,可以覆盖前面定义的排序条件。
var users []uGroup
db.Order("age desc,name").Find(&users)
fmt.Println(users)
// SELECT * FROM u_group order by age desc,name
// 多字段排序
db.Order("age desc").Order("name").Find(&users)
fmt.Println(users)
/ SELECT * FROM users ORDER BY age desc, name;
// 覆盖排序
db.Order("age desc").Find(&users1).Order("age", true).Find(&users2)
//// SELECT * FROM users ORDER BY age desc; (users1)
//// SELECT * FROM users ORDER BY age; (users2)
7.5.4数量
- limit 指定数据库检索的最大记录数。
var users []uGroup
db.Limit(2).Find(&users)
fmt.Println(users)
// SELECT * FROM users LIMIT 3;
// -1 取消Limit条件
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
//// SELECT * FROM users LIMIT 10; (users1)
//// SELECT * FROM users; (users2)
7.5.5偏移
- Offset,指定开始返回记录前要跳过的记录数。
var users []uGroup
db.Limit(2).Offset(1).Find(&users)
fmt.Println(users)
// SELECT * FROM u_groups limit 3 offset 1;
7.5.6统计总数
var users []uGroup
var count int
db.Where("name = ?", "xiaoming2").Or("name = ?","xiaoming3").Find(&users).Count(&count)
fmt.Println(users,count)
// [{2 xiaoming2 13} {3 xiaoming3 23}] 2
db.Model(&uGroup{}).Where("name = ?","xiaoming2").Count(&count)
fmt.Println(count)
// SELECT count(*) FROM users WHERE name = "xiaoming2"
db.Table("u_groups").Count(&count)
// SELECT count(*) FROM u_groups;
db.Table("u_groups").Select("count(distinct(name))").Count(&count)
//// SELECT count( distinct(name) ) FROM u_groups;
- 注意:Count 必须是链式查询的最后一个操作,因为它会覆盖前面SELECT,但如果里面使用count时就不会覆盖。
7.5.7Group & Having
- 示例1:
type Result struct{
State string
Total int
}
rows, err := db.Table("orders").Select("state,sum(amount) as total").Group("state").Rows()
for rows.Next() {
r := &Result{}
err = db.ScanRows(rows,r)
fmt.Println(r)
if err != nil{
fmt.Println(err)
break
}
}
- 示例2:
var res []Result
db.Table("orders").Select("state,sum(amount) as total").Group("state").Scan(&res)
fmt.Println(res)
- 示例三
// Having
rows, err := db.Table("orders").Select("state,sum(amount) as total").Group("state").Having("sum(amount) > ?",30).Rows()
if err != nil{
fmt.Println(err)
}
for rows.Next() {
r := &Result{}
err = db.ScanRows(rows,r)
if err != nil {
fmt.Println(err)
break
}
fmt.Println(*r)
}
- 示例四:
var res []Result
db.Table("orders").Select("state,sum(amount) as total").Group("state").Having("sum(amount) > ?",30).Scan(&res)
fmt.Println(res)
7.5.8连接
- 通过Join进行连接。
type uGroup struct {
Id int64
Name string `gorm:"default:'ss'"`
Age int64
}
type Email struct {
Id int64
Email string
UserId int64
}
type Result2 struct {
Uname string
Ename string
}
rows, err := db.Table("u_groups").Select("u_groups.name as uname, emails.email as ename").Joins("left join emails on emails.id = u_groups.id").Rows()
for rows.Next(){
r := &Result2{}
err = db.ScanRows(rows,r)
if err != nil{
fmt.Println(err)
break
}
fmt.Println(*r)
}
- 示例2:
type Result2 struct {
Uname string
Ename string
}
var results []Result2
db.Table("u_groups").Select("u_groups.name as uname, emails.email as ename").Joins("right join emails on emails.id = u_groups.id").Scan(&results)
fmt.Println(results)
- 示例3:
var u uGroup
db.Joins("JOIN emails ON emails.id = u_groups.id AND emails.email = ?", "235102030@qq.com").Joins("JOIN credit_cards ON credit_cards.id=u_groups.id").Where("credit_cards.common=?","common1").Find(&u)
fmt.Println(u)
7.5.9Pluck
- Pluck 查询model中的一个列作为切片,如果想要查询多个列可以收纳柜Scan
var ages []int64
var users []uGroup
db.Find(&users).Pluck("age",&ages)
fmt.Println(ages)
- Model指定表Struct
var names []string
db.Model(&uGroup{}).Pluck("name",&names)
fmt.Println(names)
- 查找多个列
db.Select("name,age").Find(&users)
fmt.Println(users)
7.5.10扫描
- Scan扫描结果至一个struct
type Result struct {
Name string
Age int
}
var result Result
db.Table("u_groups").Select("name,age").Where("name=?","xiaoming").Scan(&result)
fmt.Println(result)
- 示例2
var results []Result
db.Table("u_groups").Select("name,age").Where("id > ?",0).Scan(&results)
fmt.Println(results)
- 示例3:
//原生SQL
db.Raw("SELECT name,age from u_groups WHERE name=?","xiaoming").Scan(&results)
fmt.Println(results)
7.5.11 链式操作相关
- 链式操作,Gorm实现了链式操作接口,所以你可以把代码写成这样。
var results []Result
tx := db.Where("name = ?","xiaoming")
// 添加更多条件
judge:=true
if judge {
tx = tx.Where("age > ?",5)
} else {
tx = tx.Where("age = ?",30)
}
tx.Find(&results)
fmt.Println(results)
- 在调用立即执行方法前不能生成Query语句,借助这个特性你可以i创建一个函数处理一些普通逻辑。
7.5.12范围
- Scopes,Scopes 是建立在链式操作的基础之上。基于它,你可以抽取一些通用逻辑,写出更多可重用的函数库。
func UGroupAge20(db *gorm.DB) *gorm.DB{
return db.Where("age > ?", 20)
}
func NameeqXiaoming(db *gorm.DB) *gorm.DB{
return db.Where("name = ?","xiaoming")
}
func UGroupIdeq1(db *gorm.DB) *gorm.DB{
return db.Where("id = ?", 1)
}
func uGroupInId(ids []int) func(db *gorm.DB) *gorm.DB{
return func(db *gorm.DB) *gorm.DB {
return db.Scopes(UGroupAge20).Where("id IN (?)",ids)
}
}
var us []uGroup
db.Scopes(UGroupAge20,NameeqXiaoming,UGroupIdeq1).Find(&us)
fmt.Println(us)
db.Scopes(uGroupInId([]int{1,2})).Find(&us)
fmt.Println(us)
7.5.13多个立即执行
- 在GORM中使用多个立即执行方法时,后一个立即执行方法会服用前一个
立即执行方法
的条件(不包含内联条件)
var users []uGroup
var count int64
db.Where("name Like ?", "xiaoming%").Find(&users,"id IN (?)", []int{1,2,3}).Count(&count)
fmt.Println(users)
fmt.Println(count)
7.6 更新
7.6.1更新所有字段
- 使用Save() 默认会更新所有字段,即使你没有去赋值。
var user uGroup
db.First(&user)
fmt.Println(user)
user.Name = "jk"
user.Age = 22
db.Save(&user)
7.6.2 更新修改字段
- 使用Update或者Updates更新指定字段
// 更新单个属性,如果它有变化
db.Model(&user).Update("name","hello")
// 根据条件更新单个属性
db.Model(&user).Where("age = ?",21).Update("name","jk")
// 使用map更新多个属性,只会更新其中有变化属性
db.Model(&user).Updates(map[string]interface{}{"name":"xujunkai","age":18})
// 使用struct 更新多个属性,只会更新其中变化且非零的字段
db.Model(&user).Updates(uGroup{Name:"xjk",Age: 20})
// struct更新时,GORM只会更新那些非零值的字段。
// 对于 "" 0 false 不会发生任何更新
db.Model(&user).Updates(uGroup{Name:"",Age: 0})
7.6.3 更新选定字段
- 如果你想更新或忽略某些字段,你可以使用
Select
,Omit
// 下面我们在map中更新name,age.但是我们只选择了name.所以只更新name
db.Model(&user).Select("name").Updates(map[string]interface{}{"name":"xujunkai","age":19})
// Omit 表示要忽略字段
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name":"xiaoming","age":30})
7.6.4无Hooks更新
- 上面更新操作会自动运行Model的。BeforeUpdate,AfterUpdate方法。更新UpdateAt时间戳。更新时保存其Associations.如果你想调用这些方法,你可以使用UPdateColume,UpdateColumns
db.First(&user)
db.Model(&user).UpdateColumn("name","hello")
// 更新多个属性。类似Updates
db.Model(&user).UpdateColums(User{Name:"hello",Age:18})
7.6.5批量更新
- 批量更新时,Hook(钩子函数)。不会更新。
// map方式批量更新
db.Table("u_groups").Where("id IN (?)",[]int{1,5,11}).Updates(map[string]interface{}{"name":"xjk","age":33})
// struct 批量更新 只会更新非零字段。如果想更新所有字段。请使用map
db.Model(uGroup{}).Updates(uGroup{Name:"xiaoming",Age: 15})
// RowsAffected 获取更新记录
res := db.Model(uGroup{}).Updates(uGroup{Name: "xjk",Age: 40}).RowsAffected
fmt.Println(res)
7.6.6使用SQL表达式
- 查询表中第一条数据,然后更新
ar user uGroup
db.First(&user)
db.Model(&user).Update("age",gorm.Expr("age*?+?",4,1))
// UPDATE `u_groups` SET `age` = age * 4 + 1, `updated_at` = '2020-08-01 13:10:20' WHERE `users`.`id` = 1;
db.Model(&user).Updates(map[string]interface{}{"age":gorm.Expr("age - ?",500)})
db.Model(&user).Where("age > 10").UpdateColumn("age",gorm.Expr("age - ?",5))
7.6.7其他更新选项
// 为 update SQL添加其他SQL
db.Model(&user).Set("gorm:update_option","OPTION (OPTIMIZE FOR UNKNOWN)").Update("name","hello")
7.7删除
7.7.1删除记录
- 警告 删除记录时,请确保主键字段有值。GORM会通过主键去删除记录,如果主键为空,GORM会删除该model所有记录.
var user uGroup
db.First(&user)
db.Delete(&user)
7.7.2批量删除
- 删除全部匹配记录
db.Where("email LIKE ?","%xjk%").Delete(uGroup{})
7.7.3软删除
- 如果一个model有,DeletedAt字段,他将自动获得软删除功能,当调用Delete方法时,记录不会真正的从数据库中被删除,只会DeletedAt字段值会被设置为当前时间
db.Delete(&user)
// 批量删除
db.Where("age = ?",20).Delete(&u_Group{})
// 查询会忽略被软删除的记录
db.Where("age = ?",84).Find(&user)
// 通过Unscoped方法可以查询被软删除的记录
var users []uGroup
db.Unscoped().Where("age=?",84).Find(&users)
fmt.Println(users)
7.7.4物理删除
db.Unscoped().Delete(&user)
- 详细参见官方文档:GORM直通车
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库