likes
comments
collection
share

10分钟了解Go的CRUD基础——GORM + MySQL

作者站长头像
站长
· 阅读数 47

Go学习系列 Part7 了解Go的CRUD——GORM + MySQL

关于GORM

GORM 是一个流行的 Go 语言 ORM(对象关系映射)库,它允许开发者使用 Go 结构体来表示数据库中的表,并使用 Go 的方法来执行常见的数据库操作,如查询、插入、更新和删除。

特性

  • 全功能 ORM
  • 关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
  • Create,Save,Update,Delete,Find 中钩子方法
  • 支持 PreloadJoins 的预加载
  • 事务,嵌套事务,Save Point,Rollback To Saved Point
  • Context、预编译模式、DryRun 模式
  • 批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
  • SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询
  • 复合主键,索引,约束
  • Auto Migration
  • 自定义 Logger
  • 灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
  • 每个特性都经过了测试的重重考验
  • 开发者友好

官网gorm.io/zh_CN/docs/

安装

  1. 首先确保项目已经初识化了Go Modules,在项目目录下运行:go mod init <your-module-name>
  2. 安装GORM:go get -u gorm.io/gorm
  3. 安装对应数据库驱动,支持多种数据库驱动,如MySQL、PostgreSQL、SQLite等, 🌰:go get -u gorm.io/driver/mysql

连接MySQL数据库

在Go文件里引入GORM包和MySQL驱动

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

提供数据库的DSN字符串,使用mysql.Open()方法 和 gorm.Open()方法连接到MySQL数据库。

dsn := "username:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
// 将 username password dbname 替换为实际需要的
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})  
if err != nil {  
    // 处理错误  
    panic("failed to connect database")  
}

GORM 模型

gorm.Module是GORM提供的一个预定义的结构体,包含常用字段:

type Model struct {
    ID unit `gorm:"primaryKey"` // 记录唯一标识(主键)
    Create time.Time // 创建记录自动设置当前时间
    UpdateAt time.Time // 修改记录自动更新当前时间
    DeleteAt gorm.DeleteAt `gorm:"index"` // 软删除,记录标识已删除
}

约定

  1. 主键:GORM 使用一个名为ID 的字段作为每个模型的默认主键。
  2. 表名:默认情况下,GORM 将结构体名称转换为 snake_case 并为表名加上复数形式。 例如,一个 User 结构体在数据库中的表名变为 users  可以通过实现Tabler接口更改默认表名 (见后续CRUD操作) 也可以通过Table()方法,临时指定表名 (见后续CRUD操作)
  3. 列名:GORM 自动将结构体字段名称转换为 snake_case 作为数据库中的列名。
  4. 时间戳字段:GORM使用字段 CreatedAt 和 UpdatedAt 来自动跟踪记录的创建和更新时间。

更多可查看约定文档

字段级权限限制

  1. gorm:"<-:create":允许读、创建
  2. gorm:"<-:update":允许读、更新
  3. gorm:"<-:":允许读、写
  4. gorm:"<-:false":允许读,禁止写
  5. gorm:"->":只读
  6. gorm:"->;<-:create":允许读、写
  7. gorm:"->:false;<-:create":仅创建(禁止从db读)
  8. gorm:"-":通过struct读写会忽略该字段
  9. gorm:"-:all":通过struct读写、迁移会忽略该字段
  10. gorm:"-:migration":通过struct迁移会忽略该字段

字段标签

  1. 设置主键:gorm:"primarykey"
  2. 指定列名:gorm:"column:cloumn_name"
  3. 指定值唯一:gorm:"unique"
  4. 创建索引:gorm:"index"
  5. 自动递增,用于整数类型:gorm:"autoIncrement"
  6. 设置字符串/字节数组大小:gorm:"size:[number]"
  7. 指定精度和小数位数:gorm:"precision:[number],scale:[number]"用于decimal类型
  8. 默认值:gorm:"default:[value]"
  9. 忽略该字段,不映射到数据库:gorm:"-"

db.Model

用于指定后续数据库操作的目标模型或表,不执行任何数据库操作,而是设置一个上下文, 后续接一个GORM的方法。

常搭配使用:

  1. 更新操作:UpdateUpdates
  2. 删除操作:Delete
  3. 查询操作
  4. 关联操作

创建 Create

创建新表

GORM不需要直接编写SQL创建表,可以通过使用AutoMigrate方法,根据定义的模型自动创建或修改表。 如果需要使用SQL语句建表,可以使用Exec方法。

🌰:

type Product struc {
    gorm.Model
    Code string
    Price unit
}
// 修改默认表名
func (Product) TableName() string {
    return "product"
}
func main() {
    dsn := "username:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})  
    if err != nil {  
        // 处理错误  
        panic("failed to connect database")  
    }
    
    // 创建表或修改表,原来默认表名为:products,实现了Table接口后,表名为product
    db.AutoMigrate(&Product{})
    // 根据Product的字段创建‘tb_product'表
    db.Table("tb_product").AutoMigrate(&Product{})
    
    // 使用SQL语句建表
    db.Exec("CREATE TABLE product_copy (" +  
        "id INT AUTO_INCREMENT PRIMARY KEY," +  
        "created_at DATETIME," +  
        "updated_at DATETIME," +  
        "deleted_at DATETIME," +  
        "code VARCHAR(255) NOT NULL," +  
        "price DECIMAL(10,2) NOT NULL" +  
        ")")
}

插入记录

使用Create()方法,其需要传入数据的指针。 使用Select()搭配,可以为指定字段赋值。 使用Omit()方法,能忽略指定字符,不给予赋值。

p := Product{Code: "001", Name: "Apple", Price: 3, Count: 200}
result := db.Create(&p)
fmt.Println("插入条数:", result.RowsAffected) // 1
fmt.Println("返回 Error:", result.Error) // <nil>
fmt.Println("Product ID:", p.ID)

// 搭配Select
p2 := Product{Code: "002", Name: "Banne", Price: 2, Count: 200}
db.Select("Code", "Name").Create(&p2) // 数据库里Price 和Count 则没有赋值
// 等同于
db.Omit("Code", "Name").Create(&p2) // 数据库里Code、 Name 则没有赋值,只有,Price 和 Count

批量插入,将切片传给Create方法。当这些记录可以被分割成多批次时,GORM开启一个事务处理。 可以通过CreateInBatches方法指定批量插入的批次大小。

// 插入多条记录
// 存放的是指向Product类型的指针
productArr := []*Product{
    {Code: "003", Name: "Orange", Price: 5, Count: 34},
    {Code: "004", Name: "Pear", Price: 6, Count: 55},
    {Code: "005", Name: "Lemon", Price: 8, Count: 23},
    {Code: "006", Name: "Watermelon", Price: 3.99, Count: 2},
}
db.Create(productArr)
// 指定每批次大小
db.CreateInBatches(productArr, 2)

当然我们也可以在初始化GORM实例的时候,提前设定Batche的大小,避免后续频繁使用CreateInBatches

    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
+      CreateBatchSize: 1000
    })

更新

使用Save()保存所有字段,即使值为零。但不能与Model()方法一起使用。如果保存的数据里不包含主键,则将执行Create()

var product Product
db.First(&product)
fmt.Println(product)
product.Count = 77
// 更新第一条数据
db.Save(&product)
// 没有主键,插入一条新的
db.Save(&Product{Name: "Apple", Count: 89, Code: "001"})

单列更新:使用Update(列名, 值) 多列更新:使用Updates(),支持structmap[string]interface{}struct时,默认只更新非零值字段 选择更新某字段:使用Select(列名).Updates() 选择忽略某字段:使用Omit(列名).Updates()

查询

检索单个对象

  1. First():获取第一条记录,等同于Select * From tableName ORDER BY ID LIMIT 1
  2. Last():获取最后一条记录,等同于Select * From tableName LIMIT 1
  3. Take():获取一条记录,等同于Select * From tableName ORDER BY ID DESC LIMIT 1

有主键时,根据主键进行排序;如果没有主键,则按第一个字段进行排序。 还可以通过内联条件进行WHERE检索

检索全部对象:使用Find(),等同于Select *

条件检索:使用Where()

// 🌰
db.Where("name = ?", "jinzhu").First(&user)
//SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;

需要注意,如果使用struct进行查询时,GORM只会对非零字段进行查询。如果要查询零值,可以使用map,map包含所有键值作为查询条件

指定结构体查询字段:可以通过将相关字段名或dbname传递到Where()来指定要在查询条件中使用的结构中的特定值

内联查询:以Where()类似的方法内联到FirstFind等方法中。

db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)
// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;

NOT条件Not(),使用方法类似Where() OR条件Or(),使用方法类似Where() 选择特定字段Select(),一般搭配Find(),指定查询某些字段,否则默认选择全部。 排序Order()OrderBy(),指定从数据库检索记录时的顺序。 LIMITLimit(),指定要检索的最大记录数。 排序Offset(),指定在开始返回记录之前要跳过的记录数。 Group ByGroup() HavingHaving() JoinJoins(),指定联接条件。 DistinctDistinct(),从模型中选择不同的值。\

删除

使用Delete()删除记录。删除一条记录时,需要指定主键,否则会触发批量删除。 如果模型里包含gorm.DeletedAt字段,则该模型自动获取软删除能力。可以使用Unscoped()搭配Find()查询被软删除的记录;如果搭配Delete(),则会永久删除匹配的记录

删除标志:默认情况下,gorm.Model使用*time.Time作为DeletedAt 的字段类型。 但是软删除插件支持其他数据格式:

type User struct {
    ...
    // 使用Unix时间戳作为删除标识
    DeleteAt soft_delete.DeleteAt
    // 指定毫秒milli或纳秒nano作为值
    DeleteAt soft_delete.DeleteAt `gorm:"softDelete:milli"`
    // 指定 `1` / `0` 作为值
    IsDel soft_delete.DeleteAt `gorm:"softDelete:flag"`
    // 混合模式,使用 `0`,`1`或者unix时间戳来标记数据是否被软删除,并同时可以保存被删除时间
    DeleteAt time.Time
    IsDel soft_delete.DeleteAt `gorm:"softDelete:flag,DeletedAtField:DeletedAt"`
}

原始SQL

执行原始SQL进行查询可以使用Raw()方法,接收一个SQL语句作为字符串参数,以及任何必要参数值(用于参数绑定,防止SQL注入)。查询结束可以使用Scan()将结果映射到相应结构体切片或单个结构体中。

type User struct {  
    ID   uint  
    Name string  
    Age  int  
    // 其他字段...  
} 
var users []User  
db.Raw("SELECT * FROM users WHERE age > ?", 18).Scan(&users) 
转载自:https://juejin.cn/post/7367607757208944674
评论
请登录