概述
中文官网:GROM
GORM是一个使用Go语言编写的ORM框架,它提供了强大的功能和简洁的API,让数据库操作变得更加简单和易维护。GORM支持多种数据库,包括MySQL、PostgreSQL、SQLite、Oracle等。它还提供了许多有用的功能,如预加载、事务、复合主键、SQL生成器等。
准备
选择Mysql作为测试数据库
-
部署指南
# docker-compose.yaml version: "3" services: db: image: mysql:latest container_name: mysql restart: always environment: MYSQL_ROOT_PASSWORD: root volumes: - ./data:/var/lib/mysql ports: - "3306:3306" -
启动容器
docker-compose up -d
选择Navicat Premium 16作为Mysql可视化客户端
- 文末贴有免费获取方式
引入依赖
go get -u gorm.io/gormgo
"gorm.io/driver/mysql"
连接到Mysql
定义连接信息 | dsn
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
字段说明:user | 数据库用户名 ,pass | 密码
tcp(127.0.0.1:3306) | tcp连接(本地MySQL部署IP:mysql服务端口/默认3306)
dbname | 数据库名 , charset=utf8mb4 | utf8mb4编码
parseTime=True&loc=Local | 时间参数,格式化并采用本地时间
Tips:
对于本地Linux,最好查看一下本地时间是否跟国内一致,不然入库会出现时区不对造成少几个小时的情况
date | 查看本地时间
timedatectl set-timezone Asia/Shanghai | 设置国内时区
mysql驱动配置
| 字段 | 值 |
|---|---|
| DSN | dsn |
| DefaultStringSize | 256 // string 类型字段的默认长度 |
| DisableDatetimePrecision | true // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持 |
| DontSupportRenameIndex | true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引 |
| DontSupportRenameColumn | true // 用 change 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 |
具体代码
根据连接信息和驱动配置得到 db 对象,然后就可以进行mysql数据库操作
dsn := "root:root@tcp(localhost:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.New(mysql.Config{
DSN: dsn, // DSN data source name
mysql其他驱动配置...
}), &gorm.Config{})
基本用法
前提
定义模型 | 映射到数据库表字段
后续都以用户表为例
type User struct {
gorm.Model // gorm 自带字段 ID | CreatedAt | UpdatedAt | DeletedAt
Name string
Age int
}
创建
Create Api
-
创建一条记录
语句:
user:=&User{Name: "汐瀼",Age:100}
db.Create()
-
创建多条记录
语句:
users := []*User{{Name: "xiao li", Age: 10},{Name: "xiao ming", Age: 333}, }db.Create(users) -
创建记录并为指定的字段分配值
语句:
db.Select("Name", "Age").Create(&user) -
创建记录并忽略要省略的传递字段的值
语句:
db.Omit("Name", "Age").Create(&user) -
批量插入 | 高效分批次插入
gorm配置,可以设置批次
&gorm.Config{CreateBatchSize: 1000,}语句:
db.CreateInBatches(users, 100) -
根据 Map 创建 | 支持切片批量插入
语句:
db.Model(&User{}).Create(map[string]interface{}{ "Name": "ttt", "Age": 18,}) -
默认值 | 针对模型定义
Name string
gorm:"default:游客"Age int64gorm:"default:18" -
钩子 Hook | 创建周期触发的事件
需要实现 BeforeSave BeforeCreate AfterSave AfterCreate 方法
实现创建之前的Hook
func (u *User) BeforeCreate(tx *gorm.DB) (err error)
跳过Hook
DB.Session(&gorm.Session{SkipHooks: true}).Create(&user)
查询
First
-
获取第一条记录(主键升序)
语法:
db.First(&user) -
主键检索
语法:
db.First(&user, 10)
db.First(&user, "10")
db.Find(&users, []int{1,2,3}) // in
Take
-
获取一条记录,没有指定排序字段
语法:
db.Take(&user)
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user) // struct
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users) // map
db.Where([]int64{20, 21, 22}).Find(&users) // 主键id
Last
-
获取最后一条记录(主键降序)
语法:
db.Last(&user)
Find
-
检索全部字段
语法:
result := db.Find(&users) -
条件查询
语法:
db.Where("name = ?", "lily").First(&user) // 一条
db.Where("name <> ?", "lily").Find(&users) // 所有
db.Where("name IN ?", []string{"lily", "jack"}).Find(&users) // in
db.Where("name LIKE ?", "%lily%").Find(&users) // like
db.Where("name = ? AND age >= ?", "lily", "22").Find(&users) // and
db.Limit(10).Offset(5).Find(&users) // limit offset 做分页
Scan
- 语句:
db.Model(&User{}).Select("name", "age").Where("name = ?", "Antonio").Scan(&result)
更新
Save
-
保存所有字段
语句:
db.Save(&User{Name: "jinzhu", Age: 100})
Update
-
更新单个列
语句:
db.Model(&user).Update("name", "hello") -
更新多列
语句:db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
-
批量更新
语句:
db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
更新 Hook
-
BeforeSave, BeforeUpdate, AfterSave, AfterUpdate
func (u *User) BeforeUpdate(tx *gorm.DB) (err error)
删除
Delete
-
删除一条记录
语句:
db.Where("name = ?", "jack").Delete(&user) -
根据主键删除
语句:
db.Delete(&User{}, 10)
db.Delete(&User{}, "10")
db.Delete(&users, []int{1,2,3}) // in -
批量删除
语句:
db.Where("name LIKE ?", "%lily%").Delete(&User{}) -
软删除 | 模型包含 gorm.DeletedAt 字段
说明:当调用
Delete时,GORM并不会从数据库中删除该记录,而是将该记录的DeleteAt设置为当前时 间,而后的一般查询方法将无法查找到此条记录。查找被软删除的记录:Unscoped()
db.Unscoped().Where("age = 20").Find(&users) -
永久删除
语句:
db.Unscoped().Delete(&order)
Hook
-
BeforeDelete、AfterDeletefunc (u *User) BeforeDelete(tx *gorm.DB) (err error)
高级用法
复用多种数据库
我们知道,gorm是orm框架,支持多种数据库,包括MySQL、PostgreSQL、SQLite、Oracle等,那么我们可以抽象一个方法,根据不同数据库实例化对应gorm.DB
实现
初始化gorm配置
-
定义 gorm 结构体
type _gorm struct{}
-
实现 func (g *_gorm) Config(prefix string, singular bool) *gorm.Config 方法
暴露 gorm 配置
func (g *_gorm) Config(prefix string, singular bool) *gorm.Config { config := &gorm.Config{ // 命名策略 NamingStrategy: schema.NamingStrategy{ TablePrefix: prefix, // 表前缀,在表名前添加前缀,如添加用户模块的表前缀 user_ SingularTable: singular, // 是否使用单数形式的表名,如果设置为 true,那么 User 模型会对应 users 表 }, // 是否在迁移时禁用外键约束,默认为 false,表示会根据模型之间的关联自动生成外键约束语句 DisableForeignKeyConstraintWhenMigrating: true, // 其他配置... } // TODO 日志配置器,替换gorm自定义logger ... return config } // 暴露 gorm 配置 var Gorm = (*_gorm)(nil) -
以 mysql数据库为例,初始化mysql连接,最终得到 gorm.DB 对象 | 其他数据库一样
// GormMysql 初始化Mysql数据库 func GormMysql() *gorm.DB { mysqlConfig := mysql.Config{ DSN: dsn, // DSN data source name // 其他配置... } // 打开数据库连接 驱动 gorm配置 db, err := gorm.Open(mysql.New(mysqlConfig), Gorm.Config(m.Prefix, m.Singular)) // 其他配置... // TODO 实现数据迁移 return db } -
接下来,实现数据库初始化方法,根据传入的参数,判断选择哪个数据库
func InitialDB(db string) *gorm.DB { switch db { case "mysql": return GormMysql() case "postsql": return GormPgsql() default: return GormMysql() } }
自定义数据类型
实现接口
Scanner 接口
type JSON json.RawMessage
// 实现 sql.Scanner 接口,Scan 将 value 扫描至 Jsonb
func (j *JSON) Scan(value interface{}) error
Valuer 接口
// 实现 driver.Valuer 接口,Value 返回 json value
func (j JSON) Value() (driver.Value, error) {
if len(j) == 0 {
return nil, nil
}
return json.RawMessage(j).MarshalJSON()
}
分页
场景:从http请求中获取分页参数 offset 和 limit 对分页参数进行处理 返回 func(db *gorm.DB) *gorm.DB 函数对象,使用Scopes作用域复用db对象,实现分页
func Paginate(r *http.Request) func(db *gorm.DB) *gorm.DB {
return func (db *gorm.DB) *gorm.DB {
q := r.URL.Query()
page, _ := strconv.Atoi(q.Get("page"))
if page <= 0 {
page = 1
}
pageSize, _ := strconv.Atoi(q.Get("page_size"))
switch {
case pageSize > 100:
pageSize = 100
case pageSize <= 0:
pageSize = 10
}
offset := (page - 1) * pageSize
return db.Offset(offset).Limit(pageSize)
}
}
db.Scopes(Paginate(r)).Find(&users)
wx搜索 汐瀼爱分享,回复:数据库 获取数据库客户端软件