这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天
简介
GORM是基于go语言的ORM(Object-Relationl Mapping,即对象关系映射)操作库,使用简单,对开发者友好。
通过把struct结构体中的类型与数据库表中的列属性进行映射,完成通过go语言进行数据库中数据的操作。(具体如何完成映射见下面论述)
目前GORM支持的数据库有MySQL,SQLServer,PostgreSQL,SQLite。可以通过复用/自行开发驱动来连接其他类型的数据库。
MYSQL数据库基本操作
安装依赖
// 安装MySQL依赖
go get -u [gorm.io/driver/mysql](<http://gorm.io/driver/mysql>)
// 安装Gorm依赖
go get -u [gorm.io/gorm](<http://gorm.io/gorm>)
连接数据库
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{})
if err != nil {
panic(err)
}
dsn的全称为Data Source Name,基本格式如下所示:username:password@protocol(address)/dbname?param=value
具体细节可见github.com/go-sql-driv…
连接好数据库后便可以操作数据表,我们可以自己创建数据表,也可以AutoMigrate()方法来刷新数据库中的表格定义,使其保持最新,如果没有对应的话,其会创建新的表。
AutoMigrate()
利用如下所示的代码来创建数据表
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type StudentInfo struct {
StudentID int
Name string
Grade string
}
func main() {
dsn := "root:sss041619@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&StudentInfo{})
}
最终创建的表的格式如下图所示:
至于为什么表名和列名是这样的,就不得不提法GORM的一些默认配置了
- 表名默认为结构体名字的蛇形复数,也就是为什么表名是student_infos了
- 默认结构体中字段名的蛇形作为列名
- 默认结构体中的ID字段作为表中相应的主键
当然,我们可以手动配置表名和列名,这时结构体声明过程中需要添加如下配置:
type StudentInfo struct {
ID int
StudentID int `gorm:"primarykey"`
Name string `gorm:"column:student_names"`
Grade string `gorm:"column:student_grade"`
}
func (s StudentInfo) TableName() string {
return "students"
}
最终的结果如下图所示:
最后,我们还可以通过内嵌结构体的方式将gorm.model内嵌入自定义的结构体中,gorm.model包含的内容有如下几项,读者可以自己尝试
type Model struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt DeletedAt `gorm:"index"`
}
自己创建数据表
首先通过MySQL语句创建一个数据表
CREATE TABLE if not exists message_info
(
message_id bigint AUTO_INCREMENT NOT NULL COMMENT '自增主键,消息唯一标识',
from_user_id bigint NOT NULL COMMENT '消息发出方',
to_user_id bigint NOT NULL COMMENT '消息目标方',
token char(129) NOT NULL COMMENT '消息鉴权',
content varchar(255)NOT NULL COMMENT '消息内容',
create_time char(6) NOT NULL COMMENT '消息在服务器上创建时间',
PRIMARY KEY (`message_id`),
KEY (token) COMMENT'查询聊天记录用'
)ENGINE = InnoDB
DEFAULT charset = utf8mb4;
然后根据表中的各项信息,在go文件中定义结构体,如下图所示:
type MessageInfo struct {
MessageId int
FromUserId int
ToUserId int
Token string
Content string
CreatTime string
}
func (mi MessageInfo) TableName() string {
return "message_info"
}
无需AutoMigrate(),连接数据库后即可使用。
增加数据
操作students数据表,增加数据时使用的代码如下:
s1 := StudentInfo{ID: 1, Name: "zhangsan", Grade: "junior"}
res := db.Create(&s1)
fmt.Println(s1.StudentID) // 1
fmt.Println(res.Error) // nil
运行结果如下:
虽然没有设置StudentID,但因为其是自增主键,因此会将其设为1,并且回填至s1。
也可以批量进行插入,代码如下:
s := []StudentInfo{{Name: "zhangsan_1"}, {Name: "zhangsan_2"}, {Name: "zhangsan_3"}}
db.Create(&s)
结果为:
查询数据
可以使用First()函数查询,获取表中的第一条记录(主键升值)
var s2 StudentInfo
db.First(&s2)
fmt.Println(s2) //{1 1 zhangsan juni
相当于MySQL语句:SELECT * FROM students ORDER BY stddent_id LIMIT 1;
使用Last()获取最后一条记录:
var s2 StudentInfo
db.Last(&s2)
fmt.Println(s2) //{0 4 zhangsan_3 }
相当于MySQL语句SELECT * FROM students ORDER BY id DESC LIMIT 1;
此外,还可以使用Find()函数查找记录,对单个对象 db.Find(&s2) 将不加限制地使用 Find 将查询整个表并仅返回第一个性能不佳且不确定的对象,因此,使用Find()时要配以条件进行查询,当然,First函数也可以配以条件进行查询,不过如果找不到记录,First函数会返回ErrRecordNotFound,但是使用Find查询不到数据时不会返回错误。
var s2 StudentInfo
db.First(&s2, 1) //student_id = 1
fmt.Println(s2) //{1 1 zhangsan junior}
s2.StudentID = 0 //必须加,不然会 AND 上StudentID = 1的条件
db.First(&s2, []int{2, 3, 4}) //stdudent_id in {2,3,4}
fmt.Println(s2) //{0 2 zhangsan_1 }
s2.StudentID = 0
res := db.First(&s2, 100) //student_id = 100
fmt.Println(res.Error) //record not found
s2.StudentID = 0
db.Where("student_names = ?", "zhangsan_2").First(&s2) //Name = zhahhngsan_2
fmt.Println(s2) //{0 3 zhangsan_2 }
s := make([]*StudentInfo, 0)
db.Where("student_names LIKE ?", "zhangsan_%").Find(&s)
for i, _ := range s {
fmt.Println(s[i].Name, s[i].StudentID)
}
//zhangsan_1 2
// zhangsan_2 3
// zhangsan_3 4
在上面的范围查询函数中,我们最终展示了Name和StudentID两个属性,但是查询函数把所有的属性都返回给了s切片,这种情况下造成了资源浪费——
GORM 允许通过 [Select](<https://gorm.io/zh_CN/docs/query.html>) 方法选择特定的字段,如果您在应用程序中经常使用此功能,你也可以定义一个较小的结构体,以实现调用 API 时自动选择特定的字段。
type StudentInfo struct {
ID int
StudentID int `gorm:"primarykey"`
Name string `gorm:"column:student_names"`
Grade string `gorm:"column:student_grade"`
}
type API struct {
StudentID int `gorm:"primarykey"`
Name string `gorm:"column:student_names"`
}
func (s StudentInfo) TableName() string {
return "students"
}
api := make([]*API, 0)
db.Model(&StudentInfo{}).Where("student_names LIKE ?", "zhangsan_%").Find(&api)
for i, _ := range api {
fmt.Println(api[i].Name, api[i].StudentID)
}
//zhangsan_1 2
//zhangsan_2 3
//zhangsan_3 4
更新数据
Gorm更新数据是通过Update函数操作的,Update函数需要传入要更新的字段和对应的值。
db.Model(&StudentInfo{StudentID: 2}).Update("Name", "lisi")
//UPDATE students SET student_names = "lisi" WHERE student_id = 2
更新后的结果如下所示:
删除数据
删除一条数据
删除一条数据时,删除对象必须指定主键,否则会触发批量删除。
db.Delete(&StudentInfo{},1)
s3 := StudentInfo{StudentID: 1}
db.Delete(&s3)
//DELETE FROM students WHERE student_id = 10;
删除多条数据
如果指定的值不包括主属性,那么 GORM 会执行批量删除,它将删除所有匹配的记录。
当然也可以指定多个主键来删除对应的多条数据。
db.Where("stddent_names LIKE ?","zhangsan%").Delete(&StudentInfo{})
//DELETE FROM students WHERE stddent_names LIKE "zhangsan%"";
db.Delete(&StudentInfo{},[]int{1,2,3})
//DELETE FROM students WHERE student_id in (1,2,3);
软删除
如果您的模型包含了一个 gorm.deletedat 字段(gorm.Model 已经包含了该字段),它将自动获得软删除的能力!
拥有软删除能力的模型调用 Delete 时,记录不会从数据库中被真正删除。但 GORM 会将 DeletedAt 置为当前时间, 并且你不能再通过普通的查询方法找到该记录。
如果您不想引入 gorm.Model,您也可以在结构体中添加Deleted gorm.DeletedAt来启用软删除特性。