Skip to content

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 支持一对多、多对一、多对多等关联。下面是一对多示例:AuthorBook

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。
  • 考虑扩展:预留后续可能增加的字段或关联,避免频繁大改表结构。

日志记录与错误处理

操作数据库时应记录日志并处理错误,可使用标准库 logzap 等。例如:

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。

参考资料