GORM框架
ORM(Object Relational Mapping,对象关系映射)框架,就是将go中的结构体与数据库表进行映射。通过使用orm框架,可以进行增删改查等操作,无无需编写sql语句,框架会根据对应代码设置生成相应的sql语句并在数据库中执行。这里来学习一下利用GORM框架来操作Mysql数据库的相关代码实现。
需要下先下载对应依赖包以及数据库驱动:
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
前置知识
结构体相关
前面说到了ORM就是结构体与数据库表相互映射,这里来简单了解一下结构体的相关设置。
一个常见的使用:
type User struct {
gorm.Model
Name string
Age int `gorm:"default:18"`
}
这里引入了gorm预定义的结构体,直接跟进其实对应结构如下:
type Model struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt DeletedAt `gorm:"index"`
}
里面几个字段的定义以及特殊设定:
ID:每条记录的唯一标识符(主键)CreatedAt:在创建记录时自动设置为当前时间。UpdatedAt:每当记录更新时,自动更新为当前时间。DeletedAt:用于软删除(将记录标记为已删除,而实际上并未从数据库中删除)。
故前面的结构体其实就相当于如下:
type User struct {
ID uint `gorm:"primarykey"`
Name string
Age int `gorm:"default:18"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index"`
}
故总体来说,这里通过标签(tag)来定义了ID为主键,Age的默认值为18,然后设置了auto….Time等标签来让字段的值自动更新,最后还可以看到设置了一个DeletedAt的索引。由此可见,go语言中结构体的tag是非常重要的,有非常多的用处。
当然结构体还可以直接嵌入另一个的结构体,可直接通过结构体名来输出,也可以通过标签embedded来嵌入,后续有需求再继续编写相关代码。
连接方式
连接mysql:
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
dsn := "root:root@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local)"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}
直接通过驱动来连接即可。后续就简单说说GORM框架的增删改查要怎么编写。
数据库操作demo
表创建
使用AutoMigrate()方法来创建表:
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Age int `gorm:"default:18"`
}
func main() {
//连接数据库:
dsn := "root:root@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
//创建结构体对应的表:
user_table := &User{}
err1 := db.AutoMigrate(user_table)
if err1 != nil {
fmt.Println(err1)
}
}
注意调用AutoMigrate()方法的对象,是连接数据库后的对象,简单理解一下即可。运行代码可以再数据库中看到成功创建表:
mysql> show tables;
+------------------+
| Tables_in_testdb |
+------------------+
| users |
+------------------+
1 row in set (0.00 sec)
mysql> desc users;
+------------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-----------------+------+-----+---------+----------------+
| id | bigint unsigned | NO | PRI | NULL | auto_increment |
| created_at | datetime(3) | YES | | NULL | |
| updated_at | datetime(3) | YES | | NULL | |
| deleted_at | datetime(3) | YES | MUL | NULL | |
| name | longtext | YES | | NULL | |
| age | bigint | YES | | 18 | |
+------------+-----------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
以及对应的表结构也是都符合的结构体的设置的,这里需要注意的一个点就是gorm默认会将结构体名转小写然后加复数成为新的表名,这里也就会创建名为users的表,想自定义表名就需要在结构体后面加标签,如下demo:
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Age int `gorm:"default:18"`
}
func (u *User) TableName() string {
return "user"
}
func main() {
//连接数据库:
dsn := "root:root@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
//创建结构体对应的表:
user_table := &User{}
err1 := db.Debug().AutoMigrate(user_table)
if err1 != nil {
fmt.Println(err1)
}
}
定义了一个绑定在User结构体方法,这样在创建的时候就会获取这个方法的返回值,而不是按照默认的走,运行后成功创建了user表。
并且在这里的创建语句中,我是加了一个调试模式的:
user_table := &User{}
err1 := db.Debug().AutoMigrate(user_table)
在控制台可以看到对应的使用的sql语句:

可以看到存在创建语句,再运行一次就可以知道如果表中有这个表并且字段对应,那么就不会再调用Create语句来创建表了,只会更新字段(如果字段变化)。
同样的这个调试模式可以用到其他的语句调用中,格式同等替换即可。
添加数据
单条插入数据(Create()方法):
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Age int `gorm:"default:18"`
Sex string
}
func main() {
//连接数据库:
dsn := "root:root@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
//创建结构体对应的表:
//user_table := &User{}
//err1 := db.Debug().AutoMigrate(user_table)
//if err1 != nil {
// fmt.Println(err1)
//}
people := &User{Name: "fupanc", Age: 22, Sex: "boy"} //成功插入后自动修改ID字段的值
result := db.Create(people)
if result.Error != nil {
panic("failed to create user")
}
fmt.Printf("User created with ID %d", people.ID)
}
控制台输出预期值,同样的表数据也发生了变化:

插入多条数据,可以使用for循环,比如如下demo:
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Age int `gorm:"default:18"`
Sex string
}
func main() {
//连接数据库:
dsn := "root:root@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
peoples := []User{{Name: "fupanc1", Age: 23, Sex: "boy"}, {Name: "fupanc2", Age: 24, Sex: "boy"}, {Name: "fupanc3", Age: 25, Sex: "boy"}}
for _, people := range peoples {
result := db.Create(&people)
if result.Error != nil {
fmt.Println(result.Error)
}
fmt.Println("成功插入数据,ID为:", people.ID)
}
}
输出为:
成功插入数据,ID为: 2
成功插入数据,ID为: 3
成功插入数据,ID为: 4
按照自增要求成功插入数据。
后面发现其实可以直接给数组形式,直接批量插入:
peoples := []User{{Name: "fupanc4", Age: 23, Sex: "boy"}, {Name: "fupanc5", Age: 24, Sex: "boy"}, {Name: "fupanc6", Age: 25, Sex: "boy"}}
result := db.Create(&peoples)
批量插入还可以使用CreateInBatches()来控制分批次多次插入数据:
peoples := []User{{Name: "fupanc5", Age: 23, Sex: "boy"}, {Name: "fupanc6", Age: 24, Sex: "boy"}, {Name: "fupanc7", Age: 25, Sex: "boy"}}
result := db.CreateInBatches(&peoples, 2)
删除数据
数据删除主要分逻辑删除和物理删除:
- 逻辑删除就是将DeletedAt更新为删除时间,然后在查询的时候自动加上
deleted_at IS NULL,以此不会获取到相关数据,以便于恢复数据,防止误删等等操作。 - 物理删除就是传统的数据删除,直接删掉对应行的数据,不可恢复。
逻辑删除,通常通过Delete()方法来操作:
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Age int `gorm:"default:18"`
Sex string
}
func main() {
//连接数据库:
dsn := "root:root@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
//逻辑删除
db.Delete(&User{}, "id > 1")
}
执行效果:

可以看到deleted_at的值发生了变化。同样的条件还可以这样设置:
db.Where("id > ?", 0).Delete(&User{})
debug一下可以发现执行的sql语句为:
UPDATE `users` SET `deleted_at`='2026-02-06 19:44:47.62' WHERE id > 1 AND `users`.`deleted_at` IS NULL
软删除,一个更新操作。
物理删除,需要Unscoped()+Delete()方法一起才能物理删除:
//物理删除
db.Where("id > ?", 1).Unscoped().Delete(&User{})
执行后可以发现id大于1的都被完全删掉了:

debug一下可以发现执行的sql语句为:
DELETE FROM `users` WHERE id > 1
直接调用delete操作删除数据。
数据查询
获取单条数据(First、last、take):
获取第一条数据(First()):
user := User{}//结构体接收查询到的数据
//即根据id排序然后获取第一条数据
first_data := db.Debug().First(&user)
if first_data.Error != nil {
panic("failed to connect database")
}
fmt.Println(user)
获取查询到的最后一条数据(Last()):
user := User{}
//即根据id排降序获取第一条数据
last_data := db.Debug().Last(&user, "age < 14")
if last_data.Error != nil {
panic("failed to connect database")
}
fmt.Println(user)
不指定排序,取第一条(Take()):
user := User{}
//不指定排序获取第一条数据:
last_data := db.Debug().Where("age < ?", 14).Take(&user)
if last_data.Error == gorm.ErrRecordNotFound {
panic("数据为空")
}
fmt.Println(user)
至于具体是怎么排的,看日志里面的sql语句即可。
如果查询不到数据,那么result.Error就为gorm.ErrRecordNotFound,可通过这个来判断查询结果是否为空。
获取所有数据:
users := []User{}
//获取所有数据:
last_data := db.Debug().Where("age < ?", 14).Find(&users)
if last_data.Error == gorm.ErrRecordNotFound {
panic("数据为空")
}
fmt.Println(users)
注意这里要使用User类型的数组,思考一下就可以知道,由于是映射关系,如果查询结果有多个,需要使用多个结构体来接收对应的值,否则只能获取到第一条数据,由于这里的使用场景,故我们需要设置数组类型来接收查询的结果。
延伸使用:
users := []User{}
//指定字段:
last_data := db.Debug().Select("id,name,age").Where("age < ?", 14).Find(&users)
if last_data.Error == gorm.ErrRecordNotFound {
panic("数据为空")
}
fmt.Println(users)//注意虽然这里只查询了几个字段,但是映射到结构体上,没查询的值是零值
//排序查询:
last_data := db.Debug().Order("age desc").Find(&users)
//注意都是可以嵌套使用的:
last_data := db.Debug().Select("id,name,age").Order("age desc").Limit(2).Find(&users)
//结果转json格式:
usersJson, _ := json.MarshalIndent(users, "", " ")
fmt.Println(string(usersJson))
需要的时候直接用就行,不是很难。
数据更改
单字段更新(Update()方法):
package main
import (
"encoding/json"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Age int `gorm:"default:18"`
Sex string
}
func main() {
//连接数据库:
dsn := "root:root@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
users := User{}
//获取一条数据:
last_data := db.Debug().Select("id,name,age").Take(&users)
if last_data.Error == gorm.ErrRecordNotFound {
panic("数据为空")
}
fmt.Println(transform_data(users))
//更新数据
updating_data := users.ID
user1 := User{}
db.Model(&user1).Debug().Where("id = ?", updating_data).Update("name", "fupanc123-updated")
//获取更新后的数据:
user2 := User{}
db.Debug().Where("id = ?", updating_data).Find(&user2)
fmt.Println(transform_data(user2))
}
func transform_data(users interface{}) string {
usersJson, _ := json.MarshalIndent(users, "", " ")
return string(usersJson)
}
输出效果:
{
"ID": 11,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"Name": "fupanc1",
"Age": 12,
"Sex": ""
}
{
"ID": 11,
"CreatedAt": "2026-02-09T11:24:00.783+08:00",
"UpdatedAt": "2026-02-09T13:45:09.537+08:00",
"DeletedAt": null,
"Name": "fupanc123-updated",
"Age": 12,
"Sex": "boy"
}
可以看到成功更新了对应字段的值。
多字段更新(Updates()方法):
可通过结构体或者Map更新多字段,但是结构体更新会忽略零值,Map更新支持零值,结构体代码如下:
package main
import (
"encoding/json"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Age int `gorm:"default:18"`
Sex string
}
func main() {
//连接数据库:
dsn := "root:root@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
users := User{}
//获取一条数据:
last_data := db.Debug().Where("id = 13").Select("id,name,age").Find(&users)
if last_data.Error == gorm.ErrRecordNotFound {
panic("数据为空")
}
fmt.Println(transform_data(users))
//更新数据
updating_data := users.ID
user1 := User{}
db.Model(&user1).Debug().Where("id = ?", updating_data).Updates(User{Name: "fupanc1234-updated", Age: 0})
//获取更新后的数据:
user2 := User{}
db.Debug().Select("id,name,age").Where("id = ?", updating_data).Find(&user2)
fmt.Println(transform_data(user2))
}
func transform_data(users interface{}) string {
usersJson, _ := json.MarshalIndent(users, "", " ")
return string(usersJson)
}
效果如下:
{
"ID": 13,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"Name": "fupanc3",
"Age": 14,
"Sex": ""
}
{
"ID": 13,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"Name": "fupanc1234-updated",
"Age": 14,
"Sex": ""
}
可以看到age的值并没有改变,这是因为结构体初始化后就会以零值代替,虽然这里想将Age设置为0,但是会被当成零值,也就是不需要更新,不能达到想要的效果。于是使用Map来解决这个问题。
Map代码如下:
db.Model(&user1).Debug().Where("id = ?", updating_data).Updates(map[string]interface{}{"Name": "fupanc", "Age": 0})//只需要修改一行代码如上
最后运行效果如下:
{
"ID": 13,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"Name": "fupanc1234-updated",
"Age": 14,
"Sex": ""
}
{
"ID": 13,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"Name": "fupanc",
"Age": 0,
"Sex": ""
}
可以看到json和age的值都改变了。后续可以多用。
当然GORM还有一些内置方法可以使用,后续再挖掘吧,先到这里了。
参考文章: