GORM 是 Go 语言中功能强大的 ORM(对象关系映射)框架,支持 MySQL、PostgreSQL、SQLite、SQL Server 等主流数据库。以下是 GORM 的核心概念和用法详解:
一、基础入门
1. 安装
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql # 按需选择数据库驱动
所以我们看到安装的最新版本是 1.26.0.这个不是Gorm1.0的意思。是GORM2.0.参考官方2.0的文档。https://gorm.io/zh_CN/docs/
2. 连接数据库
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
Gorm 有一个 默认 logger 实现,默认情况下,它会打印慢 SQL 和错误
Logger 接受的选项不多,您可以在初始化时自定义它,例如:
newLogger := logger.New( log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer logger.Config{ SlowThreshold: time.Second, // Slow SQL threshold LogLevel: logger.Silent, // Log level IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger ParameterizedQueries: true, // Don't include params in the SQL log Colorful: false, // Disable color }, ) // Globally mode db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{ Logger: newLogger, }) // Continuous session mode tx := db.Session(&Session{Logger: newLogger}) tx.First(&user) tx.Model(&user).Update("Age", 18) |
日志级别
GORM 定义了这些日志级别:Silent
、Error
、Warn
、Info
二、模型定义
1. 结构体与表映射
type User struct {
gorm.Model // 内置字段:ID, CreatedAt, UpdatedAt, DeletedAt
Name string `gorm:"size:255"`
Age int
Email string `gorm:"uniqueIndex size:255"` // 唯一索引
}
2. 自定义表名
func (User) TableName() string {
return "custom_users" // 自定义表名
}
3.自动迁移
GORM 提供的自动迁移功能是指它能够根据定义的 Go 结构体(模型)自动创建、更新或删除数据库中的表结构,以此保持模型和数据库表结构的一致性。这一功能极大地简化了数据库表结构管理的流程。例如,当你新增一个字段到结构体中,自动迁移会尝试在数据库表中添加该字段;若删除了结构体中的某个字段,自动迁移也可能根据配置删除表中的相应列。
工作原理
- 解析模型定义:GORM 会解析你定义的 Go 结构体,识别其中的字段、类型、标签等信息。例如,对于以下
User
结构
type User struct {
gorm.Model // 内置字段:ID, CreatedAt, UpdatedAt, DeletedAt
Name string `gorm:"size:255"`
Age int
Email string `gorm:"uniqueIndex size:255"` // 唯一索引
}
func (User) TableName() string {
return "custom_users" // 自定义表名
}
GORM 会分析出 User
表应该包含 id
、created_at
、updated_at
、deleted_at
(来自 gorm.Model
)、name
、email
和 age
这些字段,并且 email
字段需要有唯一约束。
2. 生成 SQL 语句:根据解析结果,GORM 生成相应的 SQL 语句。如果数据库中不存在 custom_users
表,会生成 CREATE TABLE
语句来创建表;若表已存在,会生成 ALTER TABLE
语句来更新表结构。
3. 执行 SQL 语句:GORM 将生成的 SQL 语句发送到数据库执行,从而完成表结构的创建或更新。
使用示例
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// User 模型定义
type User struct {
gorm.Model // 内置字段:ID, CreatedAt, UpdatedAt, DeletedAt
Name string `gorm:"size:255"`
Age int
Email string `gorm:"uniqueIndex size:255"` // 唯一索引
}
func (User) TableName() string {
return "custom_users" // 自定义表名
}
func main() {
dsn := "user: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")
}
// 自动迁移 User 模型
db.AutoMigrate(&User{})
}
输出结果:
在上述代码中,调用 db.AutoMigrate(&User{})
会根据 User
结构体的定义在数据库中创建或更新 User
表。
注意事项
- 数据丢失风险:自动迁移可能会导致数据丢失。例如,当你删除结构体中的某个字段时,自动迁移可能会删除数据库表中的相应列,该列的数据就会丢失。所以在生产环境中最好不要使用该功能。
三、CRUD 操作
1. 创建记录
// 创建单条
user := User{Name: "Alice", Age: 25}
db.Create(&user)
// 批量插入
users := []User{{Name: "Bob"}, {Name: "Charlie"}}
db.CreateInBatches(users, 100) // 每批100条
2. 查询数据
var users []User
// 查询全部
db.Find(&users)
// 条件查询
db.Where("age > ?", 18).Find(&users)
db.First(&user, 1) // 查找ID=1的第一条记录
db.Last(&user) // 最后一条记录
// 高级查询
db.Select("name, age").Where("age > ?", 20).Order("age desc").Limit(10).Find(&users)
3. 更新数据
// 更新单个字段
db.Model(&user).Update("Age", 30)
// 批量更新
db.Model(&User{}).Where("age < ?", 30).Update("Age", gorm.Expr("Age + ?", 1))
4. 删除数据
// 软删除(默认使用 DeletedAt 字段)
db.Delete(&user)
// 硬删除(物理删除)
db.Unscoped().Delete(&user)
四、关联关系
一对一关联
package main
import "gorm.io/gorm"
// User 用户模型
type User struct {
gorm.Model
Name string
Profile Profile
}
// Profile 个人资料模型
type Profile struct {
gorm.Model
UserID uint
Address string
}
在这个例子中,User
结构体包含一个 Profile
字段,Profile
结构体包含一个 UserID
字段作为外键,关联到 User
表。
迁移和操作示例
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func ConnectDB() (*gorm.DB, error) {
dsn := "user: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 {
return nil, err
}
return db, nil
}
func main() {
db, err := ConnectDB()
if err != nil {
panic("failed to connect database")
}
// 自动迁移模型
db.AutoMigrate(&User{}, &Profile{})
// 创建用户和关联的个人资料
user := User{
Name: "John Doe",
Profile: Profile{
Address: "123 Main St",
},
}
db.Create(&user)
// 查询用户及其个人资料
var retrievedUser User
db.Preload("Profile").First(&retrievedUser, user.ID)
fmt.Printf("User: %s, Address: %s\n", retrievedUser.Name, retrievedUser.Profile.Address)
}
在这个例子中,我们使用 Preload
方法预加载用户的个人资料。
一对多关联
一对多关联表示一个模型的一条记录可以关联到另一个模型的多条记录。例如,一个用户可以有多个帖子。
package main
import "gorm.io/gorm"
// User 用户模型
type User struct {
gorm.Model
Name string
Posts []Post
}
// Post 帖子模型
type Post struct {
gorm.Model
Title string
UserID uint
}
在这个例子中,User
结构体包含一个 Posts
切片,Post
结构体包含一个 UserID
字段作为外键,关联到 User
表。
迁移和操作示例
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func ConnectDB() (*gorm.DB, error) {
dsn := "user: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 {
return nil, err
}
return db, nil
}
func main() {
db, err := ConnectDB()
if err != nil {
panic("failed to connect database")
}
// 自动迁移模型
db.AutoMigrate(&User{}, &Post{})
// 创建用户和关联的帖子
user := User{
Name: "John Doe",
Posts: []Post{
{Title: "First Post"},
{Title: "Second Post"},
},
}
db.Create(&user)
// 查询用户及其帖子
var retrievedUser User
db.Preload("Posts").First(&retrievedUser, user.ID)
fmt.Printf("User: %s, Posts count: %d\n", retrievedUser.Name, len(retrievedUser.Posts))
}
在这个例子中,我们同样使用 Preload
方法预加载用户的所有帖子。
多对多关联
多对多关联表示一个模型的一条记录可以关联到另一个模型的多条记录,反之亦然。例如,一个用户可以有多个角色,一个角色可以被多个用户拥有。
定义模型
package main
import "gorm.io/gorm"
// User 用户模型
type User struct {
gorm.Model
Name string
Roles []Role `gorm:"many2many:user_roles;"`
}
// Role 角色模型
type Role struct {
gorm.Model
Name string
Users []User `gorm:"many2many:user_roles;"`
}
在这个例子中,User
结构体和 Role
结构体都包含一个切片,通过 many2many
标签指定关联表为 user_roles
。
迁移和操作示例
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func ConnectDB() (*gorm.DB, error) {
dsn := "user: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 {
return nil, err
}
return db, nil
}
func main() {
db, err := ConnectDB()
if err != nil {
panic("failed to connect database")
}
// 自动迁移模型
db.AutoMigrate(&User{}, &Role{})
// 创建用户和角色
user := User{Name: "John Doe"}
role := Role{Name: "Admin"}
db.Create(&user)
db.Create(&role)
// 关联用户和角色
db.Model(&user).Association("Roles").Append(&role)
// 查询用户及其角色
var retrievedUser User
db.Preload("Roles").First(&retrievedUser, user.ID)
fmt.Printf("User: %s, Roles count: %d\n", retrievedUser.Name, len(retrievedUser.Roles))
}
我们使用 Association
方法将用户和角色关联起来,然后使用 Preload
方法预加载用户的所有角色。
- 预加载:使用
Preload
方法可以在查询主模型时同时加载关联的模型数据,避免 N+1 查询问题。 - 关联操作:对于多对多关联,可以使用
Association
方法进行关联的添加、删除等操作。 - 迁移:在使用关联关系之前,需要确保模型已经通过
AutoMigrate
方法进行了迁移,以创建相应的表和外键约束。
- 循环引用:确保模型间没有相互嵌套导致无限递归。
- 性能优化:避免
SELECT *
,明确指定需要的字段。 - 索引优化:在频繁查询的字段上添加索引(如
gorm:"index"
)。