[Go语言Web03]GORM数据库操作
1. GORM连接MySQL
1.1 ORM
1.1.1 什么是ORM
1.1.2 ORM优缺点
优点:
- 提高开发效率
缺点:
- 牺牲执行性能
- 牺牲灵活性
- 弱化SQL能力
1.2 GROM
执行下面命令安装GROM
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
(数据库不同这个也不同)
1.3 连接数据库
用gorm.Open
来完成数据库的连接,参数是规定好的,一般就按照这样就可以,有特殊需求再去修改。
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
其中root:123456
前面是账号,后面是密码;@
后面是连接的服务器及其端口(本地默认端口127.0.0.1:3306);/
后面grom
是数据库的名字;?
后面就是一些配置了。
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type UserInfo struct {
ID int
Name string
Gender string
Hobby string
}
func main() {
// 连接数据库
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// 创建表 自动迁移 (把结构体和数据表进行对应)
db.AutoMigrate(&UserInfo{})
// 创建数据行
u1 := UserInfo{
ID: 1,
Name: "firecar",
Gender: "男",
Hobby: "篮球",
}
db.Create(&u1)
}
可以使用AutoMigrate
方法来创建数据表(根据结构体的成员信息来创建)。
插入行直接db.Create(&u1)
,很方便,注意传的是地址就可以了。
1.4 简单操作
1.4.1 增Create
u1 := UserInfo{
ID: 1,
Name: "firecar",
Gender: "男",
Hobby: "篮球",
}
db.Create(&u1)
1.4.2 查First(第一条数据)
还有其他很多方法后续再补充。
u := new(UserInfo)
db.First(u)
fmt.Printf("u:%#v\n", u)
1.4.3 改
因为在First里面已经给u赋值了,所以才能通过Model...修改。
u := new(UserInfo)
db.First(u)
db.Model(u).Update("hobby", "rap")
fmt.Printf("u:%#v\n", u)
1.4.4 删
u := new(UserInfo)
db.First(u)
db.Delete(u)
可以看出来修改和删除都必须通过查询来得到具体的实例才可以进行操作。
1.5 MySQL数据库启动模板
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
// 连接数据库
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
}
2. GORM Model
在使用ORM工具时,通常我们需要在代码中定义模型(Models)与数据库中的数据表进行映射,在GORM中模型
(Models)通常是正常定义的结构体、基本的go类型或它们的指针。同时也支持sql.Scanner
及driver.Valuer
接口(interfaces) 。
2.1 gorm.Model
// gorm.Model
type Model struct{
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdateAt time.Time
DeleteAt *time.Time
}
可以把它嵌入到自己的模型中:
type User struct {
gorm.Model
Name string
}
当然用不用无所谓的……
2.2 定义模型(模型Tag)
package main
import (
"database/sql"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"time"
)
type User struct {
gorm.Model // 内嵌gorm.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"` // 设置会员号(member number)唯一并且不为空
Num int `gorm:"AUTO_INCREMENT"` // 设置 num 为自增类型
Address string `gorm:"index:addr"` // 给address字段创建名为addr的索引
IgnoreMe int `gorm:"-"` // 忽略本字段
}
func main() {
// 连接数据库
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
}
使用结构体声明模型时,标记(tags)是可选项。
gorm支持以下标记:
2.3 主键、表名、列名的约定
2.3.1 主键
默认把ID
作为主键。如果想要自行设置主键可以:
type Animal struct {
AnimalID int64 `gorm:"primary_key"`
Name string
Age int64
}
通过使用tag来设置主键。
2.3.2 表名
默认是结构体名称的小写、复数,驼峰标明的会用_
分隔开。
如:type Animal
-> animals
;type UserInfo
->user_infos
。
如果想要自行设置,需要写一个方法:
type Animal struct {
AnimalID int `gorm:"primary_key"`
Name string
}
func (Animal) TableName() string {
return "firecar"
}
还可以用方法加一些判断,一下就是判断是不是管理员再决定使用哪个表。
func(User) TableName()string {
return "profiles"
}
func(u User) TableName() string {
if u.Role == "admin" {
return "admin_users"
}
return "users"
}
禁用表的复数形式db.SingularTable(true)
还可以直接自己指定表的名字:
// 使用User结构体创建名为`deleted_users`的表
db.Table("deleted_users").CreateTable(&User{})
var deleted_users []User
db.Table("deleted_users").Find(&deleted_users)
//// SELECT * FROM deleted_users;
db.Table("deleted_users").Where("name = ?", "jinzhu").Delete()
//// DELETE FROM deleted_users WHERE name = 'jinzhu';
但是好像要一直自己指定,因为直接用db
的话他会以规则来查找表。
那最终的方案就是修改db
的从实例出发找表的默认规则了。
gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string {
return "prefix_" + defaultTableName;
}
放在main函数开头,defaultTableName
就是你的结构体名,可以给他加一个前缀,那么就return "prefix_" + defaultTableName;
当然也可以进行其他的操作。
当然,因为是默认规则,所以如果使用了其他方法修改了,就不会再去使用默认规则了。
2.3.3 列名
列名由字段名称进行下划线分割来生成,大写变小写。
type User struct {
ID uint // `id`
Name string // `name`
Birthday time.Time // `birthday`
CreatedAt time.Time // `created_at`
}
使用tag来进行指定:
type Animal struct {
AnimalId int64 `gorm:"column:beast_id"`
Birthday time.Time `gorm:"column:day_of_the_beast"`
Age int64 `gorm:"column:age_of_the_beast"`
}
2.4 时间戳
2.4.1 CreatedAt
db.Create(&user) // `CreatedAt`将会是当前时间
// 可以使用`Update`方法来改变`CreateAt`的值
db.Model(&user).Update("CreatedAt", time.Now())
2.4.2 UpdateAt
db.Save(&user) // `UpdatedAt`将会是当前时间
db.Model(&user).Update("name", "jinzhu") // `UpdatedAt`将会是当前时间
2.4.3 DeleteAt
如果模型有DeletedAt
字段,调用Delete
删除该记录时,将会设置DeletedAt
字段为当前时间,而不是直接将记录从数据库中删除(软删除)。
3. CRUD(增查改删)
增加(Create)、读取(Read)、更新(Update)和删除(Delete)
3.1 创建记录及字段默认值相关
通过Debug的方法来查看具体使用的SQL语句
3.1.1 创建记录
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// User 1. 定义模型
type User struct {
ID int
Name string
Age int
}
func main() {
// 连接MySQL数据库
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// 2. 把模型与数据库中的表对应起来
db.AutoMigrate(&User{})
// 3. 创建
u := User{Name: "firecar", age: 18} // 在代码层面创建一个User对象
res := db.Create(&u) // 通过数据的指针来创建
fmt.Println(u.ID) // 返回插入数据的主键
fmt.Println(res.Error) // 返回 error
fmt.Println(res.RowsAffected) // 返回插入记录的条数
}
/*
1
<nil>
1
*/
3.1.2 默认值
插入记录到数据库时,默认值会被用于填充值为零值的字段
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// User 1. 定义模型
type User struct {
ID int
Name string `gorm:"default:'firecar'"`
Age int
}
func main() {
// 连接MySQL数据库
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// 2. 把模型与数据库中的表对应起来
db.AutoMigrate(&User{})
// 3. 创建
u := User{Age: 18} // 在代码层面创建一个User对象
db.Debug().Create(&u) // 通过数据的指针来创建
}
/*
[7.326ms] [rows:1] INSERT INTO `users` (`name`,`age`) VALUES ('firecar',18)
*/
注意 对于声明了默认值的字段,像 0
、''
、false
等零值是不会保存到数据库。
什么意思呢,就是说,如果你设置了默认值,但是你想让你的name = ""
这是不行的,他会被默认忽略掉。
u := User{Name: "", Age: 18}
// [5.752ms] [rows:1] INSERT INTO `users` (`name`,`age`) VALUES ('firecar',18)
您需要使用指针类型或 Scanner/Valuer 来避免这个问题:
type User struct {
ID int
Name *string `gorm:"default:'firecar'"`
Age int
}
u := User{Name: new(string), Age: 28}
// [4.747ms] [rows:1] INSERT INTO `users` (`name`,`age`) VALUES ('',28)
第二个方法就是用一个sql.NullXxxx
类型来解决,先看一下这个类型:
type NullString struct {
String string
Valid bool // Valid is true if String is not NULL
}
Valid
就代表是不是有值,也就是说如果Valid = true
那么无论String
的值是什么(包括空字符串“”)它也同样会使用。
type User struct {
ID int
Name sql.NullString `gorm:"default:'firecar'"`
Age int
}
u := User{Name: sql.NullString{
String: "",
Valid: true,
}, Age: 28} // 在代码层面创建一个User对象
// [5.715ms] [rows:1] INSERT INTO `users` (`name`,`age`) VALUES ('',28)
3.2 查询操作
现在数据库里弄两条数据:
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 1. 创建模型
type User struct {
gorm.Model
Name string
Age int
}
func main() {
// 连接数据库
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// 2. 模型和数据库的表对应起来
db.AutoMigrate(&User{})
u1 := User{
Name: "firecar1",
Age: 18,
}
db.Create(&u1)
u2 := User{
Name: "sky6",
Age: 21,
}
db.Create(&u2)
}
3.2.1 一般查询
// 3. 查询
user := new(User)
db.First(user)
fmt.Printf("user:%#v", user)
// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;
// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;
// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error // returns error or nil
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
3.2.2 条件查询
// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';
// IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');
// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';
// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;
// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';
// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';
在Find
之前加上Where
语句,具体的就是SQL的内容了。
也可以把他直接写在db.Find()
的后面的参数里,如:
db.Find(&users,"name = ?", "firecar")
3.2.3 FirstOrInit查找或创建
如果查询的不存在就创建,否则还是查询。
db.FirstOrInit(&user, User{
Name : "firecar",
})
如果不存在就创建一个,存在就返回第一个。
3.2.4 Attrs创建时附加
如果要创建信息,就要把内容给附加上,如:
db.Attrs(User{
Age : 22
}).FirstOrInit(&user, User{
Name : "firecar",
})
这样,当firecar不存在的时候创建的firecar也会有Age
参数,并且等于22了。
3.2.5 Assign必须附加
和上面唯一的区别就是,Assign
不管找没找到都要附加上信息。
……更多看一下官方文档。
3.3 更新操作
更新操作和后面的删除操作那肯定是要依赖于上面的查询操作的,也可以说查询操作才是最关键的内容,因为你要更新总得要有个要修改的对象,对象哪里来?就是要通过查询操作来获取的。
3.3.1 更新所有字段
使用Save
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 1. 创建模型
type User struct {
gorm.Model
Name string
Age int
Active bool
}
func main() {
// 连接数据库
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// 2. 模型和数据库的表对应起来
db.AutoMigrate(&User{})
// 3. 查询
user := new(User)
db.First(user)
user.Name = "huoyanche"
user.Age = 100
db.Save(user)
}
此时所有的字段都会更新。
3.3.2 更新修改字段
使用Update
和Updates
来进行修改。
db.First(user)
db.Debug().Model(user).Update("name", "王大炮")
// UPDATE `users` SET `name`='王大,`updated_at`='2023-03-09 19:24:49.36' WHERE `users`.`deleted_at` IS NULL AND `id` = 1
3.3.3 更新选定字段
Select
和Omit
方法,Select
是只修改选中的,Omit
相反,只不修改选中的。
m1 := map[string]any{
"name": "ii7",
"age": 22,
"active": false,
}
db.Debug().Model(user).Updates(m1)
db.Debug().Model(user).Select("age").Updates(m1)
db.Debug().Model(user).Omit("active").Updates(m1)
Select
UPDATE `users` SET `age`=22,`up
dated_at`='2023-03-09 19:32:59.796' WHERE `users`.`deleted_at` IS NULL AND `id` = 1
Omit
UPDATE `users` SET `age`=22,`na
me`='ii7',`updated_at`='2023-03-09 19:32:59.801' WHERE `users`.`deleted_at` IS NULL AND `id` = 1
3.3.4 无HOOK
前面写的方法都会默认修改更新时间,尽管我们没有操作。如果Only修改制定的就用db.Debug().Model(user).UpdateColumn("age",30)
这样除了age
什么都不会修改了。
3.3.5 使用SQL表达式更新
让所有用户年龄+2。哎呀,这一块上次改了好久,终于发现问题所在,如果Find的参数是User
类型,它只会返回第一条查询;如果参数是[]User
类型,就会返回所有的查询。(db.Find
= SELECT * FROM ...
)
而且可能是版本不同,或者视频上有什么出入,感觉应该是不对的。
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 1. 创建模型
type User struct {
gorm.Model
Name string
Age int
Active bool
}
func main() {
// 连接数据库
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
//2. 模型和数据库的表对应起来
_ = db.AutoMigrate(&User{})
var users []User
db.Find(&users)
db.Model(&users).Update("age", gorm.Expr("age+?", 2))
}
3.4 删除操作
3.4.1 单条删除
软删除,就是给delete_at
赋值为当前的时间,表示已经被删除了,之后也不会对他进行操作。
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Age int
Active bool
}
func main() {
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
_ = db.AutoMigrate(&User{})
u := new(User)
u.ID = 1
db.Debug().Delete(u)
}
这种开一个实例对象然后再u.ID = 1
最后db.Delete
的删除方式只适用于删除指定主键
,因为在上面的代码中主键是ID因此可以通过这种操作完成任务。
如果不写主键(ID),会导致整张表的所有内容都被删除。
也就是说只要没有u.ID
就会进行一个相当于DELETE *
的操作(当然因为是软删除,所以只是把所有数据的delete_at
赋值)
3.4.2 批量删除
那如果我想根据别的列来删除,或者我要删除满足某个条件的所有信息就可以使用Where
来进行限制。
func main() {
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
_ = db.AutoMigrate(&User{})
db.Debug().Where("age = ?", 18).Delete(&User{})
}
/*
UPDATE `users` SET `deleted_at`='2023-03-11 11:08:09.101' WHERE age = 18 AND `users`.`deleted_at` IS NULL
*/
上面命令等价于db.Delete(&User{},"age = ?",18)
。
3.4.3 软删除以及物理删除
只要有deleted_at
这一列就会是软删除,被软删除的记录想要查询也是可以的:db.Unscoped().Where("age = 18").Find(&users)
。
如果想要物理删除(直接在数据库表中移除这一行)。
db.Unscoped().Delete(&order)
。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具