一、简介
GORM是一个用于Go语言的ORM(对象关系映射)库,它简化了与数据库交互的过程。GORM支持多种数据库,包括MySQL、PostgreSQL、SQLite和SQL Server等。
1.1 关键特性
- 自动迁移:GORM可以根据结构体自动创建或更新数据库表。
- CRUD操作:提供简单的方法进行增删改查操作。
- 关联关系:支持一对一、一对多、多对多等复杂的关联关系。
- 事务支持:可以方便地在事务中执行多个操作。
- 钩子函数:在创建、更新、删除之前或之后执行自定义逻辑。
二、基础用法
2.1 安装
1、要使用GORM,首先需要安装它
bash
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
2、为了方便测试,我这里本地使用docker启动一个mysql服务,并创建一个库,确保可用
2.2 初始化连接
go
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"testing"
)
var db = getMysqlConnect()
func getMysqlConnect() *gorm.DB {
url := "root:123456@tcp(127.0.0.1:3306)/local?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(url), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
return db
}
2.3 定义模型并自动迁移
定义一个模型,并让GORM为其创建对应的数据表
go
type DwdUserInfo struct {
ID uint `gorm:"primaryKey"`
Username string `gorm:"size:100"`
}
func Test1(t *testing.T) {
// 自动迁移模式,会创建表结构,如果不存在的话。
err := db.AutoMigrate(&DwdUserInfo{}) // 创建的表明是dwd_user_infos
if err != nil {
fmt.Println(err.Error())
return
}
}
如果不想要它建表时加个s
,可以自定义表名,或者关掉这个配置开关
go
// 通过设置 SingularTable: true,GORM 将使用单数形式作为默认的表名。
db, err := gorm.Open(mysql.Open(url), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
})
// 或者实现 TableName() 方法来覆盖默认实现
func (user *DwdUserInfo) TableName() string {
return "user_info"
}
2.4 CRUD 操作
go
func Test2(t *testing.T) {
// 插入数据
db.Create(DwdUserInfo{1, "小明"})
db.Create(DwdUserInfo{123, "小明1"})
db.Create(DwdUserInfo{999, "小明2"})
// 查询数据
var user1 DwdUserInfo
db.First(&user1) // 查询第一条记录
fmt.Println(user1)
var user2 DwdUserInfo
db.First(&user2, 999) // 根据主键查询第一条记录
fmt.Println(user2)
var userList []DwdUserInfo
db.Find(&userList) // 查询所有用户
fmt.Println(userList)
// 条件查询
db.Where("username = ?", "小明2").Find(&userList)
fmt.Println(userList)
// 更新数据
db.Model(&user1).Update("username", "修改后的名字")
db.Model(&user1).Updates(DwdUserInfo{Username: "修改后的名字2"}) // 使用struct更新多个字段,只会更新非零值字段
// 删除数据
db.Delete(&user1) // 读取结构体中的id去删除
var user3 DwdUserInfo
db.Delete(&user3, 999) // 删除id为999的数据
}
三、模型定义
3.1 字段标签
gorm:"primaryKey"
:指定主键。gorm:"unique"
:指定唯一字段。gorm:"uniqueIndex"
:为该字段创建唯一索引。gorm:"not null"
:指定非空字段。gorm:"default:xxx"
:设置默认值。gorm:"column:xxx"
:指定数据库中对应的列名。gorm:"index"
:为该字段创建索引。gorm:"autoIncrement"
:指定字段为自增字段(适用于整数类型的字段)。gorm:"size:xxx"
:指定字段的大小,主要用于字符串或数组类型的字段。
go
type User struct {
ID uint `gorm:"primaryKey;autoIncrement"`
Name string `gorm:"size:100;uniqueIndex:idx_name_and_code"`
Code string `gorm:"size:100;uniqueIndex:idx_name_and_code"`
Email string `gorm:"type:varchar(100);unique"` // type直接指定类型
BirthDate time.Time `gorm:"index"`
IsValid bool `gorm:"default:true"`
CreateTime time.Time `gorm:"autoCreateTime"` // 自动设置创建时间
UpdateTime time.Time `gorm:"autoUpdateTime"` // 自动设置更新时间
DeletedAt gorm.DeletedAt `gorm:"index"` // 软删除字段
TestNotNull string `gorm:"not null"`
TestColumn string `gorm:"column:xxx"`
}
func Test3(t *testing.T) {
err := db.AutoMigrate(&User{})
if err != nil {
fmt.Println(err.Error())
}
}
执行后对应mysql自动创建的表:
sql
CREATE TABLE `user` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`code` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`email` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`birth_date` datetime(3) DEFAULT NULL,
`is_valid` tinyint(1) DEFAULT '1',
`create_time` datetime(3) DEFAULT NULL,
`update_time` datetime(3) DEFAULT NULL,
`deleted_at` datetime(3) DEFAULT NULL,
`test_not_null` longtext COLLATE utf8mb4_general_ci NOT NULL,
`xxx` longtext COLLATE utf8mb4_general_ci,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_name_and_code` (`name`,`code`),
UNIQUE KEY `uni_user_email` (`email`),
KEY `idx_user_birth_date` (`birth_date`),
KEY `idx_user_deleted_at` (`deleted_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
3.2 嵌套结构体
可以通过嵌套结构体来实现字段的重用
go
type Base struct {
ID string `gorm:"primaryKey"`
CreateTime time.Time
UpdateTime time.Time
}
type Product struct {
Base // 嵌入Base结构体,自动包含CreateTime和UpdateTime字段。
gorm.Model // 框架自带的基础结构体
ProductName string `gorm:"type:varchar(100)"`
}
框架自带的基础结构体
go
// Model a basic GoLang struct which includes the following fields: ID, CreatedAt, UpdatedAt, DeletedAt
// It may be embedded into your model or you may build your own model without it
//
// type User struct {
// gorm.Model
// }
type Model struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt DeletedAt `gorm:"index"`
}
3.3关联关系
GORM 支持各种类型的关联,例如一对一、一对多、多对多等。
3.3.1 一对多
比如一个订单有多个对应的订单明细
go
type Order struct {
gorm.Model
OrderDetail []OrderDetail `gorm:"foreignKey:OrderID"`
}
type OrderDetail struct {
gorm.Model
OrderID uint
}
3.3.2 一对一
比如一个订单只有一个支付号
go
type Order struct {
gorm.Model
OrderDetail []OrderDetail `gorm:"foreignKey:OrderID"`
Payment Payment `gorm:"foreignKey:OrderID"`
}
type Payment struct {
gorm.Model
OrderID string `gorm:"type:varchar(100)"`
}
3.3.3 多对多
比如门店和订单就是多对多关系,这时建立一张id的关联表shop_order,里面只有两个字段shop_id和order_id
go
type Order struct {
gorm.Model
OrderDetail []OrderDetail `gorm:"foreignKey:OrderID"`
Payment Payment `gorm:"foreignKey:OrderID"`
Shops []Shop `gorm:"many2many:shop_order"`
}
type Shop struct {
gorm.Model
Orders []Order `gorm:"many2many:shop_order"`
}
3.3.4 预加载查询
在 GORM 中,关联查询用于处理数据库表之间的关系。GORM 支持多种类型的关系,包括一对一、一对多和多对多。通过使用关联查询,可以轻松地从一个模型导航到相关模型的数据。
go
type Parent struct {
Id uint `gorm:"primaryKey"`
Name string `gorm:"size:200"`
Sons []Son `gorm:"foreignKey:ParentId"`
}
type Son struct {
Id uint `gorm:"primaryKey"`
Name string `gorm:"size:200"`
ParentId string `gorm:"foreignKey"`
}
func Test13(t *testing.T) {
_ = db.AutoMigrate(Parent{}, Son{})
// 简单创建数据
db.Create(&Parent{
Sons: []Son{
{
Id: 1,
Name: "儿子1",
},
{
Id: 2,
Name: "儿子2",
},
},
Name: "父亲",
Id: 1,
})
var parent1 Parent
db.Find(&parent1, 1)
fmt.Println(parent1)
// 预加载查询
var parent2 Parent
db.Preload("Sons").Find(&parent2, 1)
fmt.Println(parent2)
}
输出如下,可以看到,没有预加载的话,查询默认是不会获取到子表数据的
{1 父亲 []}
{1 父亲 [{1 儿子1 1} {2 儿子2 1}]}
四、高级应用
4.1 数据库连接池配置
GORM允许你配置数据库连接池,以提高性能和资源管理
使用连接池查询数据与普通的数据库查询没有区别,因为连接池的管理是由底层的 database/sql
包自动处理的。我们只需要专注于编写查询逻辑,GORM 会在后台为你处理连接池。
go
func Test4(t *testing.T) {
sqlDB, err := db.DB()
if err != nil {
panic("failed to get database")
}
// 设置最大空闲连接数
sqlDB.SetMaxIdleConns(10)
// 设置最大打开连接数
sqlDB.SetMaxOpenConns(100)
// 设置连接可复用的最长时间
sqlDB.SetConnMaxLifetime(time.Hour)
model := Order{}
db.First(model)
marshal, _ := json.Marshal(model)
fmt.Println(string(marshal))
}
4.2 查询操作
4.2.1 准备数据
go
type UserInfoTmp struct {
ID uint `gorm:"primaryKey;autoIncrement"`
Name string `gorm:"size:100"`
Code string `gorm:"size:100"`
}
func Test5(t *testing.T) {
_ = db.AutoMigrate(&UserInfoTmp{})
db.Create(&UserInfoTmp{
Name: "小明1",
Code: "x1",
})
db.Create(&UserInfoTmp{
Name: "小明2",
Code: "x2",
})
db.Create(&UserInfoTmp{
Name: "小明22",
Code: "x22",
})
db.Create(&UserInfoTmp{
Name: "小明333",
Code: "x333",
})
db.Create(&UserInfoTmp{
Name: "小明4....",
Code: "x4...",
})
}
4.2.2 条件查询
支持链式调用进行复杂条件查询
go
func Test6(t *testing.T) {
var users []UserInfoTmp
db.Where("name = ?", "小明1").Or("name = ?", "小明2").Find(&users)
fmt.Println(users)
db.Not("name", "小明333").Find(&users)
fmt.Println(users)
db.Where("id BETWEEN ? AND ?", 4, 5).Find(&users)
fmt.Println(users)
}
输出
[{1 小明1 x1} {2 小明2 x2}]
[{1 小明1 x1} {2 小明2 x2} {3 小明22 x22} {5 小明4.... x4...}]
[{4 小明333 x333} {5 小明4.... x4...}]
4.2.3 原生SQL查询
可以执行原生SQL语句并将结果映射到模型中:
go
func Test7(t *testing.T) {
var result []UserInfoTmp
db.Raw(`SELECT * FROM user_info_tmp WHERE code like '%3%' or code = ?`, "x22").Scan(&result)
fmt.Println(result)
}
输出
[{3 小明22 x22} {4 小明333 x333}]
4.3 更新操作
支持批量更新和条件更新
go
func Test8(t *testing.T) {
// 批量更新所有记录的某个字段值:
db.Model(&UserInfoTmp{}).Where("id > ?", 3).Update("Code", gorm.Expr("Concat(`code`,?)", "Extend"))
// 使用Map进行批量更新多个列:
db.Model(&UserInfoTmp{ID: 3}).Updates(map[string]interface{}{"Name": "XXX", "Code": "xxx"})
}
4.4 删除操作
除了根据主键删除,还可以根据条件删除记录
go
func Test9(t *testing.T) {
// 没有DeletedAt字段时,直接删除
db.Delete(&UserInfoTmp{ID: 1})
_ = db.AutoMigrate(UserInfoTmp2{})
db.Create(&UserInfoTmp2{Name: "XXX"})
db.Create(&UserInfoTmp2{Name: "XXX"})
// 有DeletedAt类型的字段时,不会硬删
db.Delete(&UserInfoTmp2{ID: 1})
// 硬删
db.Unscoped().Delete(&UserInfoTmp2{ID: 2})
// 根据条件删除多条记录
db.Where("id > ?", 5).Delete(&UserInfoTmp2{})
}
注意:默认情况下,Delete
方法会执行软删除(即将DeletedAt
字段设置为当前时间),如果要永久删除,可以使用 Unscoped 方法。
4.5 事务处理
事务在需要保证一组数据库操作要么全部成功,要么全部失败时非常有用。
4.5.1 简单事务
go
func Test10(t *testing.T) {
if err := CreateUser(db); err != nil {
fmt.Println(err.Error())
}
}
// 通过事务创建用户
func CreateUser(db *gorm.DB) error {
// 通过数据库连接打开一个事务
return db.Transaction(func(tx *gorm.DB) error {
err := tx.Create(&UserInfoTmp{Name: "测试事务1"})
if err.Error != nil {
return err.Error
}
err = tx.Create(&UserInfoTmp2{Name: "测试事务2"})
// 可以简单模拟重复失败的情况
//err = tx.Create(&UserInfoTmp2{ID: 3, Name: "测试事务2"})
if err.Error != nil {
return err.Error
}
// 完成事务
return nil
})
}
4.5.2 手动管理事务
- 启动事务 :
使用db.Begin()
方法启动一个新的事务,它会返回一个新的*gorm.DB
对象,该对象绑定到当前的数据库连接。 - 执行操作 :
在返回的事务对象上执行所有需要包含在该事务中的数据库操作。 - 提交或回滚 :
- 如果所有操作都成功,则调用
tx.Commit()
提交事务。 - 如果发生错误,则调用
tx.Rollback()
回滚所有更改。
- 如果所有操作都成功,则调用
go
func Test11(t *testing.T) {
CreateUserManual(db)
}
func CreateUserManual(db *gorm.DB) {
begin := db.Begin()
defer func() {
// 捕获 panic 并确保回滚(防止程序崩溃时未正确处理)
if r := recover(); r != nil {
begin.Rollback()
log.Println("事务提交失败:", r)
}
}()
err := begin.Create(&UserInfoTmp{Name: "测试事务3"})
if err.Error != nil {
begin.Rollback()
}
err = begin.Create(&UserInfoTmp2{Name: "测试事务4"})
// 模拟panic
// err = begin.Create(UserInfoTmp2{Name: "测试事务4"})
if err.Error != nil {
begin.Rollback()
}
begin.Commit()
}
4.6 钩子函数(Hooks)
钩子函数允许你在特定事件发生前后执行自定义逻辑。例如,在创建、更新或删除之前/之后触发代码逻辑
go
type TestBeforeCreate struct {
Id string `gorm:"primaryKey"`
Name string `gorm:"size:200"`
}
func (u *TestBeforeCreate) BeforeCreate(tx *gorm.DB) (err error) {
u.Id = uuid.New().String() // 在用户创建之前生成 UUID
return
}
func Test12(t *testing.T) {
_ = db.AutoMigrate(&TestBeforeCreate{})
db.Create(&TestBeforeCreate{})
}
uuid使用需要安装库
go get github.com/google/uuid
五、常见报错
go
type UserInfoTmp struct {
ID uint `gorm:"primaryKey;autoIncrement"`
Name string
}
func Test5(t *testing.T) {
_ = db.AutoMigrate(&UserInfoTmp{})
db.Create(UserInfoTmp{
Name: "小明",
})
}
执行以上代码会报错:
panic: reflect: reflect.Value.SetUint using unaddressable value [recovered]
panic: reflect: reflect.Value.SetUint using unaddressable value
这是因为 GORM 无法设置自增主键字段,因为它需要通过反射修改原始对象。将 UserInfoTmp
的实例作为指针传递给 db.Create()
方法,GORM 才可以正确地更新对象中的字段。
db.Create(&UserInfoTmp{
Name: "小明",
})