Go框架三件套详解——Gorm篇|青训营

158 阅读18分钟

Go框架三件套详解————Gorm篇

1.三件套介绍

Gorm是一个已经迭代了10年+的功能强大的ORM框架,在字节内部被广泛使用并且拥有非常丰富的开源扩展。

Kitex是字节内部的Golang微服务RPC框架,具有高性能、强可扩展的主要特点,支持多协议并且拥有丰富的开源扩展。

Hertz是字节内部的HTTP框架,参考了其他开源框架的优势,结合字节跳动内部的需求,具有高易用性、高性能、高扩展性特点。

ORM全称是:Object Relational Mapping(对象关系映射),其主要作用是在编程中,把面向对象的概念跟数据库中表的概念对应起来。

举例来说就是,我定义一个对象,那就对应着一张表,这个对象的实例,就对应着表中的一条记录。

2.三件套使用

(1)Gorm

通过如下命令安装 GORM:

go get -u gorm.io/gorm

GORM 框架使用套路:

  1. 定义结构体映射表结构:Product 结构体在 GORM 中称作「模型」,一个模型对应一张数据库表,一个结构体实例对象对应一条数据库表记录。
  2. 连接数据库:GORM 使用 gorm.Open 方法与数据库建立连接,连接建立好后,才能对数据库进行 CRUD 操作。
  3. 自动迁移表结构:调用 db.AutoMigrate 方法能够自动完成在数据库中创建 Product 结构体所映射的数据库表,并且,当 Product 结构体字段有变更,再次执行迁移代码,GORM 会自动对表结构进行调整,非常方便。不过,不推荐在生产环境项目中使用此功能。因为数据库表操作都是高风险操作,一定要经过多人 Review 并审核通过,才能执行操作。GORM 自动迁移功能虽然理论上不会出现问题,但线上操作谨慎为妙,只有在小项目或数据不那么重要的项目中使用比较合适。
  4. CRUD 操作:迁移好数据库后,就有了数据库表,可以进行 CRUD 操作了

GORM 的详细使用:

声明模型

GORM 使用模型(Model)来映射一张数据库表,模型是标准的 Go struct,由 Go 的基本数据类型、实现了 ScannerValuer 接口的自定义类型及其指针或别名组成。

type User struct {
    ID           uint
    Name         string
    Email        *string
    Age          uint8
    Birthday     *time.Time
    MemberNumber sql.NullString
    ActivatedAt  sql.NullTime
    CreatedAt    time.Time
    UpdatedAt    time.Time
}

我们可以使用 gorm 字段标签来控制数据库表字段的类型、列大小、默认值等属性,比如使用 column 字段标签来映射数据库中字段名称。

type User struct {
    gorm.Model
    Name         string         `gorm:"column:name"`
    Email        *string        `gorm:"column:email"`
    Age          uint8          `gorm:"column:age"`
    Birthday     *time.Time     `gorm:"column:birthday"`
    MemberNumber sql.NullString `gorm:"column:member_number"`
    ActivatedAt  sql.NullTime   `gorm:"column:activated_at"`
}

func (u *User) TableName() string {
    return "user"
}

在不指定 column 字段标签情况下,GORM 默认使用字段名的 snake_case 作为列名。

GORM 默认使用结构体名的 snake_cases 作为表名,为结构体实现 TableName 方法可以自定义表名。

User 结构体中有一个嵌套的结构体 gorm.Model,它是 GORM 默认提供的一个模型 struct,用来简化用户模型定义。

GORM 倾向于约定优于配置,默认情况下,使用 ID 作为主键,使用 CreatedAtUpdatedAtDeletedAt 字段追踪记录的创建、更新、删除时间。而这几个字段就定义在 gorm.Model 中:

由于我们不使用自动迁移功能,所以需要手动编写 SQL 语句来创建 user 数据库表结构:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT '' COMMENT '用户名',
  `email` varchar(255) NOT NULL DEFAULT '' COMMENT '邮箱',
  `age` tinyint(4) NOT NULL DEFAULT '0' COMMENT '年龄',
  `birthday` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '生日',
  `member_number` varchar(50) COMMENT '成员编号',
  `activated_at` datetime COMMENT '激活时间',
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `deleted_at` datetime,
  PRIMARY KEY (`id`),
  UNIQUE KEY `u_email` (`email`),
  INDEX `idx_deleted_at`(`deleted_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';

数据库中字段类型要跟 Go 中模型的字段类型相对应,不兼容的类型可能导致错误。

连接数据库

GORM 是通过驱动的方式来链接数据库的,目前支持 Mysql、SQLSever、PostgreSQL、SQLite。如果需要链接其他类型的数据库,可以 复用/自行 开发驱动。

这里使用最常见的 MySQL 作为示例,来讲解 GORM 如何连接到数据库

我们需要连接 MySQL 需要使用 mysql 驱动。

在 GORM 中定义了 gorm.Dialector 接口来规范数据库连接操作,实现了此接口的程序我们将其称为「驱动」。针对每种数据库,都有对应的驱动,驱动是独立于 GORM 库的,需要单独引入。

连接 MySQL 数据库的代码如下:

package main

import (
    "fmt"

    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

func ConnectMySQL(host, port, user, pass, dbname string) (*gorm.DB, error) {
    dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
        user, pass, host, port, dbname)
    return gorm.Open(mysql.Open(dsn), &gorm.Config{})
}

首先,mysql.Open 接收一个字符串 dsn,DSN 全称 Data Source Name,翻译过来叫数据库源名称。DSN 定义了一个数据库的连接信息,包含用户名、密码、数据库 IP、数据库端口、数据库字符集、数据库时区等信息。DSN 遵循特定格式:

username:password@protocol(address)/dbname?param=value

通过 DSN 所包含的信息,mysql 驱动就能够知道以什么方式连接到 MySQL 数据库了。

mysql.Open 返回的正是一个 gorm.Dialector 对象,将其传递给 gorm.Open 方法后,我们将得到 *gorm.DB 对象,这个对象可以用来操作数据库。

GORM 使用 database/sql 来维护数据库连接池,对于连接池我们可以设置如下几个参数:

func SetConnect(db *gorm.DB) error {
    sqlDB, err := db.DB()
    if err != nil {
        return err
    }

    sqlDB.SetMaxOpenConns(100)                 // 设置数据库的最大打开连接数
    sqlDB.SetMaxIdleConns(100)                 // 设置最大空闲连接数
    sqlDB.SetConnMaxLifetime(10 * time.Second) // 设置空闲连接最大存活时间
    return nil
}

现在,数据库连接已经建立,我们可以对数据库进行操作了。

这是一个使用了自动迁移的简单的例子

package main

import (
    "fmt"

    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
)

// 定义数据模型
type Product struct {
    ID    uint   `gorm:"primary_key"`
    Name  string `gorm:"size:255"`
    Price uint
}

func main() {
    // 建立数据库连接
    db, err := gorm.Open("mysql", "root:password@/dbname?charset=utf8&parseTime=True&loc=Local")
    if err != nil {
        panic("连接数据库失败")
    }
    defer db.Close()

    // 自动迁移数据库结构
    db.AutoMigrate(&Product{})//自动迁移数据库结构,即自动根据 Product 结构体创建数据表。

    // 创建一条数据
    db.Create(&Product{Name: "面包", Price: 3})

    // 查询所有数据
    var products []Product
    db.Find(&products)
    fmt.Println("所有产品:", products)

    // 更新一条数据
    db.Model(&Product{}).Where("name = ?", "面包").Update("price", 4)

    // 删除一条数据
    db.Where("name = ?", "面包").Delete(&Product{})
}

下面是关于每个用法的详细说明

创建

可以使用 Create 方法创建一条数据库记录:

now := time.Now()
email := "u1@jianghushinian.com"
user := User{Name: "user1", Email: &email, Age: 18, Birthday: &now}

// INSERT INTO `user` (`created_at`,`updated_at`,`deleted_at`,`name`,`email`,`age`,`birthday`,`member_number`,`activated_at`) VALUES ('2023-05-22 22:14:47.814','2023-05-22 22:14:47.814',NULL,'user1','u1@jianghushinian.com',18,'2023-05-22 22:14:47.812',NULL,NULL)
result := db.Create(&user) // 通过数据的指针来创建

fmt.Printf("user: %+v\n", user) // user.ID 自动填充
fmt.Printf("affected rows: %d\n", result.RowsAffected)
fmt.Printf("error: %v\n", result.Error)

要创建记录,我们需要先实例化 User 对象,然后将其指针传递给 db.Create 方法。

db.Create 方法执行完成后,依然返回一个 *gorm.DB 对象。

user.ID 会被自动填充为创建数据库记录后返回的真实值。

result.RowsAffected 可以拿到此次操作影响行数。

result.Error 可以知道执行 SQL 是否出错。

在这里,我将 db.Create(&user) 这句 ORM 代码所生成的原生 SQL 语句放在了注释中,方便你对比学习。并且,之后的示例中我也会这样做。

Create 方法不仅支持创建单条记录,它同样支持批量操作,一次创建多条记录:

now = time.Now()
email2 := "u2@jianghushinian.com"
email3 := "u3@jianghushinian.com"
users := []User{
    {Name: "user2", Email: &email2, Age: 19, Birthday: &now},
    {Name: "user3", Email: &email3, Age: 20, Birthday: &now},
}

// INSERT INTO `user` (`created_at`,`updated_at`,`deleted_at`,`name`,`email`,`age`,`birthday`,`member_number`,`activated_at`) VALUES ('2023-05-22 22:14:47.834','2023-05-22 22:14:47.834',NULL,'user2','u2@jianghushinian.com',19,'2023-05-22 22:14:47.833',NULL,NULL),('2023-05-22 22:14:47.834','2023-05-22 22:14:47.834',NULL,'user3','u3@jianghushinian.com',20,'2023-05-22 22:14:47.833',NULL,NULL)
result = db.Create(&users)

代码主要逻辑不变,只需要将单个的 User 实例换成 User 切片即可。GORM 会使用一条 SQL 语句完成批量创建记录。

查询

查询记录是我们在日常开发中使用最多的场景了,GORM 提供了多种方法来支持 SQL 查询操作。

例如,如果你想查询第一条名称为 "Apple" 的产品数据,你可以这样写:

var product Product
db.First(&product, "name = ?", "Apple")

这里的参数是一个模型结构体的指针,第二个参数是查询条件,第三个参数是条件的值。

也可以使用链式查询来实现这个操作,如下:

var product Product
db.Where("name = ?", "Apple").First(&product)

⚠️ 在链式调用的时候,需要注意~!

使用时需要注意,如果查询不到符合条件的数据,First 方法将不会返回错误,而是返回一个空结构体。所以需要在使用时自己判断是否查询到数据。

使用 First 方法可以查询第一条记录:

var user User
// SELECT * FROM `user` WHERE `user`.`deleted_at` IS NULL ORDER BY `user`.`id` LIMIT 1
result := db.First(&user)

First 方法接收一个模型指针,通过模型的 TableName 方法则可以拿到数据库表名,然后使用 SELECT * 语句从数据库中查询记录。

根据生成的 SQL 可以发现 First 方法查询数据默认根据主键 ID 升序排序,并且只会过滤删除时间为 NULL 的数据,使用 LIMIT 关键字来限制数据条数。

使用 Last 方法可以查询最后一条数据,排序规则为主键 ID 降序:

var lastUser User
// SELECT * FROM `user` WHERE `user`.`deleted_at` IS NULL ORDER BY `user`.`id` DESC LIMIT 1
result = db.Last(&lastUser)

使用 Where 方法可以增加查询条件:

var users []User
// SELECT * FROM `user` WHERE name != 'unknown' AND `user`.`deleted_at` IS NULL
result = db.Where("name != ?", "unknown").Find(&users)

这里不再查询单条数据,所以改用 Find 方法来查询所有符合条件的记录。

以上介绍的几种查询方法,都是通过 SELECT * 查询数据库表中的全部字段,我们可以使用 Select 方法指定需要查询的字段:

var user2 User
// SELECT `name`,`age` FROM `user` WHERE `user`.`deleted_at` IS NULL ORDER BY `user`.`id` LIMIT 1
result = db.Select("name", "age").First(&user2)

使用 Order 方法可以自定义排序规则:

var users2 []User
// SELECT * FROM `user` WHERE `user`.`deleted_at` IS NULL ORDER BY id desc
result = db.Order("id desc").Find(&users2)

GORM 也提供了对 Limit & Offset 的支持:

var users3 []User
// SELECT * FROM `user` WHERE `user`.`deleted_at` IS NULL LIMIT 2 OFFSET 1
result = db.Limit(2).Offset(1).Find(&users3)

使用 -1 可以取消 Limit & Offset 的限制条件:

var users4 []User
var users5 []User
// SELECT * FROM `user` WHERE `user`.`deleted_at` IS NULL LIMIT 2 OFFSET 1; (users4)
// SELECT * FROM `user` WHERE `user`.`deleted_at` IS NULL; (users5)
result = db.Limit(2).Offset(1).Find(&users4).Limit(-1).Offset(-1).Find(&users5)

这段代码会执行两条查询语句,之所以能够采用这种「链式调用」的方式执行多条 SQL,是因为每个方法返回的都是 *gorm.DB 对象,这也是一种编程技巧。

使用 Count 方法可以统计记录条数:

var count int64
// SELECT count(*) FROM `user` WHERE `user`.`deleted_at` IS NULL
result = db.Model(&User{}).Count(&count)

有时候遇到比较复杂的业务,我们可能需要使用 SQL 子查询,子查询可以嵌套在另一个查询中,GORM 允许将 *gorm.DB 对象作为参数时生成子查询:

var avgages []float64
// SELECT AVG(age) as avgage FROM `user` WHERE `user`.`deleted_at` IS NULL GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `user` WHERE name LIKE 'user%')
subQuery := db.Select("AVG(age)").Where("name LIKE ?", "user%").Table("user")
result = db.Model(&User{}).Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&avgages)

Having 方法签名如下:

func (db *DB) Having(query interface{}, args ...interface{}) (tx *DB)

第二个参数是一个范型 interface{},所以不仅可以接收字符串,GORM 在判断其类型为 *gorm.DB 时,就会构造一个子查询。

更新

为了讲解更新操作,我们需要先查询一条记录,之后的更新操作都是基于这条被查询出来的 User 对象:

var user User
// SELECT * FROM `user` WHERE `user`.`deleted_at` IS NULL ORDER BY `user`.`id` LIMIT 1
result := db.First(&user)

更新操作只要修改 User 对象的属性,然后调用 db.Save(&user) 方法即可完成:

user.Name = "John"
user.Age = 20

// UPDATE `user` SET `created_at`='2023-05-22 22:14:47.814',`updated_at`='2023-05-22 22:24:34.201',`deleted_at`=NULL,`name`='John',`email`='u1@jianghushinian.com',`age`=20,`birthday`='2023-05-22 22:14:47.813',`member_number`=NULL,`activated_at`=NULL WHERE `user`.`deleted_at` IS NULL AND `id` = 1
result = db.Save(&user)

在更新操作时,User 对象要保证 ID 属性存在值,不然就变成了创建操作。

Save 方法会保存所有的字段,即使字段是对应类型的零值。

除了使用 Save 方法更新所有字段,我们还可以使用 Update 方法更新指定字段:

// UPDATE `user` SET `name`='Jianghushinian',`updated_at`='2023-05-22 22:24:34.215' WHERE `user`.`deleted_at` IS NULL AND `id` = 1
result = db.Model(&user).Update("name", "Jianghushinian")

Update 只能支持更新单个字段,要想更新多个字段,可以使用 Updates 方法:

// UPDATE `user` SET `updated_at`='2023-05-22 22:29:35.19',`name`='JiangHu' WHERE `user`.`deleted_at` IS NULL AND `id` = 1
result = db.Model(&user).Updates(User{Name: "JiangHu", Age: 0})

注意,Updates 方法与 Save 方法有一个很大的不同之处,它只会更新非零值字段。Age 字段为零值,所以不会被更新。

如果一定要更新零值字段,除了可以使用上面的 Save 方法,还可以将 User 结构体换成 map[string]interface{} 类型的 map 对象:

// UPDATE `user` SET `age`=0,`name`='JiangHu',`updated_at`='2023-05-22 22:29:35.623' WHERE `user`.`deleted_at` IS NULL AND `id` = 1
result = db.Model(&user).Updates(map[string]interface{}{"name": "JiangHu", "age": 0})

此外,更新数据时,还可以使用 gorm.Expr 来实现 SQL 表达式:

// UPDATE `user` SET `age`=age + 1,`updated_at`='2023-05-22 22:24:34.219' WHERE `user`.`deleted_at` IS NULL AND `id` = 1
result = db.Model(&user).Update("age", gorm.Expr("age + ?", 1))

gorm.Expr("age + ?", 1) 方法调用会被转换成 age=age + 1 SQL 表达式。

删除

可以使用 Delete 方法删除数记录:

var user User
// UPDATE `user` SET `deleted_at`='2023-05-22 22:46:45.086' WHERE name = 'JiangHu' AND `user`.`deleted_at` IS NULL
result := db.Where("name = ?", "JiangHu").Delete(&user)

对于删除操作,GORM 默认使用逻辑删除策略,不会对记录进行物理删除。

所以 Delete 方法在对数据进行删除时,实际上执行的是 SQL UPDATE 操作,而非 DELETE 操作。

deleted_at 字段更新为当前时间,表示当前数据已删除。这也是为什么前文在讲解查询和更新的时候,生成的 SQL 语句都自动附加了 deleted_at IS NULL Where 条件的原因。

这样就实现了逻辑层面的删除,数据在数据库中仍然存在,但查询和更新的时候会将其过滤掉。

记录被删除后,我们无法通过如下代码直接查询到被逻辑删除的记录:

// SELECT * FROM `user` WHERE name = 'JiangHu' AND `user`.`deleted_at` IS NULL ORDER BY `user`.`id` LIMIT 1
result = db.Where("name = ?", "JiangHu").First(&user)
if err := result.Error; err != nil {
    fmt.Println(err) // record not found
}

这将得到一个错误 record not found

不过,GORM 提供了 Unscoped 方法,可以绕过逻辑删除:

// SELECT * FROM `user` WHERE name = 'JiangHu' ORDER BY `user`.`id` LIMIT 1
result = db.Unscoped().Where("name = ?", "JiangHu").First(&user)

以上代码能够查询出被逻辑删除的记录,生成的 SQL 语句中没有包含 deleted_at IS NULL Where 条件。

对于比较重要的数据,建议使用逻辑删除,这样可以在需要的时候恢复数据,也便于故障追踪。

不过,如果明确想要物理删除一条记录,同理可以使用 Unscoped 方法:

// DELETE FROM `user` WHERE name = 'JiangHu' AND `user`.`id` = 1
result = db.Unscoped().Where("name = ?", "JiangHu").Delete(&user)
事务

GORM 提供了对事务的支持,这在复杂的业务逻辑中是必要的。

要在事务中执行一系列操作,可以使用 Transaction 方法实现:

func TransactionPost(db *gorm.DB) error {
    return db.Transaction(func(tx *gorm.DB) error {
        post := Post{
            Title: "Hello World",
        }
        if err := tx.Create(&post).Error; err != nil {
            return err
        }
        comment := Comment{
            Content: "Hello World",
            PostID:  post.ID,
        }
        if err := tx.Create(&comment).Error; err != nil {
            return err
        }
        return nil
    })
}

Transaction 方法内部的代码,都将在一个事务中被处理。Transaction 方法接收一个函数,其参数为 tx *gorm.DB,事务中所有数据库的操作,都应该使用这个 tx 而非 db

在执行事务的函数中,返回任何错误,整个事务都将被回滚,返回 nil 则事务被提交。

除了使用 Transaction 自动管理事务,我们还可以手动管理事务:

func TransactionPostWithManually(db *gorm.DB) error {
    tx := db.Begin()

    post := Post{
        Title: "Hello World Manually",
    }
    if err := tx.Create(&post).Error; err != nil {
        tx.Rollback()
        return err
    }
    comment := Comment{
        Content: "Hello World Manually",
        PostID:  post.ID,
    }
    if err := tx.Create(&comment).Error; err != nil {
        tx.Rollback()
        return err
    }

    return tx.Commit().Error
}

db.Begin() 用于开启事务,并返回 tx,稍后的事务操作都应使用这个 tx 对象。如果在处理事务的过程中遇到错误,可以使用 tx.Rollback() 回滚事务,如果没有问题,最终可以使用 tx.Commit() 提交事务。

注意:手动事务,事务一旦开始,你就应该使用 tx 处理数据库操作。

钩子(hook)

GORM 还支持 Hook 功能,Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数,用来管理对象的生命周期。

钩子方法的函数签名为 func(*gorm.DB) error,比如以下钩子函数在创建操作之前触发:

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
    u.UUID = uuid.New()
    if u.Name == "admin" {
        return errors.New("invalid name")
    }
    return nil
}

比如我们为 User 模型定义 BeforeCreate 钩子,这样在创建 User 对象前,GORM 会自动调用此函数,完成为 User 对象创建 UUID 以及用户名合法性验证功能。

GORM 支持的钩子函数以及执行时机如下:

钩子函数执行时机
BeforeSave调用 Save 前
AfterSave调用 Save 后
BeforeCreate插入记录前
AfterCreate插入记录后
BeforeUpdate更新记录前
AfterUpdate更新记录后
BeforeDelete删除记录前
AfterDelete删除记录后
AfterFind查询记录后
关联

日常开发中,多数情况下不只是对单表进行操作,还要对存在关联关系的多表进行操作。

这里以一个博客系统最常见的三张表「文章表、评论表、标签表」为例,对 GORM 如何操作关联表进行讲解。

这里涉及最常见的关联关系:一对多和多对多。一篇文章可以有多条评论,所以文章和评论是一对多关系;一篇文章可以存在多个标签,每个标签也可以包含多篇文章,所以文章和标签是多对多关系。

模型定义如下:

type Post struct {
    gorm.Model
    Title    string     `gorm:"column:title"`
    Content  string     `gorm:"column:content"`
    Comments []*Comment `gorm:"foreignKey:PostID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;references:ID"`
    Tags     []*Tag     `gorm:"many2many:post_tags"`
}

func (p *Post) TableName() string {
    return "post"
}

type Comment struct {
    gorm.Model
    Content string `gorm:"column:content"`
    PostID  uint   `gorm:"column:post_id"`
    Post    *Post
}

func (c *Comment) TableName() string {
    return "comment"
}

type Tag struct {
    gorm.Model
    Name string  `gorm:"column:name"`
    Post []*Post `gorm:"many2many:post_tags"`
}

func (t *Tag) TableName() string {
    return "tag"
}

在模型定义中,Post 文章模型使用 CommentsTags 分别保存关联的评论和标签,这两个字段不会保存在数据库表中。

Comments 字段标签使用 foreignKey 来指明 Comments 表中的外键,并使用 constraint 指明了约束条件,references 指明 Comments 表外键引用 Post 表的 ID 字段。

其实现在生产环境中都不再推荐使用外键,各个表之间不再有数据库层面的外键约束,在做 CRUD 操作时全部通过代码层面来进行业务约束。这里为了演示 GORM 的外键和级联操作功能,所以定义了这些结构体标签。

Tags 字段标签使用 many2many 来指明多对多关联表名。

对于 Comment 模型,PostID 字段就是外键,用来保存 Post.IDPost 字段同样不会保存在数据库中,这种做法在 ORM 框架中非常常见。

实战

拿连接mysql为例子,先按照gorm和mysql驱动

go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite

这是一个使用了自动迁移的简单的例子,首先先连接,注意dsn那边要改成自己的mysql,如果打印出来类似下图那就是连接成功了

image-20230820154130835.png

package main
​
import (
    "fmt""gorm.io/driver/mysql"
    "gorm.io/gorm"
)
​
type Product struct {
    ID    uint   `gorm:"primary_key"`
    Name  string `gorm:"size:255"`
    Price uint
}
​
func main() {
    dsn := "用户名:密码@tcp(127.0.0.1:3306)/数据库名字?charset=utf8mb4&parseTime=True&loc=Local"//主机号一般是这个
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
​
    if err != nil {
        panic("连接数据库失败")
    }
    fmt.Println(db, err)
​
}

连接成功之后我们就来进行操作吧,我就直接自动迁移了。

db.AutoMigrate(&Product{}) //自动迁移数据库结构,即自动根据 Product 结构体创建数据表。
// 创建一条数据
db.Create(&Product{Name: "面包", Price: 3})

创建之后可以看到数据库新建了一个product的表

image-20230820155605821.png

查询操作,更新和删除

    // 查询所有数据
    var products []Product
    db.Find(&products)
    fmt.Println("所有产品:", products)
//更新一条数据
 db.Model(&Product{}).Where("name = ?", "面包").Update("price", 4)
 // 删除一条数据
db.Where("name = ?", "面包").Delete(&Product{})

下面是全部代码

package main
​
import (
    "fmt""gorm.io/driver/mysql"
    "gorm.io/gorm"
)
​
type Product struct {
    ID    uint   `gorm:"primary_key"`
    Name  string `gorm:"size:255"`
    Price uint
}
​
func main() {
    dsn := "root:123456@tcp(127.0.0.1:3306)/douyin?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
​
    if err != nil {
        panic("连接数据库失败")
    }
    fmt.Println(db, err)
​
    db.AutoMigrate(&Product{}) //自动迁移数据库结构,即自动根据 Product 结构体创建数据表。
    // 创建一条数据
    db.Create(&Product{Name: "面包", Price: 3})
    // 查询所有数据
    var products []Product
    db.Find(&products)
    fmt.Println("所有产品:", products)
    //更新一条数据
    db.Model(&Product{}).Where("name = ?", "面包").Update("price", 4)
    // 删除一条数据
    db.Where("name = ?", "面包").Delete(&Product{})
}
​

下面是一个结果,中途因为我执行了太多次,所以面包生成了很多,然后中间页生成过草莓,最后执行删除面包数据的时候把所有的面包都删除了只剩草莓了。

image-20230820160759009.png

image-20230820160817557.png