【GoLang】《GORM实战》第一篇:初识GORM框架
文章目录
- 概述
- 特性
- 安装
- 连接到数据库
- 数据库配置
- 自定义驱动
- 现有的数据库连接
- 连接池
- 快速入门
- 模型
- 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 中钩子方法
- 支持
Preload
、Joins
的预加载 - 事务,嵌套事务,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。
有些数据库可能兼容 mysql
、postgres
的方言,在这种情况下,你可以直接使用这些数据库的方言。
数据库配置
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 约定使用 CreatedAt
、UpdatedAt
追踪创建/更新时间。如果您定义了这种字段,GORM 在创建、更新时会自动填充当前时间
要使用不同名称的字段,您可以配置 autoCreateTime
、autoUpdateTime
标签
如果您想要保存 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 null 、size , 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 |
关系约束,例如:OnUpdate 、OnDelete
|
示例
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
作为主键,使用结构体名的 蛇形复数
(下划线连接)作为表名,字段名的 蛇形
作为列名,并使用 CreatedAt
、UpdatedAt
字段追踪创建、更新时间。
遵循 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 定义了 Session
、WithContext
、Debug
方法做为 `新建会话方法
这种模式可以避免Statement复用,可以用在需要考虑线程安全的场景。
参考:
https://gorm.io/docs/index.html
https://www.liwenzhou.com/posts/Go/gorm/
【GoLang】《GORM实战》第一篇:初识GORM框架相关推荐
- GraphQL实战-第一篇-GraphQL介绍
GraphQL实战-第一篇-GraphQL介绍 GraphQL的前世今生 Facebook的业务线有移动端,PC端和其它端,不同的场景下对一个资源所需要的信息是不同的.如移动端需要User的a.b.c ...
- vue+uni-app商城实战 | 第一篇:从0到1快捷开发一个商城微信小程序,无缝接入OAuth2实现一键授权登录
一. 前言 本篇通过实战来讲述如何使用uni-app快速进行商城微信小程序的开发以及小程序如何接入后台Spring Cloud微服务. 有来商城 youlai-mall 项目是一套全栈商城系统,技术栈 ...
- 微信小程序商城项目实战(第一篇:项目搭建与首页)
商城项目第一篇 项目搭建 项目结构 编写整个项目中需要用到的功能 request.js 全局样式 组件(搜索框) 首页 代码编写 效果图 项目搭建 后端接口:https://www.showdoc.c ...
- Golang编程基础第一篇——Golang快入门
目录 一,为什么我们需要一门新语言 二,顺序编程 2.1 变量 2.2 常量 2.3 类型 2.4 流程控制 2.5 函数 2.6 错误处理 (golang的错误处理适合单出一篇) 以go语言编程为基 ...
- 【Linux入门指北】第一篇 初识Linux
目录 前言 一.Linux操作系统的发展历史 1.Linux操作系统的诞生 2.Linux操作系统的发展 1.自由软件基金会(FSF) 2.GPL协议 3.GUN工程 二.Linux的不同发行版本 1 ...
- hmcl整合包导入_SSM实战第一篇_SSM的整合
业务背景:在JavaWeb应用开发中,经常需要将应用系统中某些业务数据导出到Excel中,又或者需要将这些业务数据先收集到Excel然后一键导入到系统. 业务需求:如何用Java实现导入导出Excel ...
- Cocos Studio学习笔记实战第一篇-我们山寨一个那年那兔那些事(看效果)
学习了Cocos Studio大概一周,正赶上十一长假,于是乎想利用刚刚学习的东西巩固一下这几天的学习成果,9月份一直在玩手游<那年那兔那些事>,所以图方便,就把那年那兔那些事的apk报解 ...
- Android实战第一篇——时钟+闹钟+计时器+秒表
学习了快一学期的Android了,之前的知识点都是零散的学习的,只有当我们真正的去把他们用起来的时候才会发现难点,自己才会独立尝试去解决某个问题.接下来是我的一个简单的多功能时钟的小实战(视频资源ht ...
- spring el表达式解析_Spring之旅第一篇-初识Spring
目录 一.概述 二.模块 三.动手创建 一.概述 只要用框架开发java,一定躲不过spring,Spring是一个轻量级的Java开源框架,存在的目的是用于构建轻量级的J2EE应用.Spring的核 ...
- 【C初阶】第一篇——初识C语言(万字篇,带你敲响C语言的大门)
接受平凡,努力出众,承认普通,但拒绝沉沦于平庸.大家好,我是你们的老朋友,小KK. 满满的 前言 什么C语言? 数据类型 变量和常量 定义变量的方法 变量的分类 变量的作用域和生命周期 ...
最新文章
- 在局域网访问_管理Windows访问凭证,快速访问局域网上的共享资源
- 得到当前数据库中所有用户表信息
- [ js ] 可否用多线程的思路,解决大数量数据的性能问题?
- python fieldnames_csvreader.fieldnames在python中未被识别为csv reader对象的属性
- 使用try-with-resources优雅的关闭IO流
- java 改变文件权限_Java文件权限
- C# 获取汉语拼音全码及简码
- 【小专题】正交试验法设计测试用例
- PHP解密小程序加密信息
- 英雄联盟lol鼠标突然不能a兵了
- Linux常用 bash命令
- 【HiFlow】腾讯云新一代自动化助手,我用它完成了企业疫情提示(无代码)
- 二.MUI框架 开始体验MUI
- Oracle 小数格式化字符串显示 (转)
- 百度地图AK鉴权说明与白名单设置方法
- 如何合并pdf文件?多pdf文件合并技能分享
- 用Javascript开发《三国志曹操传》-开源讲座(二)-人物行走的实现
- 什么翻译软件能拍照翻译?这些翻译软件可以拍照翻译
- 预制墙板一般包括哪些类型?
- linux把分区搞成了raw,硬盘分区突然变RAW?手把手教你如何自救