文章目录

  • 概述
    • 特性
    • 安装
    • 连接到数据库
      • 数据库配置
      • 自定义驱动
      • 现有的数据库连接
      • 连接池
    • 快速入门
  • 模型
    • gorm.Model
    • 模型定义
      • 嵌入结构体
      • 字段级权限控制
      • 时间追踪
    • 结构体标签
      • 字段标签
      • 关联标签
      • 示例
  • 约定
    • 主键
    • 表名
    • 列名
    • 时间戳跟踪
      • CreatedAt
      • UpdatedAt
      • DeletedAt
    • Gorm方法分类
      • 链式方法
      • Finisher Method
      • 新建会话模式

概述

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 nameDefaultStringSize: 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 associationsFullSaveAssociations bool// 日志配置Logger logger.Interface// NowFunc the function to be used when creating a new timestampNowFunc func() time.Time// 是否支持DryRun模式,只生成SQL,不执行DryRun bool// PrepareStmt executes the given query in cached statementPrepareStmt bool// DisableAutomaticPingDisableAutomaticPing bool// 迁移时禁用外键限制DisableForeignKeyConstraintWhenMigrating bool// 禁用嵌套事务DisableNestedTransaction bool// 是否允许全局修改(update语句不带任何条件),默认falseAllowGlobalUpdate bool// QueryFields executes the SQL query with all fields of the tableQueryFields bool// 默认批量创建的大小CreateBatchSize int// ClauseBuilders clause builderClauseBuilders map[string]clause.ClauseBuilder// 使用的连接池配置ConnPool ConnPool// Dialector database dialectorDialector// 注册插件Plugins map[string]Plugincallbacks  *callbackscacheStore *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 mainimport ("gorm.io/gorm""gorm.io/driver/sqlite"
)type Product struct {gorm.ModelCode  stringPrice uint
}func main() {db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})if err != nil {panic("failed to connect database")}// 迁移 schemadb.AutoMigrate(&Product{})// Createdb.Create(&Product{Code: "D42", Price: 100})// Readvar product Productdb.First(&product, 1) // 根据整形主键查找db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录// Update - 将 product 的 price 更新为 200db.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 - 删除 productdb.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.TimeUpdatedAt time.TimeDeletedAt *time.Time
}

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

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

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

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

模型定义

嵌入结构体

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

type User struct {gorm.ModelName string
}
// 等效于
type User struct {ID        uint           `gorm:"primaryKey"`CreatedAt time.TimeUpdatedAt time.TimeDeletedAt gorm.DeletedAt `gorm:"index"`Name string
}

对于正常的结构体字段,你也可以通过标签 embedded 将其嵌入,例如:

type Author struct {Name  stringEmail string
}type Blog struct {ID      intAuthor  Author `gorm:"embedded"`Upvotes int32
}
// 等效于
type Blog struct {ID    int64Name  stringEmail stringUpvotes  int32
}

并且,您可以使用标签 embeddedPrefix 来为 db 中的字段名添加前缀,例如:

type Blog struct {ID      intAuthor  Author `gorm:"embedded;embeddedPrefix:author_"`Upvotes int32
}
// 等效于
type Blog struct {ID          int64AuthorName  stringAuthorEmail stringUpvotes     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.ModelName         stringAge          sql.NullInt64Birthday     *time.TimeEmail        string  `gorm:"type:varchar(100);unique_index"`Role         string  `gorm:"size:255"` // 设置字段大小为255MemberNumber *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     stringAge      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/

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

  1. GraphQL实战-第一篇-GraphQL介绍

    GraphQL实战-第一篇-GraphQL介绍 GraphQL的前世今生 Facebook的业务线有移动端,PC端和其它端,不同的场景下对一个资源所需要的信息是不同的.如移动端需要User的a.b.c ...

  2. vue+uni-app商城实战 | 第一篇:从0到1快捷开发一个商城微信小程序,无缝接入OAuth2实现一键授权登录

    一. 前言 本篇通过实战来讲述如何使用uni-app快速进行商城微信小程序的开发以及小程序如何接入后台Spring Cloud微服务. 有来商城 youlai-mall 项目是一套全栈商城系统,技术栈 ...

  3. 微信小程序商城项目实战(第一篇:项目搭建与首页)

    商城项目第一篇 项目搭建 项目结构 编写整个项目中需要用到的功能 request.js 全局样式 组件(搜索框) 首页 代码编写 效果图 项目搭建 后端接口:https://www.showdoc.c ...

  4. Golang编程基础第一篇——Golang快入门

    目录 一,为什么我们需要一门新语言 二,顺序编程 2.1 变量 2.2 常量 2.3 类型 2.4 流程控制 2.5 函数 2.6 错误处理 (golang的错误处理适合单出一篇) 以go语言编程为基 ...

  5. 【Linux入门指北】第一篇 初识Linux

    目录 前言 一.Linux操作系统的发展历史 1.Linux操作系统的诞生 2.Linux操作系统的发展 1.自由软件基金会(FSF) 2.GPL协议 3.GUN工程 二.Linux的不同发行版本 1 ...

  6. hmcl整合包导入_SSM实战第一篇_SSM的整合

    业务背景:在JavaWeb应用开发中,经常需要将应用系统中某些业务数据导出到Excel中,又或者需要将这些业务数据先收集到Excel然后一键导入到系统. 业务需求:如何用Java实现导入导出Excel ...

  7. Cocos Studio学习笔记实战第一篇-我们山寨一个那年那兔那些事(看效果)

    学习了Cocos Studio大概一周,正赶上十一长假,于是乎想利用刚刚学习的东西巩固一下这几天的学习成果,9月份一直在玩手游<那年那兔那些事>,所以图方便,就把那年那兔那些事的apk报解 ...

  8. Android实战第一篇——时钟+闹钟+计时器+秒表

    学习了快一学期的Android了,之前的知识点都是零散的学习的,只有当我们真正的去把他们用起来的时候才会发现难点,自己才会独立尝试去解决某个问题.接下来是我的一个简单的多功能时钟的小实战(视频资源ht ...

  9. spring el表达式解析_Spring之旅第一篇-初识Spring

    目录 一.概述 二.模块 三.动手创建 一.概述 只要用框架开发java,一定躲不过spring,Spring是一个轻量级的Java开源框架,存在的目的是用于构建轻量级的J2EE应用.Spring的核 ...

  10. 【C初阶】第一篇——初识C语言(万字篇,带你敲响C语言的大门)

    接受平凡,努力出众,承认普通,但拒绝沉沦于平庸.大家好,我是你们的老朋友,小KK.  满满的 ​前言 ​什么C语言? ​数据类型 ​变量和常量 ​定义变量的方法 ​变量的分类 ​变量的作用域和生命周期 ...

最新文章

  1. 在局域网访问_管理Windows访问凭证,快速访问局域网上的共享资源
  2. 得到当前数据库中所有用户表信息
  3. [ js ] 可否用多线程的思路,解决大数量数据的性能问题?
  4. python fieldnames_csvreader.fieldnames在python中未被识别为csv reader对象的属性
  5. 使用try-with-resources优雅的关闭IO流
  6. java 改变文件权限_Java文件权限
  7. C# 获取汉语拼音全码及简码
  8. 【小专题】正交试验法设计测试用例
  9. PHP解密小程序加密信息
  10. 英雄联盟lol鼠标突然不能a兵了
  11. Linux常用 bash命令
  12. 【HiFlow】腾讯云新一代自动化助手,我用它完成了企业疫情提示(无代码)
  13. 二.MUI框架 开始体验MUI
  14. Oracle 小数格式化字符串显示 (转)
  15. 百度地图AK鉴权说明与白名单设置方法
  16. 如何合并pdf文件?多pdf文件合并技能分享
  17. 用Javascript开发《三国志曹操传》-开源讲座(二)-人物行走的实现
  18. 什么翻译软件能拍照翻译?这些翻译软件可以拍照翻译
  19. 预制墙板一般包括哪些类型?
  20. linux把分区搞成了raw,硬盘分区突然变RAW?手把手教你如何自救

热门文章

  1. python操作yaml文件
  2. JAVA8 filter对List筛选、排序报nullPointException
  3. ac2100刷openwrt 2.4g wifi只有130m速率
  4. 《C Primer Plus》第四章 编程习题
  5. 洛谷P2181——对角线
  6. 图像语义分割——抠图勾画
  7. MySQL十表联查快速得到结果_MySQL 多表联查
  8. 叫男朋友猪头的四大理由
  9. 电脑怎么压缩图片大小kb?图片太大怎么缩小kb?
  10. 一次看懂迭代与递归的区别