链接与建表
基础概念
- 数据库类似于一个Excel表,与Redis相比,SQL必须建一个库,才能建表
- Table类似于Excel里面的Sheet工作表,表必须规定好表头
- Model模型,就是Go的结构体,GORM负责把结构体变成MySQL表。
核心函数解析
① dsn (Data Source Name)
这是连接数据库的“身份证”,格式是固定的: 用户名:密码@tcp(IP:端口)/数据库名?参数
charset=utf8mb4:必须加,否则存不了表情包(支持Emoji)。parseTime=True:把数据库的时间自动转成 Go 的time.Time。
② gorm.Open(dialector, config)
- 建立连接池。
- 如果是生产环境,通常还需要配置
SetMaxIdleConns等参数,基础学习默认配置即可。
③ db.AutoMigrate(&Struct{})
- 自动迁移,它会扫描你的 Struct 字段,对比数据库里的表。如果表不存在 -> 自动创建。如果表存在但少了字段 -> 自动添加字段。不会删除字段:为了安全,它绝不会自动删除你数据库里已有的数据列。
④ gorm.Model
- 包含字段:
ID(主键),CreatedAt,UpdatedAt,DeletedAt。 - 在你的结构体里匿名嵌入它,你的表就自动拥有了这四个标准字段。
代码示例
这里我用docker起了一个数据库

并且
docker exec -it mysql01 mysql -uroot -p123456
create database gin_rank;
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Age int
}
func main() {
dsn := "root:123456@tcp(localhost:3306)/gin_rank?charset=utf8mb4&parseTime=True&loc=Local"
/*链接*/
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("Failed to connect database")
}
fmt.Println("Database connected successfully")
/*建表*/
err = db.AutoMigrate(&User{})
if err != nil {
panic("Migartion failed")
}
fmt.Println("Table 'users' migrated successfully!")
}
我们进入容器
docker exec -it mysql01 mysql -uroot -p123456
执行SQL
use gin_rank;
desc users;

CRUD
Create—增
核心函数解析
result := db.Create(&user)
- 参数:必须传入结构体的指针
&user。GORM 插入数据后,数据库会生成一个自增的ID(比如 1, 2, 3),GORM 需要把这个新生成的 ID 写回到你的user变量里,让你知道刚才插入的数据 ID 是多少。 - 返回值:返回一个
*gorm.DB对象(这里叫result),包含两个重要字段:result.Error: 如果插入失败(比如名字太长、数据库断开),这里会有值。result.RowsAffected: 告诉你插入了几条数据(通常是 1)。
代码实现
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Age int
}
func main() {
/*此处省略链接和建表*/
u1 := User{
Name: "ttdr",
Age: 18,
}
result := db.Create(&u1)
if result.Error != nil {
fmt.Println("插入失败", result.Error)
} else {
fmt.Printf("插入成功,新用户ID为:%d\n影响行数:%d\n", u1.ID, result.RowsAffected)
}
}

Read—查
核心函数解析
1.db.First(&user,1)
获取ID为1的内容,并且写入到user中,这里需要传地址如果没有id就默认第一条。
2.db.Where("name = ?", "ttdr").Find(&users)
根据条件查询名字为ttdr的数据,语法跟英语一样,好玩捏。后面是将获得到的数据写入到切片中。
代码实现
var user User
var users []User
db.First(&user)
fmt.Println("第一个用户", user.Name)
db.First(&user, 1) /*根据id查询*/
db.Where("name = ?", "ttdr").Find(&users)
fmt.Println("叫ttdr的数量为:", len(users))
db.Where("name = ? AND age >= ?", "ttdr", 18).Find(&users)

Change—改
核心函数解析
1.db.Save
整体保存,更新所有字段
2.Model + Update (局部更新)
只想对Age修改,不想动其他的
db.Model(&User{}).Where("id = ?", 0).Update("age", 18)
代码实现
var u User
db.First(&u, 1)
u.Name = "NewName"
u.Age = 99
db.Save(&u)
db.Model(&User{}).Where("id = ?", 1).Update("age", 25)

Delete—删
核心函数解析
db.Delete(&User{}, id)
没啥好说的喵
代码实现
db.Delete(&User{}, 1)

?????????怎么在
设计哲学
1.对0的极端恐惧
以上面为例,如果要把Age更新为0,你会这样写
db.Model(&user).Updates(User{Name: "baby", Age: 0})
但是数据库里的AGe不会修改,因为GO语言中,0,"",false,GoRM设计者认为如果字段是0谁知道你存的是0还是本身就没存,为了防止这种情况,在结构体更新时,自动忽略所有零值。
在实际开发中,如果我们一定要写入0的时候我们采用map写入,因为map没有键就是没有,有键就是有值。
db.Model(&user).Updates(map[string]interface{}{"name": "baby", "age": 0})
虽然这样确实有点烦,但是避免了前端少传字段,后端吧数据库字段洗白的事故。
2.软删除
因为数据的不可逆性,所以在企业开发中,物理删除是大忌(删库跑路),一旦物理删除,几乎不可恢复,所以GORM默认开启软删除,你查的时候查不到被删数据,再出现事故后,用SQL手动查或者用Unscoped()就能把删除的数据恢复。
RESTFUL API
引入
RESTful API是一种API设计风格,用HTTP方法来表达动作,我们来写POST,GET,PUT,DELETE增删改查四个接口
话不多说我们直接上代码
代码实现
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Age int
}
var DB *gorm.DB
func initDB() {
dsn := "root :123456@tcp(127.0.0.1:3306)/gin_rank?charset=stf8mb4&parseTime=True&loc=Local"
var err error
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("数据路连接失败" + err.Error())
}
DB.AutoMigrate(&User{})
}
func CreateUser(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"errpr": err.Error()})
return
}
/*存入数据库*/
if err := DB.Create(&user).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "创建失败"})
return
}
c.JSON(http.StatusCreated, gin.H{
"msg": "create successfully",
"data": user,
})
}
func GetUserList(c *gin.Context) {
var users []User
DB.Find(&users)
c.JSON(http.StatusOK, gin.H{
"data": users,
})
}
func UpdateUser(c *gin.Context) {
id := c.Param("id")
var user User
if err := DB.First(&user, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "用户存在"})
return
}
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"err": err.Error()})
return
}
DB.Save(&user)
c.JSON(http.StatusOK, gin.H{
"msg": "update successfully",
"data": user,
})
}
func DeleteUser(c *gin.Context) {
id := c.Param("id")
if err := DB.Delete(&User{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除失败"})
return
}
c.JSON(http.StatusOK, gin.H{
"msg": "delete successfully",
})
}
func main() {
initDB()
r := gin.Default()
v1 := r.Group("/v1")
{
v1.POST("/users", CreateUser)
v1.GET("/users", GetUserList)
v1.PUT("/users/:id", UpdateUser)
v1.DELETE("/users/:id", DeleteUser)
}
r.Run(":8080")
}
可以用APIFOX测试接口
上面的代码虽然能跑,但是emm差点意思,没有一点架构的味道。










