【GoLang】《GORM实战》第一篇:初识GORM框架

概述

The fantastic ORM library for Golang aims to be developer friendly.

一个致力于开发者友好、极好的Golang ORM类库。

特性

  • 全功能 ORM
  • 关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
  • Create,Save,Update,Delete,Find 中钩子方法
  • 支持 PreloadJoins 的预加载
  • 事务,嵌套事务,Save Point,回滚到 Saved Point
  • Context、预编译模式、DryRun 模式
  • 批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
  • SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询
  • 复合主键,索引,约束
  • Auto Migration
  • 自定义 Logger
  • 灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
  • 每个特性都经过了测试的重重考验
  • 开发者友好

安装

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

连接到数据库

GORM 官方支持的数据库类型有: MySQL, PostgreSQL, SQlite, SQL Server。

有些数据库可能兼容 mysqlpostgres 的方言,在这种情况下,你可以直接使用这些数据库的方言。

数据库配置

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

func main() {
    
    
  // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
  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{
    
    })
}

注意:想要正确的处理 time.Time ,您需要带上 parseTime 参数, (更多参数) 要支持完整的 UTF-8 编码,您需要将 charset=utf8 更改为 charset=utf8mb4 查看 此文章 获取详情

MySQL驱动程序提供了 一些高级配置 可以在初始化过程中使用,例如:

这里的配置是针对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", // DSN data source name
  DefaultStringSize: 256, // string 类型字段的默认长度
  DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
  DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
  DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
  SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
}), &gorm.Config{
    
    })

还有一个配置:gorm.Config

// Config GORM config
type Config struct {
    
    
	// GORM perform single create, update, delete operations in transactions by default to ensure database data integrity
	// 是否跳过默认的事务
	SkipDefaultTransaction bool
	// 命名策略:表、列
	NamingStrategy schema.Namer
	// FullSaveAssociations full save associations
	FullSaveAssociations bool
	// 日志配置
	Logger logger.Interface
	// NowFunc the function to be used when creating a new timestamp
	NowFunc func() time.Time
	// 是否支持DryRun模式,只生成SQL,不执行
	DryRun bool
	// PrepareStmt executes the given query in cached statement
	PrepareStmt bool
	// DisableAutomaticPing
	DisableAutomaticPing bool
	// 迁移时禁用外键限制
	DisableForeignKeyConstraintWhenMigrating bool
	// 禁用嵌套事务
	DisableNestedTransaction bool
	// 是否允许全局修改(update语句不带任何条件),默认false
	AllowGlobalUpdate bool
	// QueryFields executes the SQL query with all fields of the table
	QueryFields bool
	// 默认批量创建的大小
	CreateBatchSize int

	// ClauseBuilders clause builder
	ClauseBuilders map[string]clause.ClauseBuilder
	// 使用的连接池配置
	ConnPool ConnPool
	// Dialector database dialector
	Dialector
	// 注册插件
	Plugins map[string]Plugin

	callbacks  *callbacks
	cacheStore *sync.Map
}

自定义驱动

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,参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name
}), &gorm.Config{
    
    })

现有的数据库连接

GORM 允许通过一个现有的数据库连接来初始化 *gorm.DB

import (
  "database/sql"
  "gorm.io/gorm"
)

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

连接池

GORM 使用 database/sql 维护连接池

sqlDB, err := db.DB()

// SetMaxIdleConns 设置空闲连接池中连接的最大数量
sqlDB.SetMaxIdleConns(10)

// SetMaxOpenConns 设置打开数据库连接的最大数量。
sqlDB.SetMaxOpenConns(100)

// SetConnMaxLifetime 设置了连接可复用的最大时间。
sqlDB.SetConnMaxLifetime(time.Hour)

查看 通用接口 获取详情。

快速入门

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")
  }

  // 迁移 schema
  db.AutoMigrate(&Product{
    
    })

  // Create
  db.Create(&Product{
    
    Code: "D42", Price: 100})

  // Read
  var product Product
  db.First(&product, 1) // 根据整形主键查找
  db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录

  // Update - 将 product 的 price 更新为 200
  db.Model(&product).Update("Price", 200)
  // Update - 更新多个字段
  db.Model(&product).Updates(Product{
    
    Price: 200, Code: "F42"}) // 仅更新非零值字段
  db.Model(&product).Updates(map[string]interface{
    
    }{
    
    "Price": 200, "Code": "F42"})

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

模型

在使用ORM工具时,通常我们需要在代码中定义模型(Models)与数据库中的数据表进行映射,在GORM中模型(Models)是标准的 struct,可以包含:

  • Go 的基本数据类型及其指针,如string、int、*string

  • 实现了Scanner和Valuer接口的自定义类型及其指针或别名组成,如:sql.NullInt64、*User

gorm.Model

为了方便模型定义,GORM内置了一个gorm.Model结构体。gorm.Model是一个包含了ID, CreatedAt, UpdatedAt, DeletedAt四个字段的Golang结构体。

// gorm.Model 定义
type Model struct {
    
    
  ID        uint `gorm:"primary_key"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt *time.Time
}

你可以将它嵌入到你自己的模型中:

// 将 `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt`字段注入到`User`模型中
type User struct {
    
    
  gorm.Model
  Name string
}

当然你也可以完全自己定义模型:

// 不使用gorm.Model,自行定义模型
type User struct {
    
    
  ID   int
  Name string
}

模型定义

嵌入结构体

对于匿名字段,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
}
// 等效于
type Blog struct {
    
    
  ID          int64
  AuthorName  string
  AuthorEmail string
  Upvotes     int32
}

字段级权限控制

可导出的字段在使用 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"` // 仅创建(禁止从 db 读)
  Name string `gorm:"-"`  // 通过 struct 读写会忽略该字段
}

时间追踪

GORM 约定使用 CreatedAtUpdatedAt 追踪创建/更新时间。如果您定义了这种字段,GORM 在创建、更新时会自动填充当前时间

要使用不同名称的字段,您可以配置 autoCreateTimeautoUpdateTime 标签

如果您想要保存 UNIX(毫/纳)秒时间戳,而不是 time,您只需简单地将 time.Time 修改为 int 即可

type User struct {
    
    
  CreatedAt time.Time // 在创建时,如果该字段值为零值,则使用当前时间填充
  UpdatedAt int       // 在创建时该字段值为零值或者在更新时,使用当前时间戳秒数填充
  Updated   int64 `gorm:"autoUpdateTime:nano"` // 使用时间戳填纳秒数充更新时间
  Updated   int64 `gorm:"autoUpdateTime:milli"` // 使用时间戳毫秒数填充更新时间
  Created   int64 `gorm:"autoCreateTime"`      // 使用时间戳秒数填充创建时间
}

结构体标签

使用结构体声明模型时,标记(tags)是可选项。gorm支持以下标记:

字段标签

声明 model 时,tag 是可选的,GORM 支持以下 tag: tag 名大小写不敏感,但建议使用 camelCase 风格

标签名 说明
column 指定 db 列名
type 列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not nullsize, autoIncrement… 像 varbinary(8) 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT
size 指定列大小,例如:size:256
primaryKey 指定列为主键
unique 指定列为唯一
default 指定列的默认值
precision 指定列的精度
scale 指定列大小
not null 指定列为 NOT NULL
autoIncrement 指定列为自动增长
autoIncrementIncrement 自动步长,控制连续记录之间的间隔
embedded 嵌套字段
embeddedPrefix 嵌入字段的列名前缀
autoCreateTime 创建时追踪当前时间,对于 int 字段,它会追踪秒级时间戳,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nano
autoUpdateTime 创建/更新时追踪当前时间,对于 int 字段,它会追踪秒级时间戳,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoUpdateTime:milli
index 根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看 索引 获取详情
uniqueIndex index 相同,但创建的是唯一索引
check 创建检查约束,例如 check:age > 13,查看 约束 获取详情
<- 设置字段写入的权限, <-:create 只创建、<-:update 只更新、<-:false 无写入权限、<- 创建和更新权限
-> 设置字段读的权限,->:false 无读权限
- 忽略该字段,- 无读写权限
comment 迁移时为字段添加注释

关联标签

标签 描述
foreignKey 指定当前模型的列作为连接表的外键
references 指定引用表的列名,其将被映射为连接表外键
polymorphic 指定多态类型,比如模型名
polymorphicValue 指定多态值、默认表名
many2many 指定连接表表名
joinForeignKey 指定连接表的外键列名,其将被映射到当前表
joinReferences 指定连接表的外键列名,其将被映射到引用表
constraint 关系约束,例如:OnUpdateOnDelete

示例

type User struct {
    
    
  gorm.Model
  Name         string
  Age          sql.NullInt64
  Birthday     *time.Time
  Email        string  `gorm:"type:varchar(100);unique_index"`
  Role         string  `gorm:"size:255"` // 设置字段大小为255
  MemberNumber *string `gorm:"unique;not null"` // 设置会员号(member number)唯一并且不为空
  Num          int     `gorm:"AUTO_INCREMENT"` // 设置 num 为自增类型
  Address      string  `gorm:"index:addr"` // 给address字段创建名为addr的索引
  IgnoreMe     int     `gorm:"-"` // 忽略本字段
}

约定

GORM 倾向于约定,而不是配置。默认情况下,GORM 使用 ID 作为主键,使用结构体名的 蛇形复数 (下划线连接)作为表名,字段名的 蛇形 作为列名,并使用 CreatedAtUpdatedAt 字段追踪创建、更新时间。

遵循 GORM 已有的约定,可以减少您的配置和代码量。如果约定不符合您的需求,可以自定义配置它们(通过tag或NamingStrategy配置)

主键

GORM 默认会使用名为ID的字段作为表的主键。

type User struct {
    
    
  ID   string // 名为`ID`的字段会默认作为表的主键
  Name string
}

// 使用`AnimalID`作为主键
type Animal struct {
    
    
  AnimalID int64 `gorm:"primary_key"`
  Name     string
  Age      int64
}

表名

表名默认就是结构体名称的复数,例如:

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"
  }
}

// 禁用默认表名的复数形式,如果置为 true,则 `User` 的默认表名是 `user`
db.SingularTable(true)

也可以通过Table()指定表名:

// 使用User结构体创建名为`deleted_users`的表
db.Table("deleted_users").CreateTable(&User{
    
    })

var deleted_users []User
db.Table("deleted_users").Find(&deleted_users)
 SELECT * FROM deleted_users;

db.Table("deleted_users").Where("name = ?", "lixiaoyao").Delete()
 DELETE FROM deleted_users WHERE name = 'lixiaoyao';

GORM还支持更改默认表名称规则:

gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string  {
    
    
  return "prefix_" + defaultTableName;
}

列名

列名由字段名称进行下划线分割来生成

type User struct {
    
    
  ID        uint      // column name is `id`
  Name      string    // column name is `name`
  Birthday  time.Time // column name is `birthday`
  CreatedAt time.Time // column name is `created_at`
}

您可以使用 column 标签或命名策略来覆盖列名

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`
}
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    
    
  NamingStrategy: schema.NamingStrategy{
    
    
    TablePrefix: "t_",   // 表名前缀,`User`表为`t_users`
    SingularTable: true, // 使用单数表名,启用该选项后,`User` 表将是`user`
    NameReplacer: strings.NewReplacer("CID", "Cid"), // 在转为数据库名称之前,使用NameReplacer更改结构/字段名称。
  },
})

时间戳跟踪

CreatedAt

如果模型有 CreatedAt字段,该字段的值将会是初次创建记录的时间。

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

user2 := User{
    
    Name: "jinzhu", CreatedAt: time.Now()}
db.Create(&user2) // user2 的 `CreatedAt` 不会被修改

// 想要修改该值,您可以使用 `Update`
db.Model(&user).Update("CreatedAt", time.Now())

UpdatedAt

如果模型有UpdatedAt字段,该字段的值将会是每次更新记录的时间。

db.Save(&user) // 将 `UpdatedAt` 设为当前时间

db.Model(&user).Update("name", "jinzhu") // 会将 `UpdatedAt` 设为当前时间

db.Model(&user).UpdateColumn("name", "jinzhu") // `UpdatedAt` 不会被修改

user2 := User{
    
    Name: "jinzhu", UpdatedAt: time.Now()}
db.Create(&user2) // 创建记录时,user2 的 `UpdatedAt` 不会被修改

user3 := User{
    
    Name: "jinzhu", UpdatedAt: time.Now()}
db.Save(&user3) // 更新时,user3 的 `UpdatedAt` 会修改为当前时间

DeletedAt

如果模型有DeletedAt字段,调用Delete删除该记录时,将会设置DeletedAt字段为当前时间,而不是直接将记录从数据库中删除。

数据库会根据这个字段的类型,应用不同的规则作为删除标识

  • 默认 gorm.Model中的为time.Time类型,对应时间类型

  • unix时间戳类型

    DeletedAt soft_delete.DeletedAt `gorm:"softDelete:milli"`
    
  • 0/1

    IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
    

后面两种方式需要引入:gorm.io/plugin/soft_delete,在后面的章节会说

Gorm方法分类

GORM 允许进行链式操作,所以您可以像这样写代码:

db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user)

GORM 中有三种类型的方法: 链式方法Finisher 方法新建会话方法

链式方法

链式方法是将 Clauses 修改或添加到当前 Statement 的方法,例如:

Where, Select, Omit, Joins, Scopes, Preload, Raw

注意:如果使用Raw方法,即使用原生SQL的方式,便不能与其他的链式方法组合使用

Finisher Method

Finishers 是会立即执行注册回调的方法,然后生成并执行 SQL,也称作立即执行方法,比如这些方法:

Create, First, Find, Take, Save, Update, Delete, Scan, Row, Rows

新建会话模式

在初始化了 *gorm.DB新建会话方法 后, 调用下面的方法会创建一个新的 Statement 实例而不是使用当前的

GROM 定义了 SessionWithContextDebug 方法做为 `新建会话方法

这种模式可以避免Statement复用,可以用在需要考虑线程安全的场景。

参考:
https://gorm.io/docs/index.html
https://www.liwenzhou.com/posts/Go/gorm/

Guess you like

Origin blog.csdn.net/sinat_14840559/article/details/121494806