@

概述

定义

核心功能

PreloadJoins

声明模型与约定

模型是标准的 struct,由 Go 的基本数据类型、实现了 Scanner和 Valuer接口的自定义类型及其指针或别名组成,示例如:

type User struct 
  ID           uint
  Name         string
  Email        *string
  Age          uint8
  Birthday     *time.Time
  MemberNumber sql.NullString
  ActivatedAt  sql.NullTime
  CreatedAt    time.Time
  UpdatedAt    time.Time

ID蛇形复数蛇形CreatedAtUpdatedAt

gorm.Model

GORM定义了gorm.Model,其中包括字段ID, CreatedAt, UpdatedAt, DeletedAt,可以将其嵌入到结构中以包含这些字段

字段级权限

可导出的字段在使用 GORM 进行 CRUD 时拥有全部的权限,此外,GORM 允许您用标签控制字段级别的权限。这样就可以让一个字段的权限是只读、只写、只创建、只更新或者被忽略

type User struct 
  Name string `gorm:"<-:create"` // 允许读和创建
  Name string `gorm:"<-:update"` // 允许读和更新
  Name string `gorm:"<-"`        // 允许读和写(创建和更新)
  Name string `gorm:"<-:false"`  // 允许读,禁止写
  Name string `gorm:"->"`        // 只读(除非有自定义配置,否则禁止写)
  Name string `gorm:"->;<-:create"` // 允许读和写
  Name string `gorm:"->:false;<-:create"` // 仅创建(禁止从 db 读)
  Name string `gorm:"-"`  // 通过 struct 读写会忽略该字段
  Name string `gorm:"-:all"`        // 通过 struct 读写、迁移会忽略该字段
  Name string `gorm:"-:migration"`  // 通过 struct 迁移会忽略该字段

时间惯例

GORM按惯例使用CreatedAt、UpdatedAt来跟踪创建/更新时间,如果定义了字段,GORM将在创建/更新时设置当前时间。要使用具有不同名称的字段,可以使用标签autoCreateTime、autoUpdateTime来配置这些字段。如果希望节省存储不采用时间格式改为采用UNIX(毫/纳)秒,可以简单地更改字段的数据类型。

type User struct 
  CreatedAt time.Time // 如果创建时为零,则设置为当前时间
  UpdatedAt int       // 在更新时设置为当前unix秒数,或者在创建时设置为零
  Updated   int64 `gorm:"autoUpdateTime:nano"` // 使用unix纳秒作为更新时间
  Updated   int64 `gorm:"autoUpdateTime:milli"`// 使用unix毫秒作为更新时间
  Created   int64 `gorm:"autoCreateTime"`      // 使用unix seconds作为创建时间

嵌入结构

例如将Go内置gorm.Model结构体嵌入到User结构体里

type User struct 
  gorm.Model
  Name string

// 这个定义等价于上面
type User struct 
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
  Name string

但对于普通的struct字段,可以将其嵌入标签,例如:

type Author struct 
  Name  string
  Email string


type Blog struct 
  ID      int
  Author  Author `gorm:"embedded"`
  Upvotes int32 

// 这个定义等价于上面Blog内嵌Author
type Blog struct 
  ID    int64
  Name  string
  Email string
  Upvotes  int32

可以使用标签embeddedPrefix为嵌入字段的数据库名称添加前缀,例如:

type Blog struct 
  ID      int
  Author  Author `gorm:"embedded;embeddedPrefix:author_"`
  Upvotes int32

// 这个定义等价于上面Blog内嵌Author
type Blog struct 
  ID          int64
  AuthorName  string
  AuthorEmail string
  Upvotes     int32

字段标签

MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENTserializer:json/gob/unixtimesize:256autoCreateTime:nanoautoUpdateTime:millicheck:age > 13
使用

安装

# 引入gorm
go get -u gorm.io/gorm
# 引入mySQL驱动
go get -u gorm.io/driver/mysql
# 引入sqlite驱动
go get -u gorm.io/driver/sqlite

数据库链接

import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
)

func main() 
  dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config)

MySQL Driver提供了一些可以在初始化时使用的高级配置,例如:

db, err := gorm.Open(mysql.New(mysql.Config
  DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // data source name
  DefaultStringSize: 256, // default size for string fields
  DisableDatetimePrecision: true, // disable datetime precision, which not supported before MySQL 5.6
  DontSupportRenameIndex: true, // drop & create when rename index, rename index not supported before MySQL 5.7, MariaDB
  DontSupportRenameColumn: true, // `change` when rename column, rename column not supported before MySQL 8, MariaDB
  SkipInitializeWithVersion: false, // auto configure based on currently MySQL version
), &gorm.Config)

连接池

GORM使用数据库/sql维护连接池

sqlDB, err := db.DB()// SetMaxIdleConns设置空闲连接池中的最大连接数。sqlDB.SetMaxIdleConns(10)// SetMaxOpenConns设置数据库的最大打开连接数。sqlDB.SetMaxOpenConns(100)// SetConnMaxLifetime设置连接可能被重用的最大时间。sqlDB.SetConnMaxLifetime(time.Hour)

CRUD 接口

创建

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

type User struct 
	gorm.Model
	Name string
	Age  int8


func main() 
	dsn := "root:123456@tcp(mysqlserver8:3306)/test?charset=utf8&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config)
	if err != nil 
		fmt.Println("data error")
	

	db.AutoMigrate(&User)

	// 单个插入创建
	db.Create(&UserName: "zhangsan", Age: 20)

	users := []User
		Name: "lisi", Age: 25,
		Name: "wangwu", Age: 26,
	

	// 多个插入并演示返回值
	result := db.Create(users)
	if result != nil 
		fmt.Println(result.RowsAffected)
		fmt.Println(result.Error)
	

	var user User
    // 根据主键ID查询主键值为1的记录并返回数据
	db.First(&user, 1)
	fmt.Println(user)


MySQL的test数据库及对应表users信息如下:

// 指定批处理大小
var users = []UserName: "jinzhu_1", ...., Name: "jinzhu_10000"
db.CreateInBatches(users, 100)

// 初始化GORM时使用CreateBatchSize选项,所有INSERT在创建记录和关联时都将遵循此选项
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config
  CreateBatchSize: 1000,
)
db := db.Session(&gorm.SessionCreateBatchSize: 1000)
users = [5000]UserName: "jinzhu", Pets: []Petpet1, pet2, pet3...
db.Create(&users)

钩子函数示例

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

type User struct 
	gorm.Model
	Name string
	Age  int8


func (u *User) BeforeCreate(tx *gorm.DB) (err error) 
	if u.Name == "admin" 
		fmt.Println("before create hint admin")
	
	return


func (u *User) AfterCreate(tx *gorm.DB) (err error) 
	if u.Name == "admin" 
		fmt.Println("after create hint admin")
	
	return


func (u *User) BeforeSave(tx *gorm.DB) (err error) 
	if u.Name == "admin" 
		fmt.Println("before save hint admin")
	
	return


func (u *User) AfterSave(tx *gorm.DB) (err error) 
	if u.Name == "admin" 
		fmt.Println("after save hint admin")
	
	return


func main() 
	dsn := "root:123456@tcp(mysqlserver8:3306)/test?charset=utf8&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config)
	if err != nil 
		fmt.Println("data error")
	

	db.Create(&UserName: "admin", Age: 40)


如果你想跳过Hooks方法,可以使用SkipHooks会话模式

DB.Session(&gorm.SessionSkipHooks: true).Create(&user)
DB.Session(&gorm.SessionSkipHooks: true).Create(&users)
DB.Session(&gorm.SessionSkipHooks: true).CreateInBatches(users, 100)

查询

GORM提供了First、Take、Last方法来从数据库中检索单个对象,它在查询数据库时添加了LIMIT 1条件,如果没有找到记录,它将返回错误ErrRecordNotFound。

First和Last方法将按主键顺序分别查找第一个和最后一个记录。只有当指向目标结构的指针作为参数传递给方法时,或者使用db.Model()指定模型时,它们才有效。此外,如果没有为相关模型定义主键,则模型将按第一个字段排序。

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

type User struct 
	gorm.Model
	Name string
	Age  int8


func (u *User) BeforeCreate(tx *gorm.DB) (err error) 
	if u.Name == "admin" 
		fmt.Println("before create hint admin")
	
	return


func (u *User) AfterCreate(tx *gorm.DB) (err error) 
	if u.Name == "admin" 
		fmt.Println("after create hint admin")
	
	return


func (u *User) BeforeSave(tx *gorm.DB) (err error) 
	if u.Name == "admin" 
		fmt.Println("before save hint admin")
	
	return


func (u *User) AfterSave(tx *gorm.DB) (err error) 
	if u.Name == "admin" 
		fmt.Println("after save hint admin")
	
	return


func main() 
	dsn := "root:123456@tcp(192.168.50.95:3306)/test?charset=utf8&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config)
	if err != nil 
		fmt.Println("data error")
	

	var user, user1, user2, user3 User
	// 获取一条记录,下面相当于SELECT * FROM users ORDER BY id LIMIT 1;
	db.First(&user)
	fmt.Println("user=", user)

	// 获取一条记录, 下面相当于SELECT * FROM users LIMIT 1;
	db.Take(&user1)
	fmt.Println("user1=", user1)

	// 获取一条记录, 下面相当于SELECT * FROM users ORDER BY id DESC LIMIT 1;
	db.Last(&user2)
	fmt.Println(user2)

	// 指定表获取数据放入map
	result := map[string]interface
	db.Table("users").Take(&result)
	fmt.Println("result=", result)

	db.First(&user3, "id = ?", 3)
	fmt.Println("user3=", user3)

	var users, users1 []User
	db.Find(&users, []int1, 2, 3)
	fmt.Println("users=", users)

	// 查询指定字段和条件
	db.Select("name").Where("id > ?", 2).Find(&users1)
	fmt.Println("users1=", users1)

其他详细查看官网

// limit ,SELECT * FROM users LIMIT 3;
db.Limit(3).Find(&users)
// OFFSET ,SELECT * FROM users OFFSET 3;
db.Offset(3).Find(&users)
// group by ,SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"
db.Model(&User).Select("name, sum(age) as total").Group("name").Having("name = ?", "group").Find(&result)
// distinct
db.Distinct("name", "age").Order("name, age desc").Find(&results)
// join ,SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id
db.Model(&User).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result)
// scan,将结果扫描到结构体中的工作方式类似于我们使用Find的方式
var result Result
db.Table("users").Select("name", "age").Where("name = ?", "Antonio").Scan(&result)
// 原始SQL
db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result)

高级查询

  • 智能选择字段:GORM允许通过Select选择特定的字段,如果你经常在你的应用程序中使用这个,也许你想为API定义一个更小的结构体,它可以自动选择特定的字段。
type User struct 
  ID     uint
  Name   string
  Age    int
  Gender string
  // hundreds of fields


type APIUser struct 
  ID   uint
  Name string


// 查询时自动选择“id”、“name”,SELECT `id`, `name` FROM `users` LIMIT 10
db.Model(&User).Limit(10).Find(&APIUser)
  • 锁:GORM支持不同类型的锁。
// SELECT * FROM `users` FOR UPDATE
db.Clauses(clause.LockingStrength: "UPDATE").Find(&users)
// SELECT * FROM `users` FOR SHARE OF `users`
db.Clauses(clause.Locking
  Strength: "SHARE",
  Table: clause.TableName: clause.CurrentTable,
).Find(&users)
// SELECT * FROM `users` FOR UPDATE NOWAIT
db.Clauses(clause.Locking
  Strength: "UPDATE",
  Options: "NOWAIT",
).Find(&users)
  • SubQuery:子查询可以嵌套在查询中,使用* GORM . db对象作为参数时,GORM可以生成子查询。
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)

// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users")
db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results)
  • COUNT:获取匹配记录计数。
// SELECT count(1) FROM users WHERE name = \'jinzhu\'; (count)
db.Model(&User).Where("name = ?", "jinzhu").Count(&count)

修改

  • 保存所有字段
db.First(&user)
user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)
  • 更新字段
db.First(&user, "age = ?", 25)	
db.Model(&user).Updates(UserName: "lisinew1", Age: 44)
db.Model(&user).Updates(map[string]interface"Name": "lisinew2", "Age": 35)
db.Model(&user).Update("age", 30)

删除

如果您的模型包含一个gorm。DeletedAt字段(包含在gorm.Model中),它将自动获得软删除能力!

// GORM允许使用带有内联条件的主键删除对象
db.Delete(&user, 1)

// 假如Email\'s ID is `10`,DELETE from emails where id = 10;
db.Delete(&email)

// 带附加条件删除
db.Where("name = ?", "jinzhu").Delete(&email)

// 永久删除
db.Unscoped().Delete(&order)

原始SQL

  • 使用Scan查询Raw SQL
type Result struct 
  ID   int
  Name string
  Age  int


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

var age int
db.Raw("SELECT SUM(age) FROM users WHERE role = ?", "admin").Scan(&age)

var users []User
db.Raw("UPDATE users SET name = ? WHERE age = ? RETURNING id, name", "jinzhu", 20).Scan(&users)

  • 执行原始sql
// 执行删除表数据
db.Exec("DROP TABLE users")
// 执行带有表达式的更新
db.Exec("UPDATE users SET money = ? WHERE name = ?", gorm.Expr("money * ? + ?", 10000, 1), "jinzhu")
  • DryRun模式
// Session Configuration
type Session struct 
  DryRun                   bool
  PrepareStmt              bool
  NewDB                    bool
  Initialized              bool
  SkipHooks                bool
  SkipDefaultTransaction   bool
  DisableNestedTransaction bool
  AllowGlobalUpdate        bool
  FullSaveAssociations     bool
  QueryFields              bool
  Context                  context.Context
  Logger                   logger.Interface
  NowFunc                  func() time.Time
  CreateBatchSize          int



// session mode
stmt := db.Session(&SessionDryRun: true).First(&user, 1).Statement	
println(stmt.SQL.String()) 
println(stmt.Vars)
  • Row & Rows
// row
row := db.Table("users").Where("name = ?", "jinzhu").Select("name", "age").Row()
row.Scan(&name, &age)

row := db.Raw("select name, age, email from users where name = ?", "jinzhu").Row()
row.Scan(&name, &age, &email)

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

  // do something


rows, err := db.Raw("select name, age, email from users where name = ?", "jinzhu").Rows()
defer rows.Close()
for rows.Next() 
  rows.Scan(&name, &age, &email)

  // do something

事务

GORM在事务内部执行写(创建/更新/删除)操作以确保数据一致性,如果不需要,可以在初始化时禁用它,之后将获得大约30%以上的性能提升。流程如下:

func CreateAnimals(db *gorm.DB) error 
  // 注意,在事务中使用tx作为数据库句柄
  tx := db.Begin()
  defer func() 
    if r := recover(); r != nil 
      tx.Rollback()
    
  ()

  if err := tx.Error; err != nil 
    return err
  

  if err := tx.Create(&AnimalName: "Giraffe").Error; err != nil 
     tx.Rollback()
     return err
  

  if err := tx.Create(&AnimalName: "Lion").Error; err != nil 
     tx.Rollback()
     return err
  

  return tx.Commit().Error

转换

  • ID为主键:默认情况下,GORM使用名称为ID的字段作为表的主键。可以通过实现Tabler接口来更改默认表名,例如:
// 可以使用标记primaryKey将其他字段设置为主键
type Animal struct 
  ID     int64
  UUID   string `gorm:"primaryKey"`
  Name   string
  Age    int64

  • 复数表名:GORM将结构名复数化为snake_cases作为表名,对于结构User,它的表名按照约定是users
// 转换表名为my_users
func (User) TableName() string 
	return "my_users"

  • GORM允许用户通过覆盖默认的NamingStrategy来更改默认的命名约定,该策略用于构建TableName, ColumnName, JoinTableName, RelationshipFKName, CheckerName, IndexName
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`

分片

Sharding 是一个高性能的 Gorm 分表中间件。它基于 Conn 层做 SQL 拦截、AST 解析、分表路由、自增主键填充,带来的额外开销极小。对开发者友好、透明,使用上与普通 SQL、Gorm 查询无差别,只需要额外注意一下分表键条件。 为您提供高性能的数据库访问。

功能特点

  • 非侵入式设计, 加载插件,指定配置,既可实现分表。
  • 轻快, 非基于网络层的中间件,像 Go 一样快
  • 支持多种数据库。 PostgreSQL 已通过测试,MySQL 和 SQLite 也在路上。
  • 多种主键生成方式支持(Snowflake, PostgreSQL Sequence, 以及自定义支持)Snowflake 支持从主键中确定分表键。

配置分片中间件,注册想要分片的表

import (
  "fmt"

  "gorm.io/driver/postgres"
  "gorm.io/gorm"
  "gorm.io/sharding"
)

dsn := "postgres://localhost:5432/sharding-db?sslmode=disable"
db, err := gorm.Open(postgres.New(postgres.ConfigDSN: dsn))

db.Use(sharding.Register(sharding.Config
    ShardingKey:         "user_id",
    NumberOfShards:      64,
    PrimaryKeyGenerator: sharding.PKSnowflake,
, "orders").Register(sharding.Config
    ShardingKey:         "user_id",
    NumberOfShards:      256,
    PrimaryKeyGenerator: sharding.PKSnowflake,
    // 对于show up give notifications, audit_logs表使用相同的分片规则。
, Notification, AuditLog))

序列化

序列化器是一个可扩展的接口,允许自定义如何使用databasae序列化和反序列化数据。

package main

import (
	"database/sql/driver"
	"encoding/json"
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

type Profile struct 
	Email  string `json:"email"`
	Mobile string `json:"mobile"`


type User struct 
	gorm.Model
	Name    string  `json:"name"`
	Age     int8    `json:"age"`
	Profile Profile `json:"profile" gorm:"type:json;comment:\'个人信息\'"`


// 转换表名
func (User) TableName() string 
	return "new_users"


// Value 存储数据的时候转换为字符串
func (t Profile) Value() (driver.Value, error) 
	return json.Marshal(t)


// Scan 读取数据的时候转换为json
func (t *Profile) Scan(value interface) error 
	return json.Unmarshal(value.([]byte), &t)


func main() 
	dsn := "root:123456@tcp(mysqlserver8:3306)/test?charset=utf8&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config)
	if err != nil 
		fmt.Println("data error")
	
	db.AutoMigrate(&User)

	user := User
		Name: "刘海",
		Age:  23,
		Profile: Profile
			Email:  "test1@qq.com",
			Mobile: "18822334455",
		,
	
	db.Create(&user)

	var user1 User
	db.Debug().Where("profile->\'$.mobile\'=(?)", "18822334455").First(&user1)
	fmt.Println(user1)


查看mysql已有新创建的new_users数据库和对应的数据