GORM简介
GORM作为Go语言广泛应用的关系数据库操作框架,具有功能强大、操作简单易于维护等特点。再加之gorm的作者是国人,其中文文档也相对齐全。
今天以这篇文章记录自己在学习gorm的基础操作,连接数据库进行增删查改的过程。
数据库的选择
由于上个学期刚学习完数据库原理这门课程,在课程实验中使用的是sqlserver,所以我有了一些使用经验。事先已经了解到gorm支持包含sqlserver在内多种常见的数据库,所以第一直觉是使用sqlserver进行gorm的实践。
但是在连接数据库的过程中遇到了一些问题,加之了解到gorm对mysql的适配程度要好一些,就选择了mysql进行实践(在课程实验之前有用过一小段时间的mysql,所以虽然官方文档中的示例程序是使用sqlite,因为没有接触过就没有选用)。
连接数据库
创建数据库
在Go语言中链接数据库之前,首先要保证数据库存在,在mysql中创建一个名为test的空数据库。
安装gorm框架
通过在项目目录下使用命令
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
来下载gorm框架本体和用于mysql的驱动库。完成之后应该会在go.mod文件中自动完成依赖项的整理,如果没有的话可以使用go mod tidy命令来启动整理程序。
完成之后应该是下面这样的:
执行连接
在go文件中,要连接到数据库进行操作首要的就是一个dsn字符串。dsn是data source name数据源名称的缩写,描述了连接到数据库需要的参数信息。
不同数据库的dsn格式不同,这里给出我选用的mysql的dsn格式
user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local
其中,user是数据库用户名称,pass是用户对应的密码。后面一段代表使用tcp连接,连接ip为127.0.0.1代表本机ip,3306是mysql默认的端口号。
斜杠后面是要连接操作的数据库名称,问号后面是一些诸如字符集、时区之类的信息。
main.go中主要的连接逻辑如下。在gorm的config选项中,我们将单数表名限制打开,这是为了不让gorm在数据库中生成表的时候默认将名称变成复数。
以及一点,Go语言中很多地方习惯大驼峰式命名,所以转数据库时会有一个NoLowerCase的选项,默认是false代表会转成小写,这里没有去设置(因为我们需要小写的形式)。
dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
})
if err != nil {
panic("failed to connect database")
} else {
fmt.Println("数据库打开成功!")
}
可以看到数据库连接是成功的。
对数据进行增删查改
数据库准备
这里我使用了之前数据库课程实验中所预定义的三个表s(学生表)、c(课程表)、sc(学生课程选修情况表)作为后续创建新数据库并操作数据的对照。
创建表并插入数据
在gorm框架中,数据库中的一个表对应Go语言的一个struct类型,我们根据已有的数据定义Student、Course、Stu_Cou三个结构体如下。其中在结构体成员后面用字符串字面量定义了一些大小、类型、性质等的限制。
type Student struct {
Sno string `gorm:"size:10;not null"`
Sname string `gorm:"size:20"`
Dept string `gorm:"size:20"`
Age int `gorm:"type:int"`
Sex string `gorm:"size:4"`
}
type Course struct {
Cno string `gorm:"size:10;not null"`
Cname string `gorm:"size:20"`
Cpno string `gorm:"size:10"`
Credit int `gorm:"type:int"`
}
type Stu_Cou struct {
Sno string `gorm:"size:10;not null"`
Cno string `gorm:"size:10;not null"`
Score int `gorm:"type:decimal"`
}
随后我们可以采用如下的AutoMigrate方法,通过传入结构体类型指针,在数据库中生成对应的表。
db.AutoMigrate(&Student{})
db.AutoMigrate(&Course{})
db.AutoMigrate(&Stu_Cou{})
我们可以看到数据库test中已经有了我们要的表,其属性符合我们的要求。
要想在数据库中插入数据,需要实例化一个结构体对象,填写对应的成员值(不写默认为null值,前提是可以为空),随后调用Create方法,传入对应的结构体指针,就可以在对应的表中插入数据。
stu1 := Student{
Sno: "114514",
Sname: "田所浩二",
Dept: "下北泽表演系",
Age: 24,
Sex: "男",
}
cou1 := Course{
Cno: "c114514",
Cname: "野兽学",
Credit: 1919810,
}
stu_cou1 := Stu_Cou{
Sno: "114514",
Cno: "c114514",
Score: 1919810,
}
db.Create(&stu1)
db.Create(&cou1)
db.Create(&stu_cou1)
可以看到我们的有些恶臭的记录已经插入到数据库中了。实际上,也可以使用Model方法(接收一个结构类型对象指针参数)或Table方法(接收一个表名参数)来显式地指出操作的表名,而不是由gorm自己推断。后面的部分代码中有用到这种显式指定。
查询数据
因为我们创建的数据库中的数据太少,所以先手动将提前准备好的数据粘到刚刚创建的数据表中作为测试数据。另外,由于查询部分东西太多,只学习了基础的部分内容。
单查询
gorm查询操作通过一个结构体来判断应该在哪个表中进行查询,并把查询的结果写回该结构体(因此需要传指针)。
stu := Student{}
cou := Course{}
stu_cou := Stu_Cou{}
db.Take(&stu) // 查找一条记录,没有指定特别的顺序,就是数据库表面上的第一条记录
fmt.Println(stu)
db.First(&cou) // 查找《按主键升序》的第一条记录
fmt.Println(cou)
db.Last(&stu_cou) 查找《按主键降序》的第一条记录
fmt.Println(stu_cou)
多查询
gorm通过Find方法实现对于多条记录的查询,并写入查询到的对象列表。
courses := []Course{}
db.Find(&courses)
fmt.Println(courses)
条件查询
值得注意的是,在Take、First、Last和Find方法中,可以内联一个后置参数来方便地进行主键查询(如果是一个值就是查询等于其的记录,如果是一个列表就是查询符合的记录)。
stu := Student{}
db.First(&stu, 114514) // 查询主键(sno)等于114514的记录
fmt.Println(stu)
除此之外,gorm通过Where方法来使用问号转义替换的方式进行有条件的查询。Where方法的功能更加复杂,但功能也更强大。Where方法也可以用内联的方式写成上述后置参数的形式。
stu_cous := []Stu_Cou{}
db.Where("sno = ?", "200511").Find(&stu_cous)
// db.Find(&stu_cous, "sno = ?", "200511") // 内联形式
fmt.Println(stu_cous)
另外,gorm还提供将WHERE查询条件中可选的的NOT和OR语句独立出来作为方法的查询,但不包括AND以及其他LIKE等语句。Not与Or方法的具体使用与Where相似,不再赘述。
另外一个很重要的地方是,可以通过Select方法来显式地指出要查询的字段,不使用此方法时默认是查询全部字段。
stu := Student{}
db.Select("sname", "age").First(&stu)
// db.Select("sname", "dept").First(&stu)
fmt.Println(stu)
更新数据
在学习了查询的操作之后,我们就可以对查询到的记录进行更新或后续的删除操作。
单记录,全字段更新
使用Save方法可以对单条记录进行全字段更新,也可以通过Select方法显式地指出要更新的部分字段。
stu := Student{}
db.Where("sname = ?", "王敏").Take(&stu)
fmt.Println(stu)
stu.Dept = "计算机系"
fmt.Println(stu)
db.Save(&stu)
// db.Select("dept").Save(&stu)
可以看到dept列的值已经被更新了,其他列属性如果有变动的话也会进行更新。
多记录,单字段更新
使用Update方法可以批量设置多条记录的更新值。
stus := []Student{}
db.Where("sno IN ?", []string{"200513", "200514"}).Find((&stus)) // 查第一次
fmt.Println(stus)
db.Model(&Student{}).Where("sno IN ?", []string{"200513", "200514"}).Update("age", 100)
stus = []Student{}
db.Where("sno IN ?", []string{"200513", "200514"}).Find((&stus)) // 查第二次
fmt.Println(stus)
多记录,多字段更新
一个可选的方法是在上面的“多记录,单字段更新”的基础上,通过在后面追加多个Update方法来实现。也可以通过Updates方法,传入一个结构体类型来直接完成多字段的更新。需要注意的是,传入结构体的方法默认情况下不会更新值为0的字段(即使写明,被当做缺省值了),但是可以用Select方法强制更新0值。
stus := []Student{}
db.Where("sno IN ?", []string{"200513", "200514"}).Find((&stus)) // 查第一次
fmt.Println(stus)
db.Model(&Student{}).Where("sno IN ?", []string{"200513", "200514"}).Updates(
Student{
Dept: "计算机系",
Age: 100,
})
// 下面是可选的一种方法
// db.Model(&Student{}).Where("sno IN ?", []string{"200513", "200514"}).Update("age", 100).Update("dept","计算机系")
stus = []Student{}
db.Where("sno IN ?", []string{"200513", "200514"}).Find((&stus)) // 查第二次
fmt.Println(stus)
可以看到一次更新中,两个字段的值都被更新了。
删除数据
删除数据与更新数据逻辑相似,但是只使用一种方法Delete。
stu1 := Student{}
db.Table("student").Where("sno = ?", "114514").Delete(&Student{})
db.Where("sno = ?", "114514").Take(&stu1)
运行之后找不到sno=114514的记录,说明已经被删掉了。
stus := []Student{}
db.Table("student").Where("age = ?", 100).Delete(&Student{})
db.Where("age = ?", 100).Find(&stus)
fmt.Println(stus)
运行之后找不到age=100的记录,说明已经被删掉了。另外注意到的一点是,Take方法在找不到记录的时候会报错,而Find方法则不会。经测试,First与Last方法这几个查询单条记录的都会报错。
总结
以上,学习过程中通过简单使用gorm框架的基础功能实现了对于mysql数据库的增删查改功能,其更多的高级功能以及一些诸如传map参数代替结构体之类的用法要留到实践中慢慢学习掌握了。