GORM
GORM 是 Go 语言中最受欢迎的 ORM 库之一,它提供了强大的功能和简洁的 API,让数据库操作变得更加简单和易维护。
需要的环境和工具
- 安装下载Go环境;
- 开发工具GoLand;
- MySql数据库;
- Navicat数据库管理工具;
写在前面
在今天的实践过程中,出现了几个错误和需要注意的地方,在此提前罗列出来,避坑!避坑!避坑!
- 方法名首字母要大写。本文中的curd.go文件中定义了对数据库增删改查操作的方法,这些方法名的首字母需要大写,否则在main函数中调用时找不到。
- user.go文件中定义的结构体User的属性名需要大写。
- 第一次打开项目目录,并创建main.go文件后,执行main函数时会报如下错误,执行不了。
gopls was not able to find modules in your workspace.
When outside of GOPATH, gopls needs to know which modules you are working on.
You can fix this by opening your workspace to a folder inside a Go module, or
by using a go.work file to specify multiple modules.
See the documentation for more information on setting up your workspace:
https://github.com/golang/tools/blob/master/gopls/doc/workspace.md.go list
报错原因: 当前目录下找不到workplace。
解决: 在终端输入如下命令: go mod init 根目录 然后会在根目录下生成go.mod文件。
- 报错如下:
Error running 'go build main.go': Cannot run program "C:\Users\malong\AppData\Local\JetBrains\GoLand2023.2\tmp\GoLand\___go_build_main_go.exe" (in directory "D:\my_study\go_language\go_curd_demo"): CreateProcess error=216, 该版本的 %1 与你运行的 Windows 版本不兼容。请查看计算机的系统信息,然后联系软件发布者。
原因: 找不到main或者一个项目中含有多个main,只保留主函数所存在的文件为
package main ,将其他文件下的换为文件名。
解决:
创建项目
创建根目录go_curd_demo,在开发工具GoLand中打开如图所示,根目录下创建config、curd、entity三个子目录,子目录下分别config.go、curd.go、user.go文件,分别是用来连接数据库配置、增删改查方法、对应数据库的实体对象user。在根目录下创建db_config.yml配置文件,里面存放的是连接数据的参数信息。
安装GORM
在终端输入如下代码
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
连接到MySql数据库
import中是导入需要的模块,main函数的参数说明:user连接数据库的用户名,pass是连接密码,()内是ip+端口,本机为(localhost:3306)或(127.0.0.1:3306),dbname是连接的数据库名,后面是通用参数。
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
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{})
}
本文中我使用的数据库是本机中的exercise,数据库中的表为users表。
如下是数据库表users结构。
user.go文件
该文件中只定义了User结构体,用于存放从数据库表Users中获取的数据,与该表形成映射关系。
package entity
type User struct {
Id int `json:"id"`
Name string `json:"name"`
Sex string `json:"sex"`
Age int `json:"age"`
}
db_config.yml文件
该文件中存放的是连接数据需要的参数信息,大家使用时需要注意yml的数据格式。
mysql:
driver: mysql
url: localhost:3306
username: root
password: ml105237
connDbName: exercise
config.go文件
该文件的作用是用于连接数据库,在import中导入gorm框架、mysql数据库等相关模块。文件中定义了Database结构体,用于存放读取的数据库参数信息。此外还定义了ConnectMysql方法,用于连接数据库,首先通过viper读取配置文件db_config.yml中的配置信息,然后动态拼接数据库连接信息,最后通过gorm.open()方法连接数据库。
package config
import (
"fmt"
"github.com/spf13/viper"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 从框架获取数据库操作db
var db *gorm.DB
// 定义连接数据库信息
type DataBase struct {
driver string
url string
username string
password string
connDbName string
}
func ConnectMysql() *gorm.DB {
// 读取连接配置文件db_config.yml
viper.SetConfigFile("db_config.yml")
err := viper.ReadInConfig()
if err == nil {
fmt.Println("===配置文件读取成功===")
}
// 赋值读取的属性
database := DataBase{
driver: viper.GetString("mysql.driver"),
url: viper.GetString("mysql.url"),
username: viper.GetString("mysql.username"),
password: viper.GetString("mysql.password"),
connDbName: viper.GetString("mysql.connDbName"),
}
//处理连接语句
var dns string
dns = database.username + ":" + database.password + "@tcp(" + database.url + ")/" + database.connDbName + "?charset=utf8mb4&parseTime=True&loc=Local"
// 连接数据库
db, err := gorm.Open(mysql.Open(dns), &gorm.Config{})
if err == nil {
fmt.Println("!!!数据库连接成功!!!")
}
return db
}
curd.go文件
该文件下是对数据库增删改查的方法,可以理解为MVC中Mapper接口类,因为要实现对数据库的操作,所有要导入连接数据库config和实体entity。定义了Result结构体,用于统一的返回数据信息(id-操作的数据user.id,err-返回的错误信息,row-操作数据的行数)。在该文件中有如下几个方法,新增数据有单条新增、多条新增和指定字段新增数据。修改数据有:通过用户id修改单个字段和多个字段方法;查询数据:单条查询和多条查询;删除方法为通过用户id删除数据。
package curd
import (
"go_curd_demo/config"
"go_curd_demo/entity"
)
type Result struct {
id int
err error
row int
}
var db = config.ConnectMysql()
// 新增数据——单条
func Insert(user entity.User) Result {
res := db.Create(&user)
result := Result{id: user.Id, err: res.Error, row: int(res.RowsAffected)}
return result
}
// 新增数据——多条
func InsertMore(user []entity.User) Result {
res := db.Create(user)
result := Result{err: res.Error, row: int(res.RowsAffected)}
return result
}
// 通过指定字段新增数据
func InsertByColumn(user entity.User) Result {
res := db.Select("Name", "Age").Create(&user)
result := Result{err: res.Error, row: int(res.RowsAffected)}
return result
}
// 根据用户id修改姓名
func UpdateById(newName string, user entity.User) Result {
res := db.Model(&user).Update("name", newName)
result := Result{id: user.Id, err: res.Error, row: int(res.RowsAffected)}
return result
}
// 根据用户id修改多字段
func UpdatesById(user entity.User, name string, sex string, age int) Result {
res := db.Model(&user).Updates(entity.User{Name: name, Sex: sex, Age: age})
result := Result{id: user.Id, err: res.Error, row: int(res.RowsAffected)}
return result
}
// 查询所有用户
func SelectAll() []entity.User {
var users []entity.User
db.Find(&users)
return users
}
// 根据用户id查询用户
func SelectById(id int) entity.User {
user := entity.User{}
db.First(&user, id)
return user
}
// 根据用户ID删除数据
func DeleteById(user entity.User, id int) Result {
res := db.Delete(&user, id)
result := Result{id: user.Id, err: res.Error, row: int(res.RowsAffected)}
return result
}
// 根据id删除单条数据
func Delete(user entity.User) Result {
res := db.Delete(&user)
result := Result{id: user.Id, err: res.Error, row: int(res.RowsAffected)}
return result
}
main.go文件
main()函数中是对各个执行方法的调用,此处为了便于展示,将增删改查方法都罗列在了一起,执行的时候需要单独在main函数中执行。
package main
import (
"fmt"
"go_curd_demo/curd"
"go_curd_demo/entity"
)
func main() {
// 1.新增用户--单条
user1 := entity.User{Name: "张三", Sex: "男", Age: 25}
result1 := curd.Insert(user1)
fmt.Println(result1)
// 2.新增用户——多条
users1 := []entity.User{
entity.User{Name: "小马", Sex: "男", Age: 33},
entity.User{Name: "李四", Sex: "男", Age: 40},
entity.User{Name: "王五", Sex: "男", Age: 5},
}
result2 := curd.InsertMore(users1)
fmt.Println(result2)
// 3.新增数据 通过指定的字段 此处以name和age字段为例
user3 := entity.User{Name: "张名", Age: 15}
result3 := curd.InsertByColumn(user3)
fmt.Println(result3)
// 4.根据用户id修改姓名
user4 := entity.User{Id: 1}
result4 := curd.UpdateById("唐三", user4)
fmt.Println(result4)
// 5.根据用户id修改多个字段
user5 := entity.User{Id: 1}
result5 := curd.UpdatesById(user5, "小舞", "女", 15)
fmt.Println(result5)
// 6.查询所有数据
users6 := curd.SelectAll()
for i := 0; i < len(users6); i++ {
fmt.Println(users6[i])
}
// 7.根据id查询用户
id7 := 4
user7 := curd.SelectById(id7)
fmt.Println(user7)
// 8.根据id查询用户
id8 := 2
user8 := curd.SelectById(id8)
fmt.Println(user8)
// 9.删除——根据用户id删除
users9 := curd.SelectAll()
fmt.Printf("删除前---数据库表信息\n")
for i := 0; i < len(users9); i++ {
fmt.Println(users9[i])
}
// 根据用户id删除数据
result9 := curd.Delete(entity.User{})
fmt.Println("删除的用户为:", result9)
users10 := curd.SelectAll()
fmt.Printf("删除后---数据库表信息\n")
for i := 0; i < len(users10); i++ {
fmt.Println(users10[i])
}
}
实践操作
新增用户
Gorm 新增记录:在新增数据时GORM封装了SQL语句,向开发者提供了Create()方法用于添加数据。首先定义User,并初始化数据,执行 db.Create(&u)插入用户数据。
单条新增
当前查看数据库表users中只有两条数据,如下图所示。
func main() {
//新增用户--{“张三”, “男”, 25}
user1 := entity.User{Name: "张三", Sex: "男", Age: 25}
result := curd.Insert(user1)
fmt.Println(result)
}
输出结果如下:(因为前文中定义了统一的返回结果,第一个为用户id,第二个为发生错误时的信息,第三个为执行有效操作的条数,下文中不再做说明)
{13 <nil> 1}
数据库中可以看到新增数据成功。
批量新增
可以使用切片的方式同时新增多条数据,下面同时添加三条数据。
func main() {
//新增用户——多条
users := []entity.User{
entity.User{Name: "小马", Sex: "男", Age: 33},
entity.User{Name: "李四", Sex: "男", Age: 40},
entity.User{Name: "王五", Sex: "男", Age: 5},
}
result := curd.InsertMore(users)
fmt.Println(result)
}
输出结果 (同时新增多条数据,所有没有处理多条的id)
{0 <nil> 3}
查看数据库,三条数据新增成功。
通过指定的字段新增用户
通过指定name和age两个字段来新增数据。
func main() {
//新增数据 通过指定的字段 此处以name和age字段为例
user := entity.User{Name: "张名", Age: 15}
result := curd.InsertByColumn(user)
fmt.Println(result)
}
查看数据库
修改数据
根据用户id修改用户姓名
此处修改id=1的用户姓名,将姓名由原来的“小明”改为“唐三”,修改前的数据如下。
执行修改函数
func main() {
//根据用户id修改姓名
user := entity.User{Id: 1}
result := curd.UpdateById("唐三", user)
fmt.Println(result)
}
输出结果
{1 <nil> 1}
查看数据库,id=1的用户名已修改成功。
根据用户ID修改多个字段
此处修改id=1用户的姓名、性别和年龄字段,具体修改信息如下:
func main() {
//根据用户id修姓名、性别、年龄字段
user := entity.User{Id: 1}
result := curd.UpdatesById(user, "小舞", "女", 15)
fmt.Println(result)
}
查看数据库,数据修改成功。
查询数据
查询多条
查询users表中的所有数据。
func main() {
//查询所有数据
users := curd.SelectAll()
for i := 0; i < len(users); i++ {
fmt.Println(users[i])
}
}
输出结果:
{1 小舞 女 15}
{2 小hua 男 19}
{13 张三 男 25}
{17 小马 男 33}
{18 李四 男 40}
{19 王五 男 5}
{20 张名 15}
查询单条——根据用户id查询
func main() {
// 根据id查询用户
id := 2
user := curd.SelectById(id)
fmt.Println(user)
}
输出结果:
{2 小hua 男 19}
删除数据
根据ID删除
GORM提供了db.Delete(&user{}, id)方法删除指定id的用户信息,此处删除id=20的用户信息,删除前后分别输出数据表中的用户信息。
func main() {
// 删除前先查询数据库
users := curd.SelectAll()
fmt.Printf("删除前---数据库表信息\n")
for i := 0; i < len(users); i++ {
fmt.Println(users[i])
}
// 根据用户id删除数据
result := curd.DeleteById(entity.User{Id: 20}, 20)
fmt.Println("删除的用户为:", result)
// 删除后先查询数据库
users1 := curd.SelectAll()
fmt.Printf("删除后---数据库表信息\n")
for i := 0; i < len(users1); i++ {
fmt.Println(users[i])
}
}
输出结果
删除前---数据库表信息
{1 小舞 女 15}
{2 小hua 男 19}
{13 张三 男 25}
{17 小马 男 33}
{18 李四 男 40}
{19 王五 男 5}
{20 张名 15}
删除的用户为: {20 <nil> 1}
删除后---数据库表信息
{1 小舞 女 15}
{2 小hua 男 19}
{13 张三 男 25}
{17 小马 男 33}
{18 李四 男 40}
{19 王五 男 5}
删除单条数据
GORM提供了db.Delete(&user{})方法删除单条用户信息,虽然参数上与上一方法有所区别,但实质上也是通过id删除用户信息,此处在提供的User中id=19,删除该用户信息。
func main() {
// 删除前先查询数据库
users := curd.SelectAll()
fmt.Printf("删除前---数据库表信息\n")
for i := 0; i < len(users); i++ {
fmt.Println(users[i])
}
// 根据用户id删除数据
result := curd.Delete(entity.User{Id: 19})
fmt.Println("删除的用户为:", result)
// 删除后先查询数据库
users1 := curd.SelectAll()
fmt.Printf("删除后---数据库表信息\n")
for i := 0; i < len(users1); i++ {
fmt.Println(users[i])
}
}
输出结果:
删除前---数据库表信息
{1 小舞 女 15}
{2 小hua 男 19}
{13 张三 男 25}
{17 小马 男 33}
{18 李四 男 40}
{19 王五 男 5}
删除的用户为: {19 <nil> 1}
删除后---数据库表信息
{1 小舞 女 15}
{2 小hua 男 19}
{13 张三 男 25}
{17 小马 男 33}
{18 李四 男 40}
总结
本文主要是通过GORM框架连接MySQL数据,实现了基本的新增、修改、删除和查询功能,GORM将sql语句进行封装,对开发者提供了对应的CURD调用方法,使用起来非常方便。与其他编程语言框架相比,GORM对数据库进行增删改查时更加便捷,而且提供的调用接口更多,这可能也是GO语言的特点之一,简短的逻辑结构同样能够实现相应的功能。在本文的实践过程中,需要注意那些非功能性的问题,例如文章开头提出的细节问题和各种报错,这种细节上的疏忽,往往会带来很多不必要的麻烦。
注意:
- 查询数据时若使用First,需要注意当查询不到数据时会返回ErrRecordNotFound错误。
- 使用Find查询数据时,若没有数据不会返回错误。
- 使用结构体作为查询条件时,GORM只会查询非零值字段,当字段值为0、false时字段不会被作为查询条件(更新时也是如此),此时可以使用Map集合来作为参数。
- 删除数据时GORM还提供了软删除,数据不会从数据库真正删除,本文没有举例软删除示例,可参考GORM文档学习使用。