GORM is a fantastic ORM library for Golang.

// TODO: To be updated…

目录

1. 快速开始

1.1 GORM 概述

  • 全功能 ORM(几乎)
  • 关联(包含一个/包含多个/属于/多对多/多态)
  • Callbacks(在创建/保存/更新/删除/查找之前或之后)
  • 预加载
  • 事务
  • 复合主键
  • SQL Builder
  • 数据库自动迁移
  • 自定义日志
  • 可扩展性,可基于 GORM 回调编写插件
  • 每个功能都有测试
  • 开发人员友好

1.2 安装

> go get -u github.com/jinzhu/gorm

1.3 快速体验

MySQL为例:

package main

import (
    "fmt"

    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
)

type Product struct {
    gorm.Model
    Code  string
    Price uint
}

func main() {
    // 初始化 MySQL 连接
    config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s",
        "your_username",
        "your_password",
        "host:port",
        "database",
        true,
        "Local")

    db, err := gorm.Open("mysql", config)
    if err != nil {
        panic("Failed to connect database")
    }
    defer db.Close()

    // Migrate the schema
    db.AutoMigrate(&Product{})

    // 创建
    db.Create(&Product{
        Code:  "L1212",
        Price: 1000,
    })

    // 读取
    var product Product
    db.First(&product, 1)                   // 查询 ID 为 1 的 Product
    db.First(&product, "code = ?", "L1212") // 查询 code 为 L1212 的 Product

    // 更新 - 更新 product 的 price 为 2000
    db.Model(&product).Update("price", 2000)

    // 删除 - 删除 product
    db.Delete(&product)
}

2. 数据库

2.1 连接数据库

首先需要导入目标数据库的驱动程序。例如:

import _ "github.com/go-sql-driver/mysql"

为了方便记住导入路径,GORM包装了一些驱动

import _ "github.com/jinzhu/gorm/dialects/mysql"
// import _ "github.com/jinzhu/gorm/dialects/postgres"
// import _ "github.com/jinzhu/gorm/dialects/sqlite"
// import _ "github.com/jinzhu/gorm/dialects/mssql"
MySQL

注意:为了处理time.Time,需要包括parseTime作为参数。更多支持的参数

import (
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
)

func main() {
    db, err := gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True&loc=Local")
    defer db.Close()
}

也可以参照之前的形式来写:

// 初始化 MySQL 连接
config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s",
    "your_username",
    "your_password",
    "host:port",
    "database",
    true,
    "Local")

db, err := gorm.Open("mysql", config)
if err != nil {
    panic("Failed to connect database")
}
defer db.Close()
PostgreSQL
import (
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/postgres"
)

func main() {
    db, err := gorm.Open("postgres", "host=myhost user=gorm dbname=gorm sslmode=disable password=mypassword")
    defer db.Close()
}
SQLite3
import (
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/sqlite"
)

func main() {
    db, err := gorm.Open("sqlite3", "/tmp/gorm.db")
    defer db.Close()
}

2.2 迁移 Migrate

自动迁移(Auto Migrate)模式将自动保持更新到最新。

注意:自动迁移仅仅会创建表以及缺少的列和索引,并不会改变现有列的类型或删除未使用的列,以保护数据

db.AutoMigrate(&User{})

db.AutoMigrate(&User{}, &Product{}, &Order{})

// 创建表时添加表后缀
db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&Product{})

2.3 检查表是否存在

// 检查模型`User`对应的表是否存在
db.HasTable(&User{})

// 检查表`users`是否存在
db.HasTable("users")

2.4 创建表

// 为模型`User`创建表
db.CreateTable(&User{})

// 创建表`users`时将"ENGINE=InnoDB"附加到SQL语句
db.Set("gorm:table_options", "ENGINE=InnoDB").CreateTable(&User{})

2.5 删除表

// 删除模型`User`的表
db.DropTable(&User{})

// 删除表`users`
db.DropTable("users")

// 删除模型`User`的表和表`products`
db.DropTableIfExists(&User{}, "products")

2.6 修改列

// 修改模型`User`的description列的数据类型为`text`
db.Model(&User{}).ModifyColumn("description", "text")

2.7 删除列

// 删除模型`User`的description列
db.Model(&User{}).DropColumn("description")

2.8 添加外键

// 添加外键
// 1st param : 外键字段
// 2nd param : 外键表(字段)
// 3rd param : ONDELETE
// 4th param : ONUPDATE
db.Model(&Product{}).AddForeignKey("city_id", "cities(id)", "RESTRICT", "RESTRICT")

2.9 索引

// 为`name`列添加索引`idx_user_name`
db.Model(&User{}).AddIndex("idx_user_name", "name")

// 为`name`, `age`列添加索引`idx_user_name_age`
db.Model(&User{}).AddIndex("idx_user_name_age", "name", "age")

// 添加唯一索引
db.Model(&User{}).AddUniqueIndex("idx_user_name", "name")

// 为多列添加唯一索引
db.Model(&User{}).AddUniqueIndex("idx_user_name_age", "name", "age")

// 删除索引
db.Model(&User{}).RemoveIndex("idx_user_name")

3. 模型

3.1 模型定义

package model

import (
    "database/sql"
    "time"

    "github.com/jinzhu/gorm"
)

type User struct {
    gorm.Model
    Birthday time.Time
    Age      int
    Name     string `gorm:"size:255"`       // string 长度默认为 255
    Num      int    `gorm:"AUTO_INCREMENT"` // 自增

    CreditCard CreditCard // One-To-One (拥有一个 - CreditCard 表的 UserID 作外键)
    Emails     []Email    // One-To-Many (拥有多个 - Email 表的 UserID 作外键)

    BillingAddress   Address // One-To-One (属于 - 本表的 BillingAddressID 作外键)
    BillingAddressID sql.NullInt64

    ShippingAddress   Address // One-To-One (属于 - 本表的 ShippingAddressID 作外键)
    ShippingAddressId int

    IgnoreMe  int        `gorm:"-"`                         // 忽略这个字段
    Languages []Language `gorm:"many2many:user_languages;"` // Many-To-Many , `user_languages` 是连接表
}

type Email struct {
    ID         int
    UserID     int    `gorm:"index"`                          // 外键 (属于), tag `index` 是为该列创建索引
    Email      string `gorm:"type:varchar(100);unique_index"` // `type` 设置 sql 类型,`unique_index` 为该列设置唯一索引
    Subscribed bool
}

type Address struct {
    ID       int
    Address1 string         `gorm:"not null;unique"` // 设置该字段非空且唯一
    Address2 string         `gorm:"type:varchar(100);unique"`
    Post     sql.NullString `gorm:"not null"`
}

type Language struct {
    ID   int
    Name string `gorm:"index:idx_name_code"` // 创建索引并命名,如果找到其他相同名称的索引则创建组合索引
    Code string `gorm:"index:idx_name_code"` // `unique_index` also works
}

type CreditCard struct {
    gorm.Model
    UserID uint
    Number string
}

3.2 约定

gorm.Model 结构体

基本模型定义gorm.Model,包括字段IDCreatedAtUpdatedAtDeletedAt

gorm.Modelgorm目录下的model.go中定义:

// Model base model definition, including fields `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt`, which could be embedded in your models
//    type User struct {
//      gorm.Model
//    }
type Model struct {
    ID        uint `gorm:"primary_key"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt *time.Time `sql:"index"`
}

可以将它嵌入你的模型,或者只写需要的字段

// 添加字段 `ID`,`CreatedAt`,`UpdatedAt`,`DeletedAt`
type User struct {
    gorm.Model
    Name string
}

// 只需要字段 `ID`,`CreatedAt`
type User struct {
    ID        uint
    CreatedAt time.Time
    Name      string
}
表名是结构体名的复数
type User struct {} // 默认表名是 `users`

// 设置 User 的表名为 `profiles`
func (User) TableName() string {
    return "profiles"
}

func (u User) TableName() string {
    if u.Role == "admin" {
        return "admin_users"
    } else {
        return "users"
    }
}

// 全局禁用表名负数
db.SingularTable(true) // 如果设置为 true,`User` 的默认表名为 `user`,使用 `TableName` 设置的表名不受影响
更改默认表名

可以通过定义DefaultTableNameHandler对默认表名应用任何规则

gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string  {
    return "prefix_" + defaultTableName;
}
列名是字段名的蛇形小写
type User struct {
    ID        uint      // 列名为 `id`
    Name      string    // 列名为 `name`
    Birthday  time.Time // 列名为 `birthday`
    CreatedAt time.Time // 列名为 `created_at`
}

// 重设列名
type Animal struct {
    AnimalID int64     `gorm:"column:beast_id"`         // 设置列名为 `beast_id`
    Birthday time.Time `gorm:"column:day_of_the_beast"` // 设置列名为 `day_of_the_beast`
    Age      int64     `gorm:"column:age_of_the_beast"` // 设置列名为 `age_of_the_beast`
}
字段 ID 为默认主键
type User struct {
    ID   uint // 字段 `ID` 为默认主键
    Name string
}

// 使用 tag `primary_key` 来设置主键
type Animal struct {
    AnimalID int64 `gorm:"primary_key"` // 设置 AnimalID 为主键
    Name     string
    Age      int64
}
字段 CreatedAt 存储创建时间

创建具有CreatedAt字段的记录将被设置为当前时间

db.Create(&user) // 将会设置 `CreatedAt` 为当前时间

// 使用 `Update` 来更改它的值
db.Model(&user).Update("created_at", time.Now())
字段 UpdatedAt 存储修改时间

保存具有UpdatedAt字段的记录将被设置为当前时间

db.Save(&user) // 将会设置 `UpdatedAt` 为当前时间
db.Model(&user).Update("name", "jinzhu") // 将会设置 `UpdatedAt` 为当前时间
字段 DeletedAt 用于存储删除时间

删除具有DeletedAt字段的记录时,记录本身不会从数据库中删除,只是将字段DeletedAt设置为当前时间,并且该记录在查询时无法被找到,即软删除

3.3 关联

Belongs To

A belongs to association sets up a one-to-one connection with another model, such that each instance of the declaring model “belongs to” one instance of the other model.

例如,如果您的应用程序包含用户和配置文件,并且可以将每个配置文件分配给一个用户:

type User struct {
    gorm.Model
    Name string
}

// `Profile` belongs to `User`, `UserID` is the foreign key
type Profile struct {
    gorm.Model
    UserID int
    User   User
    Name   string
}

4. Code Snippets

// Enable Logger, show detailed log
db.LogMode(true)

// Disable Logger, don't show any log
db.LogMode(false)

// Debug a single operation, show detailed log for this operation
db.Debug().Where("name = ?", "jinzhu").First(&User{})

5. 高级用法

参见 1.6. 高级用法 · GORM 中文文档

5.1 错误处理

执行任何操作后,如果发生任何错误,GORM 会将其设置为*DBError字段:

if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
    // 错误处理...
}

// 如果有多个错误发生,用`GetErrors`获取所有的错误,它返回`[]error`
db.First(&user).Limit(10).Find(&users).GetErrors()

// 检查是否返回 RecordNotFound 错误
db.Where("name = ?", "hello world").First(&user).RecordNotFound()

if db.Model(&user).Related(&credit_card).RecordNotFound() {
    // 没有信用卡被发现处理...
}

5.2 事务

要在事务中执行一组操作,一般流程如下:

// 开始事务
tx := db.Begin()

// 在事务中做一些数据库操作(从这一点使用'tx',而不是'db')
tx.Create(...)

// ...

// 发生错误时回滚事务
tx.Rollback()

// 或提交事务
tx.Commit()

一个具体的例子:

func CreateAnimals(db *gorm.DB) err {
  tx := db.Begin()
  // 注意,一旦你在一个事务中,使用tx作为数据库句柄

  if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
     tx.Rollback()
     return err
  }

  if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
     tx.Rollback()
     return err
  }

  tx.Commit()
  return nil
}

5.3 SQL 构建

执行原生 SQL
db.Exec("DROP TABLE users;")
db.Exec("UPDATE orders SET shipped_at=? WHERE id IN (?)", time.Now, []int64{11,22,33})

// Scan
type Result struct {
    Name string
    Age  int
}

var result Result
db.Raw("SELECT name, age FROM users WHERE name = ?", 3).Scan(&result)
sql.Row & sql.Rows

获取查询结果为*sql.Row*sql.Rows

row := db.Table("users").Where("name = ?", "jinzhu").Select("name, age").Row() // (*sql.Row)
row.Scan(&name, &age)

rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
defer rows.Close()
for rows.Next() {
    ...
    rows.Scan(&name, &age, &email)
    ...
}

// Raw SQL
rows, err := db.Raw("select name, age, email from users where name = ?", "jinzhu").Rows() // (*sql.Rows, error)
defer rows.Close()
for rows.Next() {
    ...
    rows.Scan(&name, &age, &email)
    ...
}
迭代中使用 sql.Rows 的 Scan
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
defer rows.Close()

for rows.Next() {
  var user User
  db.ScanRows(rows, &user)
  // do something
}

5.4 通用数据库接口 sql.DB

*gorm.DB连接获取通用数据库接口*sql.DB

// 获取通用数据库对象`*sql.DB`以使用其函数
db.DB()

// Ping
db.DB().Ping()

设置连接池

db.DB().SetMaxIdleConns(10)
db.DB().SetMaxOpenConns(100)

5.5 复合主键

多个字段设置为主键以启用复合主键

type Product struct {
    ID           string `gorm:"primary_key"`
    LanguageCode string `gorm:"primary_key"`
}

5.6 日志

Gorm 有内置的日志记录器支持,默认情况下,它会打印发生的错误:

// 启用Logger,显示详细日志
db.LogMode(true)

// 禁用日志记录器,不显示任何日志
db.LogMode(false)

// 调试单个操作,显示此操作的详细日志
db.Debug().Where("name = ?", "jinzhu").First(&User{})

也可以自定义Logger

db.SetLogger(gorm.Logger{revel.TRACE})
db.SetLogger(log.New(os.Stdout, "\r\n", 0))

参见:

  1. Logger | GORM 文档
  2. 自定义 Logger - GORM 中文文档 | LearnKu

// TODO: To be updated…

参考文章

  1. gorm.io
  2. GORM Guides | gorm.io
  3. GORM Guides 中文版 | gorm.io
  4. GoDoc | Search for Go Packages
  5. GORM 中文文档 | LearnKu
  6. GORM 中文文档 | GitBook
  7. 逻辑数据库设计 - 多态关联 | 博客园
  8. Logger | GORM 文档
  9. 自定义 Logger - GORM 中文文档 | LearnKu