使用GORM连接数据库,并实现增删改查
1、GORM简介
GORM是用Go语言开发的ORM(Object-Relational Mapping)框架,旨在简化数据库操作,使开发者能够通过面向对象的方式与数据库交互,而无需直接编写复杂的SQL语句。GORM支持多种数据库系统,包括 MySQL、PostgreSQL、SQLite、SQL Server 和 TiDB等,提供了丰富的API和功能,使得数据库操作更加高效便捷。
文档地址:gorm.io
2、安装GORM
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
3、连接MySQL
本次测试大的数据库是使用mysql,其他支持的数据库也是类似操作。
要求:
- Go的版本 >=1.21
- MySQL版本 >= 5.7或者MariaDB >= 10.5
安装Go的MySQL驱动:
go get -u github.com/go-sql-driver/mysql
连接MySQL数据库
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
username := "root"
password := "root"
host := "127.0.0.1"
port := 3306
dbname := "gorm"
timeout := "10s"
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s", username, password, host, port, dbname, timeout)
db, err := gorm.Open(mysql.Open(dsn))
if err != nil {
panic("连接数据库失败,error=" + err.Error())
}
fmt.Println("连接成功")
fmt.Println(db)
}
上述代码中的 dbname字段表示的是你MySQL数据库中字符集为utf8mb4的数据库名称。
简单介绍一下MySQL中utf8与uft8mb4字符集的区别:
-
编码范围和存储空间
- uft8:是MySQL中最早支持的Unicode字符集,使用1到3个字节来编码每个字符,最大能表示的Unicode码点是U+FFFF,即Unicode的基本多文种平面(BMP)。这意味着utf8不能存储一些超出BMP的字符,例如Emoji表情、部分罕用汉字、新增的Unicode字符等。这些字符需要4个字节来编码,所以utf8在遇到这些字符时会报错或者出现乱码。
- utf8mb:这是MySQL在5.5.3版本之后增加的一个新的字符集,是utf8的超集。它使用1到4个字节来编码每个字符,最大能表示的Unicode码点是U+10FFFF,即Unicode的所有17个平面。这意味着utf8mb4可以存储任何合法的Unicode字符,包括BMP中的字符和辅助平面中的字符,如Emoji表情、部分罕用汉字、新增的Unicode字符等。由于utf8mb4可以使用4个字节来编码字符,所以它占用的存储空间会比utf8略大一些。
-
兼容性和性能
- 兼容性:utf8mb4是utf8的超集,能够兼容utf8的所有字符并且支持更多字符。如果你的应用需要支持BMP之外的字符,如Emoji表情,那么应该使用utf8mb43。
- 性能:在MySQL中,不同的字符集可以有不同的排序规则,甚至同一个字符集也可以有多种排序规则。例如,utf8mb4_bin按照二进制方式比较字符串,区分大小写和重音符号。排序规则的选择会影响查询性能和字符串比较的准确性。
-
实际应用场景
- utf8:适用于大部分应用场景,特别是那些不需要存储BMP之外的字符的应用。
- utf8mb4:适用于需要存储辅助平面字符的场景,如支持Emoji表情等。在较新版本的MySQL中(5.5.3及以后),utf8mb4是推荐的字符集,因为它提供了更全面的Unicode支持3。
综上所述,utf8mb4由于其更广泛的字符支持范围和更好的兼容性,通常是更优的选择,特别是在需要支持新Unicode字符的应用中。
表gorm是我创建的一个空表
运行刚才编写的Golang程序,如果与下图一样则表示连接成功;如果连接未成功,检查用户名、密码、IP地址、端口号、数据库名等是否填写正确。
-
在
gorm数据库中创建一个Student表gorm采用的命名策略是:表名是蛇形复数,字段名是蛇形单数
type Student struct { ID uint Name string Age int } func main() { DB.AutoMigrate(&Student{}) }gorm会为我们这样生成表结构
CREATE TABLEstudents```(idbigint unsigned AUTO_INCREMENT,namelongtext,agebigint``运行后显示表
students创建完成了
4、显示日志
gorm的默认日志是只打印错误和慢SQL
- 但我们可以自己设置日志的显示方式——以全局的方式显示日志:
var mysqlLogger logger.Interface
mysqlLogger = logger.Default.LogMode(logger.Info)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: mysqlLogger,
})
这种方式会输出全部的日志
- 第二种是以Session的方式显示日志
var mysqlLogger logger.Interface
mysqlLogger = logger.Default.LogMode(logger.Info)
DB = DB.Session(&gorm.Session{
Logger: mysqlLogger,
})
- 第三种是以Debug的方式显示日志
DB.Debug().AutoMigrate(&Student{})
5、模型定义
模型是标准的struct,由Go的基本数据类型,实现了Scanner和Valuer接口的自定义类型及指针或别名组成。
定义一张表
type Student struct {
ID uint // 默认使用ID作为主键
Name string
Age int
Email *string // 使用指针是为了存空值
}
CREATE TABLE `students (id bigint unsigned AUTO_INCREMENT,name longtext,age bigint, email longtext`
常识:小写属性是不会生成字段的
如果觉得上述字段分配的内存空间太大或小,可以使用gorm标签进行修改
修改大小
使用gorm标签修改分配的内存大小有两种方式
Name string `gorm:"type:varchar(16)"`
Name string `gorm:"size:16"`
例如:
type Student struct {
ID uint `gorm:"type:varchar(10)`
Name string `gorm:"type:varchar(16)`
Age int `gorm:"size:3"`
Email *string `gorm:"size:128"`
}
字段标签
type:定义字段类型
size:定义字段大小
column:自定义列名
primaryKey:将列定义为主键
unique:将列定义为唯一键
default:定义列的默认值
not null:不可为空
embedded:嵌套字段
embeddedPrefix:嵌套字段前缀
comment:注释
多个标签之间用;连接
自动生成表结构
// 可以放多个
DB.AutoMigrate(&Student{})
AutoMigrate的逻辑是只新增,不删除,不修改(大小会修改)
例如将Name字段修改为Name1,进行迁移,会多出一个name1字段
6、单表操作
6.1添加记录
添加单条数据
// 添加记录
email := "123456789@qq.com"
s1 := Student{
Name: "abc",
Age: 21,
Gender: true,
Email: &email,
}
result := DB.Create(&s1).Error
看看数据库中有没有添加成功
有两个地方需要注意一下:
1、指针类型是为了更好的存null类型,但是传值的时候也要记得传指针;
2、Create接受的是一个指针,而不是值。
由于我们传递的是一个指针,调用完Create之后,student这个对象上面就有该记录的信息了,如创建的id
DB.Create(&student)
fmt.Printf("%#v\n",student)
还可以使用Create()添加多条记录
abd_email := "123456780@qq.com"
acd_email := "123456783@qq.com"
students := []*Student{
{Name: "abd", Age: 18, Email: &abd_email, Gender: true},
{Name: "acd", Age: 18, Email: &acd_email, Gender: false},
}
result := DB.Create(&students)
添加后,可以在数据库中看到批量添加的两条数据
批量插入数据
要高效地插入大量记录,请将切片传递给Create方法。 GORM 将生成一条 SQL 来插入所有数据,以返回所有主键值,并触发 Hook 方法。 当这些记录可以被分割成多个批次时,GORM会开启一个事务</0>来处理它们。
var students = []Student{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
DB.Create(&students)
在数据库中可以看到创建了三个只有name的数据
6.3 查询
检索单个对象
GORM 提供了 First、Take、Last 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误
// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;
// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;
// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error // returns error or nil
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
例如:使用First()获取按主键升序的第一条记录
var student Student
DB.First(&student)
fmt.Println(student)
根据主键检索
如果主键是数字类型,您可以使用 内联条件 来检索对象。 当使用字符串时,需要额外的注意来避免SQL注入。
db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;
db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;
db.Find(&users, []int{1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);
根据其他条件查询
以检索name为“acd”的学生为例
var student Student
DB.Take(&student,"name = ?","acd")
fmt.Println(student)
使用?作为占位符,将查询的内容放入?中,这样可以有效的防止sql注入
SELECT * FROM studentsWHERE name = 'acd' LIMIT 1
它的原理就是将参数全部转义,如
DB.Take(&student,"name = ?","acd' or 1=1;#" )
SELECT * FROM `students` WHERE name = 'acd' or 1=1;#'LIMIT 1
根据struct查询
var student Student
// 只能有一个主要值
student.ID = 3
DB.Take(&student)
fmt.Print(student)
当使用struct进行查询时,GORM只会使用非零字段进行查询,这意味着如果字段的值为0、''、false或其他零值,则不会用于构建查询条件。
查询多条数据
// 查询多条记录
var studentList []Student
count := DB.Find(&studentList).RowsAffected //获取信息的条数
fmt.Println(count)
for _, student := range studentList {
fmt.Print(student)
}
会打印出students表中的所有数据
查询多条数据,并将其转换为json
// 查询多条记录,并将其转换为json
var studentList []Student
count := DB.Find(&studentList).RowsAffected //获取信息的条数
fmt.Println(count)
data, _ := json.Marshal(studentList)
fmt.Println(string(data))
根据主键列表查询
var studentList []Student
DB.Find(&studentList, []int{1,3,5,7})
DB.Find(&studentList, 1,3,5,7)// 与上面一样的效果
fmt.Println(studentList)
根据其他条件查询
var studentList []Student
DB.Find(&studentList, "name in ?", []string{"abc", "acd"})
6.4 更新与删除
先找到才能更新与删除
Save保存所有字段
用于单个记录的全字段更新,它也会保存所有字段,即使是零值
var student Student
DB.Take(&student, 2)
student.Name = "张三"
student.Age = 0
// 全字段更新
DB.Save(&student)
若要指定更新某个字段,可以使用Select()方法
var student Student
DB.Take(&student, 2)
student.Name = "张三"
student.Age = 0
// 全字段更新
DB.Select("name").Save(&student) // 指定更新name字段,其他字段不更新
批量更新
var studentList []Student
DB.Find(&studentList, []int{1,3,5}).Update("gender",false)
删除
根据结构体删除
// 删除主键为2号的student
DB.Delete(&student, 2)
删除多个
DB.Delete(&Student{}, []int{1,2,3})
// 查询到的切片列表
DB.Delete(&studentList)