go Gorm连接数据库,并实现增删改查操作

Gorm

1. 准备工作

首先进入终端下载我们需要的包(确保go和mysql安装完成,并设置了环境变量)

go 复制代码
go get -u gorm.io/driver/mysql
go get -u gorm.io/gorm

有两份官方文档有对 GORM 更详细的讲解。

  1. 创建 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.(中文)
  2. go-sql-driver/mysql: Go MySQL Driver is a MySQL driver for ++Go's++ (golang) database/sql package (github.com)

2. 连接数据库

这里以mysql为例,其他数据库请看官方文档。

go 复制代码
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{})
}

user -> 用户名

pass -> 密码

127.0.0.1 -> mysql默认本地地址,也就是localhost

3306 -> mysql默认端口

dbname -> 数据库名称

charset -> 字符集编码

parseTime -> 把数据库datetime和date类型转换为golang的time.Time类型

loc -> 使用系统本地时区

gorm.config{} 中可以进行一些高级配置,详情请看官方文档。

例如:

我们可以在其中写入:
Logger: logger.Default.LogMode(logger.Info)

这个句子的作用是配置GORM库,告诉它使用默认的日志记录器并将日志模式设置为信息级别(logger.Info),以便在终端中输出有关数据库操作的信息。

这样我们在对使用GORM的时候就可以在终端中看到输出的对应SQL语句了。

3. 结构体与mysql table 的映射关系

go的orm框架可以让我们在用go语言实现数据库操作的时候不使用sql语言,简单易上手,提高开发效率。

下面是gorm中go与mysql的对应关系

GO mysql
结构体 数据表
结构体实例 数据行
结构体字段 字段

例如,一个结构体:

go 复制代码
type Student struct {
	ID                int
	Name              string
	age               int
	ExternalCharacter string
}

gorm 会将其映射为

mysql 复制代码
CREATE TABLE `students` (
    `id` bigint AUTO_INCREMENT,
    `name` longtext,
    `external_character` longtext,
    PRIMARY KEY (`id`)
)

这里可以发现,结构体字段中的大写都变成了小写age完全消失Student也变成了studentsExternalCharacter变成了external_character。这里涉及到了gorm的映射规定。

具体可看模型定义 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.

简单来说,就是咱要首字母大写,会按驼峰命名法 给我们加 _ 符号,默认ID 为主键,表名会变成复数

再看数据类型,明显这是不知道有多大所以往大了去调

当然,这些我们也是能够一一自定义的。

3.1. 表名:

实现Tabler接口 中的TableName函数。

例如,针对上述的结构体,我们可以这样写:

go 复制代码
func (s *Student) TableName() string {
	return "student"
}

这样表名就会被映射为student而不是students

3.2 字段名:

我们可以在字段后面打上gorm tag,tag有很多用途,比如标记主键,指定not null等等,具体请看官方文档。

这里介绍自定义字段名,还是以上述的结构体为例:

go 复制代码
type Student struct {
	ID                int `gorm:"column:序号"`
	Name              string
	age               int
	ExternalCharacter string `gorm:"column:externalCharacter"`
}

如果我们改成这样的话,映射的表就是这样的:

mysql 复制代码
CREATE TABLE `student` (
    `序号` bigint AUTO_INCREMENT,
    `name` longtext,
    `externalCharacter` longtext,
    PRIMARY KEY (`序号`)
)
3.2 数据类型

longtext改成 varchar(255) ,也是通过tag实现的。通过size:255来确定最大长度

bigint改成int只需要将int改成int32就行了,因为go中,咱64位的电脑默认intint64

例如:

go 复制代码
type Student struct {
	ID                int32
	Name              string
	age               int
	ExternalCharacter string `gorm:"column:externalCharacter; size:255"`
}

映射出来的表就是这样的

mysql 复制代码
CREATE TABLE `student` (
    `id` int AUTO_INCREMENT,
    `name` longtext,
    `externalCharacter` varchar(255),
    PRIMARY KEY (`id`)
)

tag中定义多种内容,中间打;号就行了

4. 创建table

我们可以通过 db.AutoMigratedb.Migrator().CreateTable() 方法来创建table。

AutoMigrate 方法:

使用 db.AutoMigrate 方法是一种自动创建和迁移数据库表的方式。它会根据你的 GORM 模型定义,自动检查数据库表是否存在,如果不存在则创建表,如果表结构有变化则进行迁移。这种方式适用于开发过程中的快速迭代和维护数据库结构的情况。

示例:

go 复制代码
// 自动创建和迁移表
err := db.AutoMigrate(&Student{})
// 这里的db就是上述gorm.open的db哈
if err != nil {
    panic("创建/迁移表格失败, error = " + err.Error())
}

Migrator().CreateTable 方法:

使用 db.Migrator().CreateTable 方法是一种手动创建数据库表的方式。你需要显式地指定要创建的表,它不会检查是否已经存在,而是直接创建表。这种方式适用于在特定情况下需要手动控制表创建过程的情况。

示例:

go 复制代码
// 手动创建表
err := db.Migrator().CreateTable(&Student{})
if err != nil {
    panic("创建表格失败, error = " + err.Error())
}

区别总结:

  • AutoMigrate 方法自动根据模型定义创建和迁移表,适用于自动化和快速迭代。
  • Migrator().CreateTable 方法手动创建表,适用于需要手动控制创建过程的情况。

在实际应用中,你可以根据项目需求选择合适的方法。通常情况下,开发和调试阶段可能更适合使用 AutoMigrate 方法,而特定场景下的手动控制可能需要使用 Migrator().CreateTable 方法。

5. 增

以下列结构体为例:

go 复制代码
type Student struct {
	ID                int
	Name              string
	Age               int
	ExternalCharacter string `gorm:"column:externalCharacter; size:255"`
}

func (s *Student) TableName() string {
	return "student"
}
5.1. db.Create(结构体对象)
go 复制代码
lisi := Student{2, "lisi", 18, "modest"}
result := db.Create(&lisi)
if result.Error != nil {
    panic("插入字段失败, error = " + result.Error.Error())
}
fmt.Println(result.RowsAffected) // 返回影响的行数 1

等于INSERT INTO student (name,age,externalCharacter,id) VALUES ('lisi',18,'modest',2)

当然也可以同时增添多个字段

例如:

go 复制代码
students := []Student{
    {ID: 3, Name: "wangwu", Age: 20, ExternalCharacter: "generous"},
    {ID: 4, Name: "xuliu", Age: 21, ExternalCharacter: "liberal"},
}
result := db.Create(&students)
if result.Error != nil {
    panic("增加字段失败")
}
fmt.Println(result.RowsAffected) // 返回影响的行数 2

等于INSERT INTOstudent (name,age,externalCharacter,id) VALUES ('wangwu',20,'generous',3),('xuliu',21,'liberal',4)

5.2. db.Select(指定字段).Create(结构体对象)
go 复制代码
lisi := Student{2, "lisi", 18, "modest"}
result := db.Select("ID", "Name", "Age").Create(&lisi)
if result.Error != nil {
    panic("插入字段失败, error = " + result.Error.Error())
}
fmt.Println(result.RowsAffected) // 返回影响的行数 1

等于INSERT INTO student (name,age,id) VALUES ('lisi',18,2)

5.3. db.Omit(忽略字段).Create(结构体对象)
go 复制代码
lisi := Student{2, "lisi", 18, "modest"}
result := db.Omit("ExternalCharacter").Create(&lisi)
if result.Error != nil {
    panic("插入字段失败, error = " + result.Error.Error())
}
fmt.Println(result.RowsAffected) // 返回影响的行数 1

也等于INSERT INTO student (name,age,id) VALUES ('lisi',18,2)

5.4. 原生sql语句
go 复制代码
lisi := Student{2, "lisi", 18, "modest"}
result := db.Exec("INSERT INTO `student` (`name`,`age`,`externalCharacter`,`id`) VALUES ('lisi',18,'modest',2)")
if result.Error != nil {
    panic("插入字段失败, error = " + result.Error.Error())
}
fmt.Println(result.RowsAffected) // 返回影响的行数 1

6. 查

之后所有的操作都是基于我的这份表实现的。

我的表

我现在的表如下:

6.1 where

我们这里使用where 函数 设置条件

func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB)

参数说明:

参数名 说明
query sql语句的where子句, where子句中使用问号(?)代替参数值,则表示通过args参数绑定参数
args where子句绑定的参数,可以绑定多个参数

例如db.Where("id in (?)", []int{1,2,3,4})

后面的 select、having 语句这些 query 的也同样适用

6.2 Take

取走第一条查询信息

go 复制代码
s1 := Student{}
db.Where("age = 18").Take(&s1)
fmt.Println(s1)

这里我们是先创建了一个结构体对象,查询的结果直接赋予了该对象

输出:

go 复制代码
{1 zhangsan 18 humble}

等于:

mysql 复制代码
 SELECT * FROM `student` WHERE age = 18 LIMIT 1

注意这个limit 1,我这里有两个age = 18的,但是take只取一个,一般是第一个

6.3 First

根据主键正序排序后,查询的第一条数据

go 复制代码
s1 := Student{}
db.Where("age = 18").First(&s1)
fmt.Println(s1)

输出:

go 复制代码
{1 zhangsan 18 humble}

等于:

mysql 复制代码
SELECT * FROM `student` WHERE age = 18 ORDER BY `student`.`id` LIMIT 1
6.4 Last

根据主键倒序排序后,查询最后一条记录

go 复制代码
s1 := Student{}
db.Where("age = 18").Last(&s1)
fmt.Println(s1)

输出:

go 复制代码
{5 feixin 18 frantic}

等于:

mysql 复制代码
 SELECT * FROM `student` WHERE age = 18 ORDER BY `student`.`id` DESC LIMIT 1
6.5 Find

这里就不是查询一条记录了,可以查询多条记录

这里因为多条记录,一个结构体对象咱也装不完,所以需要改成使用结构体切片(咱也不知道会查出多少记录)

还有一点就是find如果没有找到记录是不会报err 的,但是take、first、last会,因为他们都要取走一条数据。

go 复制代码
s1 := []Student{}
db.Where("age = 18").Find(&s1)
fmt.Println(s1)

输出:

go 复制代码
[{1 zhangsan 18 humble} {5 feixin 18 frantic}]

等于:

mysql 复制代码
SELECT * FROM `student` WHERE age = 18

再举一个使用where args的例子

go 复制代码
s1 := []Student{}
db.Where("id in (?)", []int{1, 2, 3, 4}).Find(&s1)
fmt.Println(s1)

输出:

go 复制代码
[{1 zhangsan 18 humble} {2 lisi 19 modest} {3 wangwu 20 generous} {4 xuliu 21 liberal}]

等于:

mysql 复制代码
SELECT * FROM `student` WHERE id in (1,2,3,4)
6.6 Pluck

查询一列的值

这个函数返回切片类型,同时需要一个模型Model,就是我们之前定义的结构体模型

go 复制代码
col := []string{}
db.Model(&Student{}).Pluck("ExternalCharacter", &col)
for _, i := range col {
    fmt.Println(i)
}

输出:

go 复制代码
humble
modest
generous
liberal
frantic

等于:

mysql 复制代码
SELECT `externalCharacter` FROM `student`

关于这个model其实之前的所有操作都可以去写成这个model形式,不过可以省略,在这里就不行了因为你这里没有任何有关student结构体的信息,它甚至都不知道你是哪一个表,那肯定就查找不了咯,所以这里要写这个model。

比如之前的db.Where("age = 18").Find(&s1) 也可以写成

db.Model(&Student{}).Where("age = 18").Find(&s1)

然后这里如果不使用字符串切片,使用结构体student切片的话,就可以不使用model,每一个student对象中没在查找范围的值会变成默认值,也就是int 变 0, string 变 空值。

例如:

go 复制代码
col := []Student{}
db.Pluck("ExternalCharacter", &col)
fmt.Println(col)

输出:

go 复制代码
[{0  0 humble} {0  0 modest} {0  0 generous} {0  0 liberal} {0  0 frantic}]

等于:

mysql 复制代码
 SELECT `ExternalCharacter` FROM `student`

所以,model的作用 ok 了吗 😝

6.7 Select

Pluck是 一列,那如果我需要多列信息呢?select来帮忙

例如,我想要查询 nameage 两列

go 复制代码
result := []Student{}
err = db.Select("Age", "Name").Find(&result).Error
if err != nil {
    panic("查询失败, error = " + err.Error())
}
for _, i := range result {
    fmt.Println(i.Name, i.Age)
}

之的代码没有加err,汗,懒得加了,反正err是这样加😉

输出:

go 复制代码
zhangsan 18
lisi 19
wangwu 20
xuliu 21
feixin 18

等于:

mysql 复制代码
SELECT `age`,`name` FROM `student`

select 中也可以使用mysql中的聚集函数

avg 为例:

go 复制代码
var averAge float32
err = db.Model(&Student{}).Select("avg(age)").Pluck("age", &averAge).Error
if err != nil{
    panic("查询失败, error = " + err.Error())
}
fmt.Println(averAge)

输出:

go 复制代码
19.2

等于:

mysql 复制代码
SELECT avg(age) FROM `student`

使用方法和mysql中差不多,具体看官方文档哈

6.8 Order

看单词儿也大概知道了,是排序用的,可以将搜索结果进行排序

例如我按年龄排序:

go 复制代码
result := []Student{}
err = db.Order("age asc").Select("Age", "Name").Find(&result).Error
if err != nil {
    panic("查询失败, error = " + err.Error())
}
for _, i := range result {
    fmt.Println(i.Name, i.Age)
}

这其实就是之前那个加了一个order

输出:

go 复制代码
zhangsan 18
feixin 18
lisi 19
wangwu 20
xuliu 21

等于:

mysql 复制代码
 SELECT `age`,`name` FROM `student` ORDER BY age asc
6.9 Limit

可以限制查找条数,比如上面这个,如果想要只找出前三条的话,可以这样写:

go 复制代码
result := []Student{}
err = db.Limit(3).Order("age asc").Select("Age", "Name").Find(&result).Error
if err != nil {
    panic("查询失败, error = " + err.Error())
}
for _, i := range result {
    fmt.Println(i.Name, i.Age)
}

输出:

go 复制代码
zhangsan 18
feixin 18
lisi 19

等于:

mysql 复制代码
SELECT `age`,`name` FROM `student` ORDER BY age asc LIMIT 3
6.10 Offset

这个可以过滤掉前几个,还是以上面的为例,如果我想不要zhangsan 和 feixin,取后面三个,那可以这样写:

go 复制代码
result := []Student{}
err = db.Offset(2).Limit(3).Order("age asc").Select("Age", "Name").Find(&result).Error
if err != nil {
    panic("查询失败, error = " + err.Error())
}
for _, i := range result {
    fmt.Println(i.Name, i.Age)
}

输出:

go 复制代码
lisi 19
wangwu 20
xuliu 21

等于:

mysql 复制代码
SELECT `age`,`name` FROM `student` ORDER BY age asc LIMIT 3 OFFSET 2

有没有一种反复套娃的赶脚,是这样的没错。🤔

6.11 Count

这个就是返回查询相对应了多少条行数,比如我的表里有两个18岁的小伙子,查查他们的

go 复制代码
var count int64
err = db.Model(&Student{}).Where("age = 18").Count(&count).Error
// 这里记得where要写在count前面
if err != nil {
    panic("查询失败, error = " + err.Error())
}
fmt.Println(count)

输出:

go 复制代码
2

等于:

mysql 复制代码
SELECT count(*) FROM `student` WHERE age = 18
6.12 Group 与 Having

GroupHaving 一般联合使用,和sql中也是一样的,因为where 没法对聚合函数进行操作

在 GORM 中,使用 Group 函数时,通常需要搭配 Select 函数一起使用,以指定你希望在分组操作中选择的字段。这是因为在 SQL 查询中,使用 GROUP BY 进行分组时,通常需要明确指定分组后所需的字段。

所以Group, Having , Select 三者一般一起使用

简而言之,就是Select 用了聚集函数,然后Group 分组,再用Having 进行筛选

比如我现在要对我的表中的各个 age 的人数进行统计,首先我的表中18的有两人,19,20,21的各一人

go 复制代码
type Result struct {
    Age     int
    Numbers int
}
var results []Result
err = db.Model(&Student{}).Select("age, count(*) as numbers").
    Group("age").Having("numbers > 0").Find(&results).Error
if err != nil {
    panic("查询失败, error = " + err.Error())
}
fmt.Println(results)

这里我是定义了一个Result 结构体存储结果, count(*) as numbers 是计算这一列的数目,并取一个别名为numbers, 这里所有这些都要用引号括起来,因为实际上他就是要转化为sql语句(可以仔细对照一下 下文中的等于啥sql语句)。最后老方法用Find 取结果并赋值给了results, 注意这里赋值也是映射赋值,你结构体里面的字段名一定要大写

输出:

go 复制代码
[{18 2} {19 1} {20 1} {21 1}]

等于:

mysql 复制代码
SELECT age, count(*) as numbers FROM `student` GROUP BY `age` HAVING numbers > 0
6.13 原生sql语句

就是上面的例子,如果使用sql语句的话,也可以这样写

go 复制代码
type Result struct {
    Age     int
    Numbers int
}
var results []Result
sql := " SELECT age, count(*) as numbers FROM `student` GROUP BY `age` HAVING numbers > 0"
err = db.Raw(sql).Find(&results).Error // 这个Find 应该要换成 Scan
if err != nil {
    panic("查询失败, error = " + err.Error())
}
fmt.Println(results)

之前在 中 使用的 Exec 方法是只返回语句执行情况,不会返回结果集,这里需要查询结果,我们改用Raw

结果是一样的,就不表了,这里说一下 FindScan

  1. Find 方法: Find 方法用于从数据库中查询数据并将结果映射到指定的结构体切片中。它适用于正常的 GORM 查询操作,根据条件从数据库中检索数据并进行映射。
  2. Scan 方法: Scan 方法用于将结果扫描到指定的结构体切片中,而不需要进行查询操作。它通常用于执行原生 SQL 查询,并将查询结果映射到结构体中。

意思就是FindScan差不多,但是这里我在使用原生sql语句,所以应该使用Scan

7. 删

删就很简单了,我们可以先查出来再删,也可以根据主键删除

还是这张表

现在我要把这个frantic的家伙给删除

可以先找到他

go 复制代码
s := Student{}
err = db.Where("externalCharacter = 'frantic'").Find(&s).Error
if err != nil {
    panic("查询失败, error = " + err.Error())
}
err = db.Delete(s).Error
if err != nil {
    panic("删除失败, error = " + err.Error())
}

如果知道主键的话也可以直接

go 复制代码
err = db.Delete(&Student{}, 5).Error
if err != nil {
    panic("删除失败, error = " + err.Error())
}

等于:

mysql 复制代码
DELETE FROM `student` WHERE `student`.`id` = 5

8. 改

还是这张表

8.1 Save

我们可以直接对结构体的字段进行修改然后进行save 操作映射到mysql中同步更改

比如我这里把 feixin 的 age 改为 19

go 复制代码
s := Student{}
err = db.Where("id = ?", 5).Find(&s).Error
if err != nil {
    panic("查询失败, error = " + err.Error())
}
s.Age = 19
err = db.Save(&s).Error
if err != nil {
    panic("更新失败, error = " + err.Error())
}

等于:

mysql 复制代码
UPDATE `student` SET `name`='feixin',`age`=19,`externalCharacter`='frantic' WHERE `id` = 5
8.2 Update

上面的也可以这样写

go 复制代码
s := Student{}
err = db.Where("id = ?", 5).Find(&s).Error
if err != nil {
    panic("查询失败, error = " + err.Error())
}
err = db.Model(&s).Update("age", 19).Error
if err != nil {
    panic("更新失败, error = " + err.Error())
}

等于:

mysql 复制代码
UPDATE `student` SET `age`=19 WHERE `id` = 5
8.3 Updates

可以更新多列值

go 复制代码
s := Student{}
err = db.Where("id = ?", 5).Find(&s).Error
if err != nil {
    panic("查询失败, error = " + err.Error())
}
err = db.Model(&s).Updates(Student{
    Name: "feixin",
    Age:  18,
}).Error
if err != nil {
    panic("更新失败, error = " + err.Error())
}

等于:

mysql 复制代码
UPDATE `student` SET `name`='feixin',`age`=18 WHERE `id` = 5
8.4 表达式

比如我把所有人的年龄都加1

go 复制代码
err = db.Model(&Student{}).Where("age > ?", 0).Update("age", gorm.Expr("age + ?", 1)).Error
if err != nil {
    panic("更新失败, error = " + err.Error())
}

等于:

mysql 复制代码
UPDATE `student` SET `age`=age + 1 WHERE age > 0

在 GORM 中,使用 Update 方法时,需要提供一个条件来指定要更新哪些记录。所以这里需要写where 语句,不然会报err,之前的没有没有使用是因为Model 里填的是结构体对象,实际上就算已经指定了条件。、

当然,如果不想使用where的话也是有办法的,我们可以启用 AllowGlobalUpdate 模式

go 复制代码
err = db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&Student{}).Update("age", gorm.Expr("age + ?", 1)).Error
if err != nil {
    panic("更新失败, error = " + err.Error())
}

等于:

mysql 复制代码
UPDATE `student` SET `age`=age + 1
相关推荐
有想法的py工程师35 分钟前
PostgreSQL + Debezium CDC 踩坑总结
数据库·postgresql
Nandeska1 小时前
2、数据库的索引与底层数据结构
数据结构·数据库
Victor3561 小时前
Netty(20)如何实现基于Netty的WebSocket服务器?
后端
缘不易1 小时前
Springboot 整合JustAuth实现gitee授权登录
spring boot·后端·gitee
小卒过河01041 小时前
使用apache nifi 从数据库文件表路径拉取远程文件至远程服务器目的地址
运维·服务器·数据库
Kiri霧1 小时前
Range循环和切片
前端·后端·学习·golang
过期动态1 小时前
JDBC高级篇:优化、封装与事务全流程指南
android·java·开发语言·数据库·python·mysql
WizLC1 小时前
【Java】各种IO流知识详解
java·开发语言·后端·spring·intellij idea
Mr.朱鹏1 小时前
SQL深度分页问题案例实战
java·数据库·spring boot·sql·spring·spring cloud·kafka
Victor3561 小时前
Netty(19)Netty的性能优化手段有哪些?
后端