gorm框架概述
github地址:https://github.com/go-gorm/gorm
中文文档地址:https://gorm.io/zh_CN/
什么是gorm
gorm是一个go语言开发出一个出色的orm库,旨在对开发者友好
gorm的特点
- 全功能 ORM
- 关联 (拥有一个,拥有多个,属于,多对多,多态,单表继承)
- Create,Save,Update,Delete,Find 中钩子方法
- 支持 Preload、Joins 的预加载
- 事务,嵌套事务,Save Point,Rollback To to Saved Point
- Context、预编译模式、DryRun 模式
- 批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
- SQL 构建器,Upsert,锁,Optimizer/Index/Comment Hint,命名参数,子查询
- 复合主键,索引,约束
- 自动迁移
- 自定义 Logger
- 灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
- 每个特性都经过了测试的重重考验
- 开发者友好
什么是orm
orm称之为:对象关系模型映射。全名是Object Relation Mapping。
orm的作用是把数据库中的字段映射到go的对象(通常是结构体)的过程。
快速入门
- 创建一个项目,初始化gomod文件
- 在终端输入命令
// 安装gorm库
go get -u gorm.io/gorm
// 安装mysql驱动
go get -u gorm.io/driver/mysql
- 数据库连接信息
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
// 1:配置数据库连接信息
var (
username string = "root"
password string = "123456"
host string = "127.0.0.1"
port int = 3306
dbname string = "gorm-test"
)
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
username, password, host, port, dbname)
// 2:连接数据库
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("数据库连接错误,err=" + err.Error())
}
fmt.Println(db)
}
官网入门案例
将数据库的连接信息提取成一个公共的方法
/dbn/dbconn.go
package dbn
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func DbConn() *gorm.DB {
// 1:配置数据库连接信息
var (
username string = "root"
password string = "123456"
host string = "127.0.0.1"
port int = 3306
dbname string = "gorm-test"
)
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
username, password, host, port, dbname)
// 2:连接数据库
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("数据库连接错误,err=" + err.Error())
}
return db
}
案例代码:
package main
import (
"gorm.io/gorm"
"orm/gorm-study/dbn"
)
type Product struct {
gorm.Model
Code string
Price uint
}
func main() {
db := dbn.DbConn()
// 在数据库中同步表
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)
}
声明模型
模型定义
模型是标准的 struct,由 Go 的基本数据类型、实现了 Scanner 和 Valuer 接口的自定义类型及其指针或别名组成
例如:
type User struct {
// 字段名为“ID”,会被设定为主键,自增长
ID int64
Name string
Email string
Age uint8
Birthday *time.Time
CreateTime time.Time
UpdateTime time.Time
}
约定
GORM 倾向于约定优于配置 默认情况下,GORM 使用
ID
作为主键,使用结构体名的
蛇形复数
作为表名(会在最后加上s,如果表名以数字结尾,不会加上s),字段名的
蛇形
作为列名,并使用
CreatedAt
、
UpdatedAt
字段追踪创建、更新时间
如果您遵循 GORM 的约定,您就可以少写的配置、代码。 如果约定不符合您的实际要求,GORM 允许你配置它们
gorm.Model
GORM 定义一个
gorm.Model
结构体,其包括字段
ID
、
CreatedAt
、
UpdatedAt
、
DeletedAt
// gorm.Model 的定义
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
您可以将它嵌入到您的结构体中,以包含这几个字段
字段标签
声明 model 时,tag 是可选的,GORM 支持以下 tag: tag 名大小写不敏感,但建议使用
camelCase
风格
标签名 | 说明 |
---|---|
column | 指定 db 列名 |
type |
列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:
、
,
… 像
这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:
|
serializer |
指定将数据序列化或反序列化到数据库中的序列化器, 例如:
|
size |
定义列数据类型的大小或长度,例如
|
primaryKey | 将列定义为主键 |
unique | 将列定义为唯一键 |
default | 定义列的默认值 |
precision | 指定列的精度 |
scale | 指定列大小 |
not null | 指定列为 NOT NULL |
autoIncrement | 指定列为自动增长 |
autoIncrementIncrement | 自动步长,控制连续记录之间的间隔 |
embedded | 嵌套字段 |
embeddedPrefix | 嵌入字段的列名前缀 |
autoCreateTime |
创建时追踪当前时间,对于
字段,它会追踪时间戳秒数,您可以使用
/
来追踪纳秒、毫秒时间戳,例如:
|
autoUpdateTime |
创建/更新时追踪当前时间,对于
字段,它会追踪时间戳秒数,您可以使用
/
来追踪纳秒、毫秒时间戳,例如:
|
index |
根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看 索引 获取详情 |
uniqueIndex |
与
相同,但创建的是唯一索引 |
check |
创建检查约束,例如
,查看 约束 获取详情 |
<- |
设置字段写入的权限,
只创建、
只更新、
无写入权限、
创建和更新权限 |
-> |
设置字段读的权限,
无读权限 |
– |
忽略该字段,
表示无读写,
表示无迁移权限,
表示无读写迁移权限 |
comment | 迁移时为字段添加注释 |
自定义表名
TableName
可以实现
Tabler
接口来更改默认表名
func (User) TableName() string {
return "zs_user"
}
增删改查
定义结构体:/model/User.go
package model
import "time"
type User struct {
ID uint64 `gorm:"comment:主键;"`
UserName string `gorm:"comment:用户名;type:varchar(100);index"`
PassWord string `gorm:"comment:密码;type:varchar(80)"`
Avatar string `gorm:"comment:头像;type:varchar(200)"`
Email string `gorm:"comment:邮箱;type:varchar(128)"`
Age uint8 `gorm:"comment:年龄;type:int(3);not null;default:18"`
Amount float32 `gorm:"comment:余额;precision:10;scale:2;default:0"`
Birthday time.Time `gorm:"comment:生日;type:date"`
CreateTime time.Time `gorm:"comment:创建时间;autoCreateTime"`
UpdateTime time.Time `gorm:"comment:修改时间;autoUpdateTime"`
}
数据库连接信息:/dbn/dbconn.go
package dbn
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func DbConn() *gorm.DB {
// 1:配置数据库连接信息
var (
username string = "root"
password string = "123456"
host string = "127.0.0.1"
port int = 3306
dbname string = "gorm-test"
)
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
username, password, host, port, dbname)
// 2:连接数据库
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("数据库连接错误,err=" + err.Error())
}
return db
}
增加数据
package main
import (
"fmt"
"orm/gorm-study/dbn"
"orm/gorm-study/model"
"time"
)
func main() {
// 1:连接数据库
db := dbn.DbConn()
// 2:同步结构体到数据库表
db.AutoMigrate(&model.User{})
// 3:增加数据
user := model.User{
UserName: "zhangsan",
PassWord: "123456",
Avatar: "a.txt",
Email: "1111@qq.com",
Age: 18,
Amount: 1.11,
Birthday: time.Now(),
}
// 返回结果:gorm.DB类型
result := db.Create(&user)
// 错误信息
fmt.Println(result.Error)
// 受影响的行数
fmt.Println(result.RowsAffected)
}
-
添加数据使用
Create()
方法,返回结果是一个
gorm.DB
type DB struct {
*Config
Error error
RowsAffected int64
Statement *Statement
clone int
}
如果想要打印执行的sql语句,加入
Debug()
方法
result := db.Debug().Create(&user)
查询数据
查询单条数据
GORM 提供了
First
、
Take
、
Last
方法,以便从数据库中检索单个对象。当查询数据库时它添加了
LIMIT 1
条件,且没有找到记录时,它会返回
ErrRecordNotFound
错误
package main
import (
"errors"
"fmt"
"gorm.io/gorm"
"orm/gorm-study/dbn"
"orm/gorm-study/model"
)
func main() {
// 1:连接数据库
db := dbn.DbConn()
// 2:同步结构体到数据库表
db.AutoMigrate(&model.User{})
user := model.User{}
// 获取第一条记录(主键升序)
// SELECT * FROM users ORDER BY id LIMIT 1;
db.First(&user)
fmt.Println(user)
// 获取一条记录,没有指定排序字段
// SELECT * FROM users LIMIT 1;
db.Take(&user)
fmt.Println(user)
// 获取最后一条记录(主键降序)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
db.Last(&user)
fmt.Println(user)
// 返回找到的记录数
result := db.First(&user)
fmt.Println(result.RowsAffected)
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
}
where查询
// where查询
// SELECT * FROM `users` WHERE `id` = 1
result := db.Debug().Where("id", 1).Find(&user)
fmt.Println(user)
-
where()
第一个参数采用占位符的方式
// where查询
// SELECT * FROM `users` WHERE id = 1 and user_name = 'zhangsan'
result := db.Debug().Where("id = ? and user_name = ?", 1, "zhangsan").Find(&user)
fmt.Println(user)
-
使用
Find()
// where查询
// SELECT * FROM `users` WHERE `users`.`id` IN (1,2)
result := db.Debug().Find(&user, 1, 2)
fmt.Println(user)
新增的参数为查询数据的主键的条件
查询多个数据
使用结构体切片
users := []model.User{}
result:=db.Debug().Find(&users)
fmt.Println(result.RowsAffected)
更新数据
save方法
user := model.User{}
db.Find(&user, 1)
user.UserName = "lisi"
db.Save(&user)
注意:
- save在执行sql时会包含所有字段,即时字段没有被修改,所以最好先把要修改的用户查询出来,避免没有修改的值被覆盖
update方法
user := model.User{}
// UPDATE `users` SET `user_name`='lisi1',`update_time`='2023-02-28 21:20:08.968' WHERE `id` = 1
result := db.Model(&user).Debug().Where("id", 1).Update("user_name", "lisi1")
fmt.Println(result.RowsAffected)
- 根据用户修改指定字段
user := model.User{}
user.UserName = "lisi1"
user.Age = 100
// UPDATE `users` SET `user_name`='lisi1',`age`=100,`update_time`='2023-02-28 21:23:26.069' WHERE `id` = 1
result := db.Model(&user).Debug().Where("id", 1).Updates(&user)
fmt.Println(result.RowsAffected)
- 根据map修改指定字段
user := model.User{}
userMap := make(map[string]interface{})
userMap["user_name"] = "lisi1"
userMap["age"] = 11
// UPDATE `users` SET `age`=11,`user_name`='lisi1',`update_time`='2023-02-28 21:27:28.021' WHERE `id` = 1
result := db.Model(&user).Debug().Where("id", 1).Updates(userMap)
fmt.Println(result.RowsAffected)
删除数据
user := model.User{}
// DELETE FROM `users` WHERE `id` = 1
result := db.Debug().Where("id", 1).Delete(&user)
fmt.Println(result.Error)
fmt.Println(result.RowsAffected)
或者写成
user := model.User{}
// DELETE FROM `users` WHERE `id` = 1
result := db.Debug().Delete(&user, "id", 1)
fmt.Println(result.Error)
fmt.Println(result.RowsAffected)
like查询
users := []model.User{}
// SELECT * FROM `users` WHERE user_name like '%zhangsan%'
result := db.Debug().Where("user_name like ?", "%zhangsan%").Find(&users)
for _, user := range users {
fmt.Println(user)
}
fmt.Println(result.Error)
fmt.Println(result.RowsAffected)
查询指定列
users := []model.User{}
// SELECT `id`,`user_name`,`age` FROM `users`
result := db.Debug().Select("id", "user_name", "age").Find(&users)
for _, user := range users {
fmt.Println(user)
}
fmt.Println(result.Error)
fmt.Println(result.RowsAffected)
排除指定列,查询剩余列
users := []model.User{}
// SELECT `users`.`id`,`users`.`user_name`,`users`.`pass_word`,`users`.`avatar`,`users`.`email`,`users`.`age`,`users`.`amount`,`users`.`birthday` FROM `users`
result := db.Debug().Omit("create_time", "update_time").Find(&users)
for _, user := range users {
fmt.Println(user)
}
fmt.Println(result.Error)
fmt.Println(result.RowsAffected)
分页查询
users := []model.User{}
// SELECT * FROM `users` LIMIT 5 OFFSET 1
result := db.Debug().Offset(0).Limit(5).Find(&users)
for _, user := range users {
fmt.Println(user)
}
fmt.Println(result.Error)
fmt.Println(result.RowsAffected)
从第1条数据开始查,查询出5条数据
钩子函数
对象生命周期
Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数。
如果您已经为模型定义了指定的方法,它会在创建、更新、查询、删除时自动被调用。如果任何回调返回错误,GORM 将停止后续的操作并回滚事务。
钩子方法的函数签名应该是
func(*gorm.DB) error
创建对象
创建时可用的 hook
// 开始事务
BeforeSave
BeforeCreate
// 关联前的 save
// 插入记录至 db
// 关联后的 save
AfterCreate
AfterSave
// 提交或回滚事务
代码示例:
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
u.UUID = uuid.New()
if !u.IsValid() {
err = errors.New("can't save invalid data")
}
return
}
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
if u.ID == 1 {
tx.Model(u).Update("role", "admin")
}
return
}
注意
在 GORM 中保存、删除操作会默认运行在事务上, 因此在事务完成之前该事务中所作的更改是不可见的,如果您的钩子返回了任何错误,则修改将被回滚。
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
if !u.IsValid() {
return errors.New("rollback invalid user")
}
return nil
}
更新对象
更新时可用的 hook
// 开始事务
BeforeSave
BeforeUpdate
// 关联前的 save
// 更新 db
// 关联后的 save
AfterUpdate
AfterSave
// 提交或回滚事务
代码示例:
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
if u.readonly() {
err = errors.New("read only user")
}
return
}
// 在同一个事务中更新数据
func (u *User) AfterUpdate(tx *gorm.DB) (err error) {
if u.Confirmed {
tx.Model(&Address{}).Where("user_id = ?", u.ID).Update("verfied", true)
}
return
}
删除对象
删除时可用的 hook
// 开始事务
BeforeDelete
// 删除 db 中的数据
AfterDelete
// 提交或回滚事务
代码示例:
// 在同一个事务中更新数据
func (u *User) AfterDelete(tx *gorm.DB) (err error) {
if u.Confirmed {
tx.Model(&Address{}).Where("user_id = ?", u.ID).Update("invalid", false)
}
return
}
查询对象
查询时可用的 hook
// 从 db 中加载数据
// Preloading (eager loading)
AfterFind
代码示例:
func (u *User) AfterFind(tx *gorm.DB) (err error) {
if u.MemberShip == "" {
u.MemberShip = "user"
}
return
}
修改当前操作
func (u *User) BeforeCreate(tx *gorm.DB) error {
// 通过 tx.Statement 修改当前操作,例如:
tx.Statement.Select("Name", "Age")
tx.Statement.AddClause(clause.OnConflict{DoNothing: true})
// tx 是带有 `NewDB` 选项的新会话模式
// 基于 tx 的操作会在同一个事务中,但不会带上任何当前的条件
err := tx.First(&role, "name = ?", user.Role).Error
// SELECT * FROM roles WHERE name = "admin"
// ...
return err
}
日志
Gorm 有一个 默认 logger 实现,默认情况下,它会打印慢 SQL 和错误
Logger 接受的选项不多,可以在初始化时自定义它,例如:
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值
LogLevel: logger.Silent, // 日志级别
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
Colorful: false, // 禁用彩色打印
},
)
// 全局模式
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
Logger: newLogger,
})
// 新建会话模式
tx := db.Session(&Session{Logger: newLogger})
tx.First(&user)
tx.Model(&user).Update("Age", 18)
日志级别
GORM 定义了这些日志级别:
Silent
、
Error
、
Warn
、
Info
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
Debug
Debug 单个操作,将当前操作的 log 级别调整为 logger.Info
db.Debug().Where("name = ?", "jinzhu").First(&User{})
事务
事务是一组操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位
事务的特征ACID:
- 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
- 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
- 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
// 开启事务
tx:=db.Begin()
// 执行一系列数据库操作,使用tx调用
tx.Create()
...
// 发生错误回滚事务
tx.Rollback()
// 提交事务
tx.Commit()