概述

gorm是go语言编写的开发者友好的orm库。

有如下特性:

  • 全功能的ORM。
  • 关系(一对一,一对多,多对多,多态,单表继承)。
  • 钩子函数,在Create/Save/Update/Delete/Find之前或者之后操作。
  • 使用Preload,Joins预加载
  • 事务,嵌套事务,保存点,回滚保存点
  • Context, Prepared Statement模式,DryRun模式
  • 批量插入,批量查询,使用Map查找和创建,使用SQL表达式和context valuer进行CRUD。
  • SQL构建器,UPSERT,加锁,优化器/索引/注释提示,命名参数,子查询
  • 复合主键,索引,约束。
  • 自动迁移,这里的迁移表示会根据struct类型创建数据库表。
  • 日志记录器
  • 可扩展,灵活的插件API:数据库解析器(多数据库,读写分离)
  • 每个功能都有测试
  • 开发者友好

安装

go get -u gorm.io/gorm  
go get -u gorm.io/driver/sqlite

快速启动

package main  
  
import (  
  "gorm.io/gorm"  
  "gorm.io/driver/sqlite"  
)  
  
type Product struct {  
  gorm.Model  
  Code  string  
  Price uint  
}  
  
func main() {  
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})  
  if err != nil {  
    panic("failed to connect database")  
  }  
  
  // Migrate the schema  
  db.AutoMigrate(&Product{})  
  
  // Create  
  db.Create(&Product{Code: "D42", Price: 100})  
  
  // Read  
  var product Product  
  db.First(&product, 1) // find product with integer primary key  
  db.First(&product, "code = ?", "D42") // find product with code D42  
  
  // Update - update product's price to 200  
  db.Model(&product).Update("Price", 200)  
  // Update - update multiple fields  
  db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // non-zero fields  
  db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})  
  
  // Delete - delete product  
  db.Delete(&product, 1)  
}

声明Model

模型是带有基本Go类型的普通结构体,它们的指针/别名或者自定义类型实现了ScannerValuer接口。 例如:

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  
}

约定

GORM是约定大于配置的,默认情况下,GORM使用ID作为主键,将结构体的名称转为蛇形命名作为数据库表的名字,列名同样这样处理。使用CreatedAt,UpdateAt来跟踪创建和更新时间。

遵循约定可以少些很多代码,如果约定不符合需求,也可以自己配置。

gorm.Model

这是GORM定义的结构体,其中包含了约定中提到的字段,可以将其嵌入到自己的结构体中。

// gorm.Model definition  
type Model struct {  
  ID        uint           `gorm:"primaryKey"`  
  CreatedAt time.Time  
  UpdatedAt time.Time  
  DeletedAt gorm.DeletedAt `gorm:"index"`  
}

只要遵循约定,那么这个嵌入这个结构体可以帮助节省很多工作。

进阶

字段级别权限

使用GORM进行CRUD的时候导出字段有所有的权限,GORM允许使用标签来改变字段级别的权限。因此可以设置字段为只读,只写,只能创建,只能更新或者忽略。

当使用GORM Migrator创建表时,不会创建被忽略的字段

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"` // 只能创建 
  Name string `gorm:"-"`            // 读写时忽略此字段
  Name string `gorm:"-:all"`        // 读写,migrate忽略此字段
  Name string `gorm:"-:migration"`  // migrate时忽略此字段  
}

Creating/Updating时间

GORM约定使用CreateAt,UpdateAt来跟踪创建和更新时间,只要设置了这两个字段,那么就会在对应的create或者update操作中将数据库中对应字段设置为当前时间。

要使用不同名称的字段,可以用autoCreateTime、autoUpdateTime标签配置这些字段。如果希望保存UNIX(Milli/Nano)秒而不是时间,则可以简单地更改字段的数据类型为int

type User struct {  
  CreatedAt time.Time // Set to current time if it is zero on creating  
  UpdatedAt int       // Set to current unix seconds on updating or if it is zero on creating  
  Updated   int64 `gorm:"autoUpdateTime:nano"` // Use unix nano seconds as updating time  
  Updated   int64 `gorm:"autoUpdateTime:milli"`// Use unix milli seconds as updating time  
  Created   int64 `gorm:"autoCreateTime"`      // Use unix seconds as creating time  
}

嵌入结构体

对于匿名字段,GORM会将它的字段包含到它的父结构体中,例如:

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  
}

对于普通的结构体字段,可以使用embedded标签,例如

type Author struct {  
  Name  string  
  Email string  
}  
  
type Blog struct {  
  ID      int  
  Author  Author `gorm:"embedded"`  
  Upvotes int32  
}
 

等价于:

type Blog struct {  
  ID    int64  
  Name  string  
  Email string  
  Upvotes  int32  
}

还可以使用embeddedPrefix标签来为嵌入的字段对应的db字段名加上前缀。例如

type Blog struct {  
  ID      int  
  Author  Author `gorm:"embedded;embeddedPrefix:author_"`  
  Upvotes int32  
}  
// equals  
type Blog struct {  
  ID          int64  
  AuthorName  string  
  AuthorEmail string  
  Upvotes     int32  
}

字段标签

在声明model的时候标签时可选的。标签时大小写敏感的,使用驼峰式命令风格,GORM支持下面的标签。

标签名描述
column数据库中列的字段名
type列的类型
serializer如何序列化和反序列化到数据库,例如serializer:json/gob/unixtime
size指定列的数据大小或者长度
primaryKey指定列为主键
unique指定列为unique约束
default指定列的默认值
precision指定列精度
scale指定列的数据范围
not null指定列非空
autoIncrement指定列自增的
autoIncrementIncrement指定列自增的步长
embedded嵌入字段
embeddedPrefix为嵌入字段设置列名前缀
autoCreateTime设置create时间为当前时间,如果类型为int,那么记录为unix秒,使用autoCreateTime:nano/milli来记录unix纳秒和毫秒。
autoUpdateTime在create/update的时候设置为当前时间,标签值的作用与上一个标签相同。
index创建索引,如果多个字段的此标签的值相同,那么会创建多字段的复合索引
uniqueIndex与index作用相同,但是创建唯一索引
check创建检查约束,例如:check:age > 13
设置字段的写权限,<-:create是只能创建的字段,<-:update只能更新,<-:false没有写权限,<-可以创建与更新
设置读权限,->:false表示没有读权限
-忽略此字段,-表示没有读写权限,-:migration没有迁移权限,-all没有读写迁移权限。
comment在migrate的时候为字段添加注释

关联标签

在关系型数据库中,表与表之间是存在关联关系的,GORM提供了关联标签,方便对表之间的关联关系进行设置。

有如下关联标签:

标签名描述
foreignKey指定外键。
references指定参考表的列名,该名称映射到联接表的外键
polymorphic指定多态类型,例如model名
polymorphicValue指定多态值,默认是表名
many2many指定连接表名
joinForeignKey指定映射到当前表的连接表的外键列名
joinReferences指定映射到参考表的连接表的外键列名
constraint关系约束,例如onUpdate,onDelete

连接数据库

GORM官方支持 MySQL, PostgreSQL, SQLite, SQL Server。

mysql

导入对应的包

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

设置dsn来连接数据库

func main() {  
  // refer https://github.com/go-sql-driver/mysql#dsn-data-source-name for details  
  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{})  
}

Note

为了正确处理time.Time,需要包含parseTime参数,同时为了全面支持utf8编码,将charset=utf8改为charset=utf8mb4

MYSQL驱动,支持在初始化的时候进行高级配置,例如:

db, err := gorm.Open(mysql.New(mysql.Config{  
  DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // 数据源的名称  
  DefaultStringSize: 256, // string字段的默认大小  
  DisableDatetimePrecision: true, // 禁用datetime精度,MYSQL5.6之前版本不支持  
  DontSupportRenameIndex: true, // 重命名索引先删除再创建,MYSQL5.7之前不支持  
  DontSupportRenameColumn: true, // `change` when rename column, rename column not supported before MySQL 8, MariaDB  
  SkipInitializeWithVersion: false, // 基于当前的MYSQL版本自动配置  
}), &gorm.Config{})

自定义驱动

GORM可以使用DriverName选项自定义MYSQL驱动,例如:

import (  
  _ "example.com/my_mysql_driver"  
  "gorm.io/gorm"  
)  
  
db, err := gorm.Open(mysql.New(mysql.Config{  
  DriverName: "my_mysql_driver",  
  DSN: "gorm:gorm@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True&loc=Local", // data source name, refer https://github.com/go-sql-driver/mysql#dsn-data-source-name  
}), &gorm.Config{})

已存在的数据库连接

GORM可以使用已经存在的数据库连接初始化*gorm.DB

import (  
  "database/sql"  
  "gorm.io/driver/mysql"  
  "gorm.io/gorm"  
)  
  
sqlDB, err := sql.Open("mysql", "mydb_dsn")  
gormDB, err := gorm.Open(mysql.New(mysql.Config{  
  Conn: sqlDB,  
}), &gorm.Config{})

PostgreSQL

使用与mysql基本相同,无非就是使用的驱动不同

import (  
  "gorm.io/driver/postgres"  
  "gorm.io/gorm"  
)  
  
dsn := "host=localhost user=gorm password=gorm dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai"  
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})

也可以像mysql一样使用自定义驱动和利用已建立连接。

连接池

GORM使用database/sql来维持连接池。

sqlDB, err := db.DB()  
  
// SetMaxIdleConns sets the maximum number of connections in the idle connection pool.  
sqlDB.SetMaxIdleConns(10)  
  
// SetMaxOpenConns sets the maximum number of open connections to the database.  
sqlDB.SetMaxOpenConns(100)  
  
// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.  
sqlDB.SetConnMaxLifetime(time.Hour)

tags: gorm