四、Gorm
GORM是Golang目前比较人们的数据库ORM操作库,对开发者也比较友好,使用非常简单,使用上主要就是把struct类型和数据库表记录进行映射,操作数据库的时候不需要直接手写Sql代码,这里主要介绍MYSQL数据库。
GORM库github地址:github.com/go-gorm/gor…
一、基础使用
下面将以使用Gorm连接MySQL为例,看下Gorm的使用。
Gorm约定 Gorm在使用时有以下约定:
Gorm使用名为ID的字段作为主键
如果没有TableName函数,使用结构体的蛇形复数作为表名
字段名的蛇形作为列表
使用CreatedAt、UpdatedAt字段作为创建更新时间
1.1 安装依赖
需要两个依赖:MySQL和Gorm。
// 安装MySQL依赖
`go get -u gorm.io/driver/mysql`
// 安装Gorm依赖
`go get -u gorm.io/gorm`
在实际执行的时候,安装MySQL后,会自动安装Gorm。
1.2 使用方法
Gorm操作MySQL的步骤如下:
先用struct定义一个模型,字段与数据库的字段一致 使用Gorm连接数据库 使用Gorm操作数据库 既然是操作数据库,那么就需要有数据库表,下面以product表为例,先看下表结构
//导入连接MySQL的依赖包
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
CREATE TABLE `product` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`code` varchar(50) DEFAULT NULL,
`price` int(11) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
1.3 创建模型
创建一个Product类型的结构体,结构体里的字段和MySQL的product表字段一一对应。
type Product struct {
ID unit
Code string
Price uint
}
struct的字段和MySQL表里的字段是一样对应的,包括大小写,如果MySQL的字段是小写的话,比如code字段不一致(如数据中叫product_code),那么在结构体里需要加上关系对应,对应的写法为:
Code string `gorm:"column:product_code"`
如果是主键的话可以写成:
ID string `gorm:"primarykey"`
通过使用default标签为字段定义默认值:
Name string `gorm:"default:galeone"`
创建表名对应关系,刚才创建的结构体Product和MySQL里的表名并不一致,所以要创建一个对应关系,创建方式为为Product结构体增加一个TableName方法
func (p Product) TableName() string {
return "product"
}
1.4 连接数据库
gorm支持多种数据库,这里主要介绍mysql,连接mysql主要有两个步骤:
- 配置DSN (Data Source Name)
- 使用gorm.Open连接数据库
配置DSN (Data Source Name)
gorm库使用dsn作为连接数据库的参数,dsn翻译过来就叫数据源名称,用来描述数据库连接信息。一般都包含数据库连接地址,账号,密码之类的信息。
DSN格式:
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", username, password, host, port, dbname)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database, error=" + err.Error())
}
gorm调试模式
为了方便调试,了解gorm操作到底执行了怎么样的sql语句,开发的时候需要打开调试日志,这样gorm会打印出执行的每一条sql语句。
使用Debug函数执行查询即可
例子:
result := db.Debug().Where("username = ?", "tizi365").First(&u)
gorm连接池
在高并发实践中,为了提高数据库连接的使用率,避免重复建立数据库连接带来的性能消耗,会经常使用数据库连接池技术来维护数据库连接。
gorm自带了数据库连接池使用非常简单只要设置下数据库连接池参数即可。
数据库连接池使用例子:
定义tools包,负责数据库初始化工作
1.5创建数据
通过Create函数创建数据,先初始化一个结构体,然后调用Create函数。
p := Product{Code: "D42", Price: 100}
if err := db.Create(&p).Error; err != nil {
fmt.Println("插入失败", err)
}
Create函数还支持创建多条数据,如果是多条的话,需要传一个数组参数。
products := []*Product{{Code: "D42", Price: 100},{Code: "D43", Price: 200}}
if err := db.Create(&products).Error; err != nil {
fmt.Println("插入失败", err)
}
提示:如果gorm设置了数据库连接池,那么每次执行数据库查询的时候都会从数据库连接池申请一个数据库连接,那么上述代码必须使用数据库事务,确保插入数据和查询自增id两条sql语句是在同一个数据库连接下执行,否则在高并发场景下,可能会查询不到自增id,或者查询到错误的id。
1.6 查询数据
gorm查询数据本质上就是提供一组函数,帮我们快速拼接sql语句,尽量减少编写sql语句的工作量。
gorm查询结果我们一般都是保存到结构体(struct)变量,所以在执行查询操作之前需要根据自己想要查询的数据定义结构体类型。
提示:gorm库是协程安全的,gorm提供的函数可以并发的在多个协程安全的执行。
var product Product
db.First(&product, 1)
这段代码生成的SQL如下:
SELECT * FROM `product` WHERE `product`.`code` = 1 ORDER BY `product`.`code` LIMIT 1
如果要按照指定的条件查询的话,则需要传条件参数:
var product Product
db.First(&product, "Code = ?", "D42")
或者使用Where函数:
db.Where("Code = ?", "D42").First(&product)
这两种方法的功能是一样的,生成的SQL如下:
SELECT * FROM `product` WHERE Code = 'D42' ORDER BY `product`.`code` LIMIT 1
First函数只能查询一条记录,如果要查多条的话,需要使用Find函数。
products := make([]Product, 0)
db.Find(&products, "price = ?", 2)
使用IN查询:
products := make([]*Product, 0)
db.Where("price IN ?", []uint{100, 2}).Find(&products)
使用like查询:
products := make([]*Product, 0)
db.Where("code like ?", "%D%").Find(&products)
使用结构体查询:
db.Where(&Product{Code: "D43", Price: 0}).Find(&products)
生成的SQL为:
SELECT * FROM `product` WHERE `product`.`code` = 'D43'
注意:
使用First查询时,如果查询不到数据会返回ErrRecordNotFound
使用Find查询时,查询不到数据不会返回错误
使用结构体作为查询条件时,Gorm只会查询非零值字段,也就是0、''、false或其他零值字段将被忽略,可以使用Map或Select来替换
1.7 更新数据
Gorm更新数据是通过Update函数操作的,Update函数需要传入要更新的字段和对应的值。
需要通过Model函数来传入要更新的模型,主要是用来确定表名,也可以使用Table函数来确定表名。
db.Model(&Product{}).Update("Price", 200)
如果要更新多个字段的话,可以使用Updates函数,该函数需要传入一个结构体或map:
db.Model(&Product{}).Updates(Product{Code: "D43", Price: 1})
如果使用结构体作为参数的话,可以省略Model这一部分,因为Gorm也会从参数的结构体中查询相关表名。
注意在使用结构体时,不会更新零值,如果要更新的话,需要使用map:
db.Model(&Product{}).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})
如果是按条件更新的话,可以通过Where函数传入条件:
db.Model(&Product{}).Where("Code = ?", "D42").Update("Price", 200)
更新选定字段,只会更新Select函数里的字段,其他字段会被忽略:
db.Model(&Product{}).Select("price").Updates(map[string]interface{}{"Price": 200, "Code": "F42"})
表达式更新,如果要实现update product set price = price * 2 * 100的话,实现方式如下:
db.Model(&Product{}).Update("price", gorm.Expr("price * ? * ?", 2, 100))
1.8 删除数据
删除数据又分为物理删除和逻辑删除。
物理删除
删除数据使用的是Delete函数,Delete函数需要传入一个结构体以及参数,如果参数是整形的话,会按主键进行删除。
db.Delete(&Product{}, 1)
db.Delete(&Product{}, "Code = ?", "F42")
db.Delete(&Product{}, []int{1,2,3})
db.Where("code like ?", "%F%").Delete(Product{})
如果想按条件删除的话,需要使用Where函数:
db.Where("Code = ?", "F42").Delete(&Product{})
软删除
Gorm提供了软删除的能力,需要在结构体中定义一个Deleted字段,此时再调用Delete删除函数,则会生成update语句,并将deleted字段赋值为当前删除时间。
type Product struct {
ID uint
Code string
Price uint
Deleted gorm.DeletedAt
}
再次执行删除操作:
db.Delete(&Product{}, 1)
生成的sql为:
UPDATE `product` SET `deleted`='2023-01-30 22:22:22.202' WHERE `product`.`id` = 1 AND `product`.`deleted` IS NULL
查询被软删除的操作,要使用Unscoped函数:
db.Unscoped().First(&product, 2)
有了DeleteAt字段后,删除操作已经变成了更新操作,那么想要物理删除怎么办?也是使用Unscoped函数:
db.Unscoped().Delete(&Product{}, 1)
1.9 Gorm事务
Gorm提供了Begin、Commit、Rollback方法用于使用事务。
// 开启事务
tx := db.Begin()
if err := tx.Create(&Product{Code: "D32", Price: 100}).Error; err != nil {
// 出现错误回滚
tx.Rollback()
return
}
if err := tx.Create(&Product{Code: "D33", Price: 100}).Error; err != nil {
// 出现错误回滚
tx.Rollback()
return
}
// 提交事务
tx.Commit()
注意:在开启事务后,调用增删改操作是应该使用开启事务返回的tx而不是db。
Gorm还提供了Transaction函数用于自定提交事务,避免用户漏写Commit、Rollback。
if err = db.Transaction(func(tx *gorm.DB) error {
if err := db.Create(&Product{Code: "D55", Price: 100}).Error; err != nil {
return err
}
if err := db.Create(&Product{Code: "D56", Price: 100}).Error; err != nil {
return err
}
return nil
}); err != nil {
return
}
这种写法,当出现错误时会自动进行Rollback,当正常执行时会自动Commit。
1.10 GORM的关联查询
GORM的关联查询(又叫连表查询)中的属于关系是一对一关联关系的一种,通常用于描述一个Model属于另外一个Model。
例子
存在一个users表和profiles表:
- users - 用户表
- profiles - 用户个性化信息表
他们之间存在一对一关系,每一个用户都有自己的个性化数据,那么可以说每一条profiles记录都属于某个用户。
// 用户表 - 下面使用go struct表示表结构
type User struct {
// 继承gorm的基础Model,里面默认定义了ID、CreatedAt、UpdatedAt、DeletedAt 4个字段
gorm.Model
Name string
}
// 个性化信息表
type Profile struct {
gorm.Model
UserID uint // 外键
// 定义user属性关联users表,默认情况使用 类型名 + ID 组成外键名,在这里UserID属性就是外键
User User
Name string
}
1.10.1外键
在关联查询中必须包含外键,默认gorm使用(关联属性类型 + 主键)组成外键名,如上面的例子User + ID 组成UserID,UserID就作为Profile的外键。
也可以通过下面方式修改外键
type Profile struct {
gorm.Model
Name string
User User `gorm:"foreignkey:UserRefer"` //使用 UserRefer 作为外键
UserRefer uint // 外键
}
1.10.2关联外键
在连表操作中,除了外键,还需要一个关联外键组成一对才能完成连表,例如上面的例子,Profile中UserID属性作为外键,它和User中的ID进行关联,这里User的ID就是关联外键。
默认GORM使用主键作为关联外键,所以上面的User使用ID作为关联外键。
也可以自定义关联外键
type User struct {
gorm.Model
Refer string // 关联外键
Name string
}
type Profile struct {
gorm.Model
Name string
User User `gorm:"references:Refer"` // 使用 Refer 作为关联外键
UserRefer string
}
1.10.3属于关联查询例子
profile := Profile{}
// 查询用户个性数据
//自动生成sql: SELECT * FROM `profiles` WHERE id = 1 AND `profiles`.`deleted_at` IS NULL LIMIT 1
db.Where("id = ?", 1).Take(&profile)
fmt.Println(profile)
user := User{}
// 通过Profile关联查询user数据, 查询结果保存到user变量
db.Model(&profile).Association("User").Find(&user)
fmt.Println(user)
// 自动生成sql: SELECT * FROM `users` WHERE `users`.`id` = 1 // 1 就是user的 ID,已经自动关联
1.11 GORM 关联查询 - 一对多关系(Has Many)
GORM的关联查询(又叫连表查询)中的Has Many关系是一对多关联关系,通常用于描述一个Model拥有多个Model。
例子
一个用户拥有多张信用卡,下面以Go Struct表示表结构
// 用户
type User struct {
// 继承gorm的基础Model,里面默认定义了ID、CreatedAt、UpdatedAt、DeletedAt 4个字段
gorm.Model
CreditCards []CreditCard // 一对多关联属性,表示多张信用卡
}
// 信用卡
type CreditCard struct {
gorm.Model
Number string // 卡号
UserID uint // 默认外键, 用户Id
}
1.11.1 外键
默认情况下,GORM使用持有关联属性的 类型名 + 主键ID 作为外键名。
如上例,User使用User + ID = UserID 作为外键名。
自定义外键
type User struct {
gorm.Model
// 通过标签,将外键定义为:UserRefer
CreditCards []CreditCard `gorm:"foreignkey:UserRefer"`
}
type CreditCard struct {
gorm.Model
Number string
UserRefer uint // 新定义的外键名
}
1.11.2关联外键
外键和关联外键都是成对出现的,默认情况GORM使用主键ID,作为关联外键。
主键ID,默认为ID,如上面的例子,使用User的ID作为关联外键
自定义关联外键
type User struct {
gorm.Model
MemberNumber string // 关联外键字段
// 使用references定义关联外键名
CreditCards []CreditCard `gorm:"foreignkey:UserMemberNumber;references:MemberNumber"`
}
type CreditCard struct {
gorm.Model
Number string
UserMemberNumber string // 外键字段
}
1.11.3一对多关联查询例子
user := User{}
// 查询用户数据
//自动生成sql: SELECT * FROM `users` WHERE (username = 'tizi365') LIMIT 1
db.Where("username = ?", "tizi365").First(&user)
fmt.Println(user)
//自动生成SQL: SELECT * FROM emails WHERE user_id = 111; // 111 是user的主键ID值
// 关联查询的结果,保存到user.CreditCard属性
db.Model(&user).Association("CreditCard").Find(&user.CreditCard)