7.6 GORM 框架入门
简介
GORM 是 Go 语言中常用的数据库抽象层(DBAL)库,API 简洁灵活,便于完成连接、增删改查,并支持 MySQL、PostgreSQL、SQLite 等多种数据库。下文介绍 ORM 与 GORM 的基本概念、安装与连接、模型与 CRUD、事务与关联与钩子,以及连接池与模型设计等实践建议。
基础概念
什么是 ORM
对象关系映射(Object Relational Mapping,ORM)用于在面向对象语言与关系型数据之间做映射。使用 ORM 时,可以用结构体与对象表达表与行,减少手写 SQL。在 GORM 中,通过定义模型结构体并操作实例,即可对应到表上的增删改查。
GORM 中的基本概念
- 模型(Model):模型是一个结构体,对应一张表;字段对应列,通过操作结构体完成对该表的操作。
- 数据库会话(DB Session):使用
*gorm.DB表示会话,上面挂载查询、创建、更新、删除等方法。通过gorm.Open得到*gorm.DB后即可执行各类数据库操作。
使用方法
安装 GORM
确保已安装 Go,然后执行:
bash
go get -u gorm.io/gorm按目标数据库安装对应驱动,例如 MySQL:
bash
go get -u gorm.io/driver/mysql连接数据库
以 MySQL 为例:
go
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
// 数据库连接字符串
dsn := "user:password@tcp(127.0.0.1:3306)/database_name?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// 在这里可以进行后续的数据库操作
_ = db
}定义模型
用结构体表示表,例如 User:
go
package main
import "gorm.io/gorm"
type User struct {
ID uint
Name string
Age int
}ID 会被 GORM 识别为主键并支持自增。需要自定义主键或列名时,可使用 tag,例如:
go
type User struct {
UserID uint `gorm:"primaryKey;column:user_id"`
Name string
Age int
}此处将 UserID 设为主键,列名为 user_id。
基本的 CRUD 操作
创建(Create)
go
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
ID uint
Name string
Age int
}
func main() {
dsn := "user:password@tcp(127.0.0.1:3306)/database_name?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
user := User{Name: "John", Age: 30}
result := db.Create(&user)
if result.Error != nil {
fmt.Println("Create user failed:", result.Error)
} else {
fmt.Println("User created successfully, ID:", user.ID)
}
}读取(Read)
go
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
ID uint
Name string
Age int
}
func main() {
dsn := "user:password@tcp(127.0.0.1:3306)/database_name?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
var user User
result := db.First(&user, 1) // 根据 ID 读取
if result.Error != nil {
fmt.Println("Read user failed:", result.Error)
} else {
fmt.Printf("User found: Name: %s, Age: %d\n", user.Name, user.Age)
}
}更新(Update)
go
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
ID uint
Name string
Age int
}
func main() {
dsn := "user:password@tcp(127.0.0.1:3306)/database_name?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
var user User
db.First(&user, 1) // 先读取要更新的记录
user.Age = 31
result := db.Save(&user)
if result.Error != nil {
fmt.Println("Update user failed:", result.Error)
} else {
fmt.Println("User updated successfully")
}
}删除(Delete)
go
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
ID uint
Name string
Age int
}
func main() {
dsn := "user:password@tcp(127.0.0.1:3306)/database_name?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
result := db.Delete(&User{}, 1) // 根据 ID 删除
if result.Error != nil {
fmt.Println("Delete user failed:", result.Error)
} else {
fmt.Println("User deleted successfully")
}
}常见实践
事务处理
需要多个操作原子完成时使用事务,例如:
go
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
ID uint
Name string
Age int
}
func main() {
dsn := "user:password@tcp(127.0.0.1:3306)/database_name?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
tx := db.Begin()
if tx.Error != nil {
fmt.Println("Begin transaction failed:", tx.Error)
return
}
user1 := User{Name: "Alice", Age: 25}
if err := tx.Create(&user1).Error; err != nil {
tx.Rollback()
fmt.Println("Create user1 failed:", err)
return
}
user2 := User{Name: "Bob", Age: 28}
if err := tx.Create(&user2).Error; err != nil {
tx.Rollback()
fmt.Println("Create user2 failed:", err)
return
}
if err := tx.Commit().Error; err != nil {
fmt.Println("Commit transaction failed:", err)
return
}
fmt.Println("Transactions committed successfully")
}关联关系处理
GORM 支持一对多、多对一、多对多等关联。下面是一对多示例:Author 与 Book。
go
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type Author struct {
ID uint
Name string
Books []Book
}
type Book struct {
ID uint
Title string
AuthorID uint
}
func main() {
dsn := "user:password@tcp(127.0.0.1:3306)/database_name?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
author := Author{Name: "J.K. Rowling"}
book1 := Book{Title: "Harry Potter and the Philosopher's Stone"}
book2 := Book{Title: "Harry Potter and the Chamber of Secrets"}
author.Books = []Book{book1, book2}
// 自动保存关联关系
result := db.Create(&author)
if result.Error != nil {
fmt.Println("Create author and books failed:", result.Error)
} else {
fmt.Println("Author and books created successfully")
}
}钩子函数的使用
钩子是在执行某些数据库操作前后由 GORM 自动调用的方法。例如在创建 User 前加密密码:
go
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"golang.org/x/crypto/bcrypt"
)
type User struct {
ID uint
Name string
Password string
}
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), 14)
if err != nil {
return err
}
u.Password = string(hashedPassword)
return nil
}
func main() {
dsn := "user:password@tcp(127.0.0.1:3306)/database_name?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
user := User{Name: "Tom", Password: "password123"}
result := db.Create(&user)
if result.Error != nil {
fmt.Println("Create user failed:", result.Error)
} else {
fmt.Println("User created successfully")
}
}最佳实践
数据库连接池优化
GORM 底层使用驱动提供的连接池。可按负载调整最大连接数、最大空闲连接等,例如先拿到 *sql.DB 再交给 GORM:
go
package main
import (
"database/sql"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
dsn := "user:password@tcp(127.0.0.1:3306)/database_name?charset=utf8mb4&parseTime=True&loc=Local"
sqlDB, err := sql.Open("mysql", dsn)
if err != nil {
panic("failed to open database")
}
// 设置连接池参数
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
db, err := gorm.Open(mysql.New(mysql.Config{
Conn: sqlDB,
}), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// 在这里进行数据库操作
_ = db
}模型设计原则
- 单一职责:每个模型对应一种业务实体,避免单结构体承载过多职责。
- 约定优于配置:尽量使用 GORM 默认约定(如驼峰字段对应下划线列名),减少冗余 tag。
- 考虑扩展:预留后续可能增加的字段或关联,避免频繁大改表结构。
日志记录与错误处理
操作数据库时应记录日志并处理错误,可使用标准库 log 或 zap 等。例如:
go
package main
import (
"fmt"
"log"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
dsn := "user:password@tcp(127.0.0.1:3306)/database_name?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("failed to connect database:", err)
}
var user struct{}
result := db.First(&user)
if result.Error != nil {
log.Println("Read user failed:", result.Error)
} else {
fmt.Println("User found")
}
}小结
GORM 为 Go 开发者提供了便捷的数据库访问方式。理解 ORM 与 *gorm.DB、掌握安装连接与模型 CRUD,并结合事务、关联与钩子,再注意连接池与模型设计、日志与错误处理,有利于在中小型到大型项目中稳定使用 GORM。
参考资料
- GORM 官方文档
- GORM GitHub 仓库
- 《Go 语言编程之旅:一起用 Go 做项目》