文章目录

  • 1.ORM 是什么
  • 2.GORM 是什么
  • 3.安装
  • 4.连接 DB
  • 5.创建数据表
  • 6.增加(Create)
  • 7.查询(Read)
    • 按照主键查询
    • IN 查询
    • AND 条件
    • OR 条件
    • Group 条件
    • 通过结构体指定查询字段
    • 查询记录数
    • 查询记录是否存在
    • 查询单个字段
    • 查询多个字段
    • Limit & Offset
    • 排序
  • 8.更新(Update)
  • 9.删除(Delete)
  • 10 Upsert
  • 11.小结
  • 参考文献

1.ORM 是什么

ORM(Object Relational Mapping),中文名为对象关系映射。

使用 ORM 组件,可以让开发者通过操作对象的方式完成对数据库的操作(读写),避免手动书写 SQL 和完成数据到对象的转换,让我们更方便的操作数据库。

理论上 ORM 可以让我们脱离 SQL,但实际上还是需要懂 SQL 才能更好地使用 ORM。

2.GORM 是什么

GORM 是一个流行的 Golang ORM 库。

类似于 Java 生态里大家听到过的 Mybatis、Hibernate、SpringData 等。

GORM 由国人开发,中文文档齐全,对开发者友好,支持主流关系型数据库。

  • MySQL
  • SQL Server
  • PostgreSQL
  • SQlite

GORM 功能丰富齐全:

  • 关联 (拥有一个,拥有多个,属于,多对多,多态,单表继承)
  • 钩子(before/after create/save/update/delete/find)
  • 支持 Preload、Joins 的预加载
  • 事务,嵌套事务,保存点,回滚到保存点
  • Context、预编译模式、DryRun 模式
  • 批量插入,FindInBatches,使用 Map Find/Create,使用 SQL 表达式、Context Valuer 进行 CRUD
  • SQL 构建器,Upsert,锁,Optimizer/Index/Comment Hint,命名参数,子查询
  • 复合主键,索引,约束
  • 自动迁移
  • 自定义 Logger
  • 灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
  • 每个特性都经过了测试的重重考验
  • 开发者友好

GORM 最新源码地址:go-gorm/gorm。

GORM V1 版本地址:jinzhu/gorm。

GORM 中文文档地址:这里。

本文将讲解 GORM 中常用的功能,帮助你快速上手。

当然除了 GORM,你还有其他选择,比如 facebook-ent、sqlx 和 sqlc 等。

3.安装

基于 Go Module 开发,import 最新包然后 go get 即可。

go get -u gorm.io/gorm// 不同 DB 对应的驱动
go get -u gorm.io/driver/sqlite
go get -u gorm.io/driver/mysql
go get -u gorm.io/driver/postgres
go get -u gorm.io/driver/sqlserver

驱动包按照自己实际使用的 DB 选择即可。

本文将以 MySQL 为例,讲解 GORM 的使用。

4.连接 DB

以 MySQL 为例,建立数据库连接。

import ("fmt""gorm.io/driver/mysql""gorm.io/gorm"
)// MySQLConn GORM MySQL 连接。
var MySQLConn *gorm.DB// Init gorm mysql connnection.
// 依赖服务配置初始化完成。
func InitMySQLConn() error {// data source name.dsn := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=utf8mb4&parseTime=True&loc=Local", Conf.Mysql.User, Conf.Mysql.Passwd, Conf.Mysql.IP, Conf.Mysql.Port, Conf.Mysql.Dbname)var err errorMySQLConn, err = gorm.Open(mysql.Open(dsn))return err
}

填入 DB 对应的正确的用户名、密码、地址、端口、数据库名称等信息后,便可建立对应数据源的连接。相关配置一般在服务启动时,事先从配置文件中加载。

5.创建数据表

在进行增查改删(CRUD)之前,需要先创建一个数据表。

GORM 中一个 struct 对应一张数据库表,对应的 struct 被称为模型。

假如我们要创建一张商品(goods)表,那么 struct 可定义为:

// Good 商品。
type Good struct {gorm.ModelName  string `gorm:"type:varchar(255) not null"`Price int    `gorm:"type:bigint not null"`
}

其中 gorm.Model 时 GORM 预先定义的一些基础字段,我们可以嵌入直接拿来用。

// Model a basic GoLang struct which includes the following fields: ID, CreatedAt, UpdatedAt, DeletedAt
// It may be embedded into your model or you may build your own model without it
//
//  type User struct {
//    gorm.Model
//  }
type Model struct {ID        uint `gorm:"primarykey"`CreatedAt time.TimeUpdatedAt time.TimeDeletedAt DeletedAt `gorm:"index"`
}

字段后的 tag 用来定义字段在 DB 中的相关属性,如 primarykey 表示主键,index 表示索引,type 表示字段类型。

除此以外,还有更加丰富的标签定义参见官方文档:字段标签。

一般在服务启动时创建数据表,如建立 DB 连接后只执行一次来完成数据表的创建。

db.AutoMigrate(&User{})db.AutoMigrate(&User{}, &Product{}, &Order{})// 创建表时添加后缀。
db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})

比如创建我们上面的商品表。

// 自动创建表,如果表已经存在不会有任何动作。
err := MySQLConn.AutoMigrate(&Good{})

创建好后的数据表名为 struct 名称命名方式是 snake_case(下划线命名法)的复数形式,字段名为 struct 字段的 sanke_case 形式。

如果想更改表名,可以通过在模型结构体上添加 TableName() 方法来自定义表名称。例如:

func (Good) TableName() string {return "tb_good"
}

创建好的表名称为 goods,结构如下:

6.增加(Create)

// createGood 插入商品。
func createGood(name string, price int) {task := &Good{Name:   name,Price: price,}result := MySQLConn.Create(task)if result.Error != nil {return }return nil
}

主键 ID 会自增,此外 GORM 还会自动维护 created_at、updated_ad 和 deleted_at 三个字段。

7.查询(Read)

按照主键查询

// getGoodByID 根据主键检索。
func getGoodByID (id int) (good Good, err error) {err = db.First(&good, id).Errorreturn
}

如果查不到,将报 gorm.ErrRecordNotFound(“not found error”) 错误。

IN 查询

比如按照多个主键查询。

db.Find(&goods, []int{1,2,3})

或者通过内联条件。

查询条件可以以类似于 Where 的方式内联到 First 和 Find 等方法中。

db.Find(&goods, "id IN ?", []int{1,2,3})

或者通过 Where 指定 IN 条件。

db.Where("id IN ?", []int{1,2,3}).Find(&goods)

AND 条件

再如按照其他字段进行 and 查询。

多次调用 Where 方法可指定多个条件,条件关系为 AND。

// getGoodsByInfo 根据商品信息分页拉取。
func getGoodsByInfo(name string, price int, lastID uint) ([]Good, error) {db := internal.MySQLConnif name != "" {db.Where("name = ?", name)}db.Where("price >= ?", price)// 按照每页大小 50 拉取商品。db.Where("id > ?", lastID).Order("id ASC").Limit(50)var goods []Goodresult := db.Find(&goods)return goods, result.Error
}

OR 条件

db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';// Struct
db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);// Map
db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2", "age": 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);

Group 条件

使用 Group 条件可以更轻松的编写复杂 SQL。

db.Where(db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
).Or(db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"),
).Find(&Pizza{}).Statement// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge")

通过结构体指定查询字段

db.Where(&Good{Name: "衣服", Price: 10}).Find(&goods)
// SELECT * FROM goods WHERE name = "衣服" AND price = 10;db.Where(&Good{Name: "衣服", Price: 10}, "Name").Find(&goods)
// SELECT * FROM goods WHERE name = "衣服";

查询记录数

// getGoodNumber 获取符合条件的商品数量。
func getGoodNumber(price int) (int, error) {var num intresult := internal.MySQLConn.Model(&Good{}).Where("price >= ?", price).Count(&num)return num, result.Error
}

查询记录是否存在

在 GORM 中,可以使用 Count 方法来判断一个查询是否返回了记录。

func GoodIsExist(name string) (bool, error) {var count int64if err := MySQLConn.Model(&Good{}).Where("name = ?", name).Count(&count).Error; err != nil {return false, err}return count > 0, nil
}

当然,你也可以使用 First 方法,并结合 ErrRecordNotFound 错误来判断记录是否存在。

func GoodIsExist(name string) (bool, error) {var good Gooderr := MySQLConn.Where("name = ?", name).First(&good).Error// 不存在。if err == gorm.ErrRecordNotFound {return false, nil}// 查询发生错误。if err != nil {return false, err}// 存在。return true, nil
}

使用 Count 方法可以在不加载实际记录的情况下检查是否存在记录。这种方法比使用 First 方法更高效,尤其是在需要检查大量记录是否存在的情况下。因为 Count 方法只计算匹配条件的记录数,而不需要加载和返回记录的实际内容。但是,它可能会有一些微小的开销,因为它需要向数据库发送一个额外的 COUNT(*) 查询来计算记录数。

所以,如果你只是需要检查记录是否存在,推荐使用 Count 方法。

还有很多查询方式,比如按照 map 指定查询字段以及 or 和 not 条件等,具体请参考官方文档 GORM 查询。

查询单个字段

使用 Pluck 方法可以查询指定字段的所有值。如下面的代码查询 users 表中所有用户的姓名字段。

var names []string
db.Model(&User{}).Pluck("name", &names)

如果是一条记录的某个字段,可以使用单个变量而非切片接收查询结果。

var name string
db.Model(&User{}).Where("id = ?", 1).Pluck("name", &name)

查询多个字段

如果您想要查询多列,您应该使用 Select 和 Scan 或 Find。

// 超过一列的查询,应该使用 `Scan` 或 `Find`
db.Select("name", "age").Scan(&user)
db.Select("name", "age").Find(&users)

虽然 Scan 和 Find 都可以查询数据库并返回结果,但它们的使用场景和作用略有不同。

Find 用于查询满足条件的多条数据,结果可以被扫描到一个结构体切片中。当你需要查询一张表中的所有数据或者一部分数据时,可以使用 Find 方法。

而 Scan 则用于查询一条记录的一个或多个字段,将查询结果扫描到变量或结构体中。这种情况下,查询结果只有一条记录,因此无法使用结构体切片来接收结果。

Limit & Offset

可以使用 Limit & Offset 实现分页查询。

Limit 指定要检索的最大记录数, Offset 指定在开始返回记录之前要跳过的记录数。

db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;// Cancel limit condition with -1
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
// SELECT * FROM users LIMIT 10; (users1)
// SELECT * FROM users; (users2)db.Offset(3).Find(&users)
// SELECT * FROM users OFFSET 3;db.Limit(10).Offset(5).Find(&users)
// SELECT * FROM users OFFSET 5 LIMIT 10;// Cancel offset condition with -1
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
// SELECT * FROM users OFFSET 10; (users1)
// SELECT * FROM users; (users2)

利用 Limit & Offset 实现分页查询时,如果需要同时查询符合条件的记录总数,则需要先查询记录数,再查询记录。

db.Model(&User{}).Where("age >= ?", 18)// 先查询记录数。
var count int64
db.Count(&count)
// 再查询记录。
db.Limit(10).Offset(10).Find(&users)// 或 Count 在前。
var count int64
db.Count(&count).Limit(10).Offset(10).Find(&users)

排序

从数据库检索记录时指定顺序。

db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;// Multiple orders
db.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;db.Clauses(clause.OrderBy{Expression: clause.Expr{SQL: "FIELD(id,?)", Vars: []interface{}{[]int{1, 2, 3}}, WithoutParentheses: true},
}).Find(&User{})
// SELECT * FROM users ORDER BY FIELD(id,1,2,3)

8.更新(Update)

比如更新所有字段。

使用 Save 方法更新所有字段,即使是零值也会更新。

// 先根据 ID  查询。
db.First(&good, 1)// 再修改值。
good.Name = "小米"// 最后写回。
db.Save(&user)

再如更新单列。

注意,当使用 Model 方法且其值具有主键时,主键将用于构建条件。

// 条件更新
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;// 注意:user 的 ID 是 111。
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;// 根据条件和 model 的值进行更新
db.Model(&user).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;

再如更新多列。

Updates 方法支持 structmap[string]interface{} 参数。当使用 struct 更新时,默认情况下,GORM 只会更新非零值的字段。

// 注意:user 的 ID 是 111。// 根据 `struct` 更新属性,只会更新非零值的字段
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;// 根据 `map` 更新属性
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

9.删除(Delete)

如删除一条记录。

删除一条记录时,删除对象需要指定主键,否则会触发批量 Delete,例如:

// Email 的 ID 是 10。
db.Delete(&email)
// DELETE from emails where id = 10;// 带额外条件的删除。
db.Where("name = ?", "jinzhu").Delete(&email)
// DELETE from emails where id = 10 AND name = "jinzhu";

再如根据主键删除。

GORM 允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字,也可以使用字符串。

db.Delete(&User{}, 10)
// DELETE FROM users WHERE id = 10;db.Delete(&User{}, "10")
// DELETE FROM users WHERE id = 10;db.Delete(&users, []int{1,2,3})
// DELETE FROM users WHERE id IN (1,2,3);

注意:

如果您的模型包含了一个 gorm.DeletedAt 字段(gorm.Model 已经包含了该字段),它将自动获得软删除的能力!

如果您不想引入 gorm.Model,您也可以这样启用软删除特性:

type User struct {ID      intDeleted gorm.DeletedAtName    string
}

拥有软删除能力的模型调用 Delete 时,记录不会被数据库。但 GORM 会将 DeletedAt 置为当前时间, 并且你不能再通过普通的查询方法找到该记录。

使用 Unscoped 方法查找被软删除的数据。

db.Unscoped().Where("user_name = gry").Find(&users)

要想物理删除,使用 Unscoped 方法永久删除数据。

user.ID = 14
db.Unscoped().Delete(&user)

10 Upsert

GORM 提供了 Upsert 的能力,记录存在(根据主键判断)则更新,不存在则增加。

func UpsertYourModel(m *YourModel) error {return Db.Save(m).Error
}

或者在键冲突时决定要更新的列。

import "gorm.io/gorm/clause"// Do nothing on conflict
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&user)// Update columns to new value on `id` conflict
db.Clauses(clause.OnConflict{Columns:   []clause.Column{{Name: "id"}},DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
}).Create(&users)// Update all columns to new value on conflict except primary keys and those columns having default values from sql func
db.Clauses(clause.OnConflict{UpdateAll: true,
}).Create(&users)// Update all columns to new value on composite unique index conflict.
db.Clauses(clause.OnConflict{Columns:   []clause.Column{{Name: "student_no"}, {Name: "course_no"}},DoUpdates: clause.AssignmentColumns([]string{"status", "updated_at"}),
}).Create(&courseSelection)

11.小结

本文简单介绍了 ORM、GORM、以及 GORM 连接数据库,创建数据表和 CRUD 的简单操作,帮忙新手快速上手。

更多用法,请参见官方文档 GORM 指南,这里有你想要的一切。


参考文献

GORM 指南| GORM - GORM
GORM 极速入门- 卢振千的博客
19-Gorm入门到精通- 刘清政 - 博客园
Go组件学习——gorm四步带你搞定DB增删改查 - 掘金

GORM CRUD 10 分钟快速上手相关推荐

  1. Tensorflow 10分钟快速上手

    Tensorflow 快速上手 系统版本 : Ubuntu 16.04LTS Python版本 : 3.6.1 Tensorflow 版本 : 1.0.1 本文依据教程 TensorFlow Tuto ...

  2. 【Microsoft Azure 的1024种玩法】五十四. 十分钟快速上手创建部署Azure speech服务

    [简介] Azure语音服务是Microsoft提供稳定可靠的云通信服务,其在单个 Azure 订阅中统合了语音转文本.文本转语音以及语音翻译功能,我们可以通过各种方式(语音 CLI.语音 SDK.S ...

  3. 10分钟快速配置sublime2支持jQuery开发

    昨天介绍了javascript的开发工具sublime 2 edit,今天我们将介绍如何10分钟快速配置sublime2支持jQuery开发.希望大家能喜欢着款jQuery开发工具. 相关介绍:使用s ...

  4. endnote一打开就自动关闭_【EndNote文献管理】5分钟快速上手Endnote

    EndNote是我们在撰写和编辑论文中常用的软件,有了它可以让我们更加快速的管理参考文献,也可以更方便地插入和编辑参考文献. 本篇教程教你如何快速上手EndNote,通过几个常用的功能就可以迅速掌握这 ...

  5. python的spider程序下载_PHPspider爬虫10分钟快速教程(内附python教程分享)

    说到做爬虫,大家都可能第一时间想到的是python,其实php也是可以用来写爬虫程序的.php一贯简洁.易用,亲测使用PHPspider框架10分钟就能写出一个简单的爬虫程序. 一.PHP环境安装 和 ...

  6. python程序员专用壁纸_神级python程序员分享的让小白30分钟快速上手的一张神图,赶快收藏!...

    原标题:神级python程序员分享的让小白30分钟快速上手的一张神图,赶快收藏! 现在很多人学编程都把Python作为入门语言,其实这是个很不错的选择,那么你知道新手如何学Python吗?小编给大家分 ...

  7. 转载文章-【工具】10分钟快速搭建属于自己的文档网站-来自掘金

    掘金 首页 探索掘金 搜索 lvhanghmm的头像 Gopal lv-4 2021年03月09日 阅读 9930 关注 [工具]10分钟快速搭建属于自己的文档网站 前言 很多同学都希望能够拥有自己的 ...

  8. 文字识别软件测试初学者,【只要10分钟 快速掌握文字识别】

    [只要10分钟 快速掌握文字识别] 教程 1.获取接口权限       2.下载接口调用工具       3.进行接口调用 具体步骤如下: 1.获取接口权限 1.1  登录网址:ai.baidu.co ...

  9. 【华为云技术分享】10分钟快速在华为云鲲鹏弹性云服务器上部署一个自己的弹幕网站!

    摘要:从零代码开始,10分钟快速开发一个可以发送弹幕的网站,并将其部署在华为云服务器上:学完本期教程,将知道如何使用Nginx.如何将自己的网站部署到云服务器上. 直播相信大家都不陌生了吧,大家经常会 ...

最新文章

  1. 词频统计-------------web版本
  2. Keyboard驱动中button中断的处理机制
  3. LeetCode_Pascal's Triangle_杨辉三角形(Java实现)
  4. because the principal dbo does not exist 问题解决方法
  5. mysqlbinlog日志一天产生太多脚本
  6. 爱立信两大股东不满股价表现 欲撤换CEO卫翰思
  7. 计算机跨专业考经济学,21考研报名人数377万!这八大专业报名人数最多~
  8. win10怎么进入修复计算机,Win10怎么进入安全模式以及Win10:使用Windows恢复环境轻松修复...
  9. 多通道波形记录仪自动化计量校准软件NSAT-3070
  10. 【python】52周存钱法功能改进
  11. linux正常关机使用命令是,Linux系统关机的命令
  12. idea当中批量替换变量名字
  13. PTA-IP地址转换
  14. codeforces869EThe Untended Antiquity(二维树状数组)
  15. vxe-table 如何用回车键替换 Tab 键功能,回车切换到右侧单元格,回车切换下一个单元格
  16. html思维导图word版,(完整word版)非谓语动词练习及思维导图
  17. 京东小程序开放平台,他来了
  18. 长城信息IPO过会:年营收13.4亿 中国电子控制77%股权
  19. 公司价值评估-自由现金流法
  20. 首马破四-IT男的健康分享

热门文章

  1. Sketch教程如何实现背景局部模糊
  2. 转发与重定向的区别详解
  3. 2018秋招历程之28所
  4. 三维中通过一个点和距离与其欧拉角计算另一个点位置
  5. 教你从零做起谷歌Adsense。开户,过审核,过pin码达到稳定收益
  6. 【图像配准】多图配准/不同特征提取算法/匹配器比较测试
  7. C++多线程学习06 利用RAII
  8. ALV 下载到EXCEL里出现的问题
  9. 空调遥控器c语言源码,51单片机格力空调遥控器设计源码
  10. 上传身份证百度ocr识别