安装
我使用的是和 mysql 的连接。
项目创建参考GIN的安装。
接下来在 goland 中打开终端,使用 go get 命令来安装库依赖:
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
安装完成后就可以正常使用了。
使用
简单连接
在使用之前我们需要先初始化来建立连接。
这就需要一些数据库的数据:
- 用户名:username
- 密码:password
- 数据库地址:host (一般本地的数据库地址为 127.0.0.1)
- 端口号:port(一般为 3306)
- 数据库名:Dbname
- 连接超时:timeout
接下来是数据库的连接:
username := "root" //用户名
password := "*********" //密码
host := "127.0.0.1" //数据库地址,可以是IP或者域名
port := 3306 //端口号
Dbname := "gorm" //数据库名
timeout := "10s" //超时连接,10秒
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s", username, password, host, port, Dbname, timeout)
//连接mysql,获得Db类型实例,用于后面的数据库读写操作
db, err := gorm.Open(mysql.Open(dsn),&gorm.Config{})
if err != nil {
panic("数据库连接失败,error=" + err.Error())
}
以我的代码为例,我创建了一个名叫 gorm 的数据库,如果连接失败就会抛出异常。
上述操作会写在一个叫 init 的函数里面。在函数之外一般会写一个 *gorm.DB 类型的变量,将连接 mysql 的 db 的值传递过去,便于在其他地方操作数据库。
gorm.Config{}
这个就是 GORM 在数据库建立连接后框架本身做的一些默认配置。
大概有这么多:
type Config struct {
SkipDefaultTransaction bool
NamingStrategy schema.Namer
Logger logger.Interface
NowFunc func() time.Time
DryRun bool
PrepareStmt bool
DisableNestedTransaction bool
AllowGlobalUpdate bool
DisableAutomaticPing bool
DisableForeignKeyConstraintWhenMigrating bool
}
具体参考这篇文章:Go ORM框架 - GORM 踩坑指南
约定
GORM 更倾向于约定而不是配置。默认情况下,GORM 使用 ID 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名。
我们可以修改上述的默认配置:
NamingStrategy: schema.NamingStrategy{
TablePrefix: "", //表名前缀
SingularTable: true, //是否使用单数表名,这里表示要单表名
NoLowerCase: true, //是否大小写转换,这里表示不要小写
},
将其放在 gorm.Config{} 即可生效。
日志
默认情况下,控制台是没有任何日志输出的。
第一种方式,我们可以在 gorm.Config{} 中进行配置。可以使用 GORM 的 logger.Default.LogMode(logger.Info) 来打印日志。
Logger: logger.Default.LogMode(logger.Info),
将其放在 gorm.Config{} 即可生效。
第二种方式是在主函数中,对数据库的对象进行操作:
DB = DB.Session(&gorm.Session{
Logger: logger.Default.LogMode(logger.Info),
})
这样也能够显示日志。
第三种方式是在进行一些操作的时候加上一个 Debug():
DB.Debug().AutoMigrate(&Student{}) //新建一个空白表
加上 Debug() 的时候就能够显示日志。
模型
定义一张表:
type Student struct{
ID uint //默认使用ID作为主键
Name string
Email *string //使用指针是为了存空值 nil
}
小写的属性是不会生成字段的。
自动生成表结构
可以使用函数 AutoMigrate() 函数自动生成表结构,但是它只能新增,不删除也不修改表结构(大小会修改)。
DB.AutoMigrate(&Student{})
例如将结构体 Student 添加一个成员,进行迁移,表中也会新增一个字段。
例如将 Name 修改为 Name1,进行迁移,会多出一个 name1 的字段,原本的 Name 字段还在。
修改字段大小
在默认情况况下,字段的值都非常的大,有些字段可能用不到这么大的存储量,所以就需要进行一些修改。
可以使用字段标签 gorm:"size:x" (x 表示字节个数)来修改存储的大小:
type Student struct {
ID uint `gorm:"size:10"`
Name string `gorm:"size:16"`
Email *string `gorm:"size:128"`
}
这样的话,就可以将字段大小进行细致的修改,size 后面的参数为字节的长度,比如 Name 字段给的是 128 位字节数进行存储。
另外还有一种方式可以修改字段大小:
Name string `gorm:"type:varchar(16)"`
这样也和上面修改的大小一样。
字段标签
type 定义字段类型,也可以顺便定义大小
size 定义字段大小
column 自定义列名
primaryKey 将列定义为主键
unique 将列定义为唯一键
default 定义列的默认值
not bull 不可为空
embedded 嵌套字段
embeddedPrefix 嵌套字段前缀
comment 注释
多个标签使用 ; 隔开。
column 字段需要注意,后面的字符串是什么,字段名就是什么,要注意有没有多添加一个空格或者其他符号。
表单的增删改查
增
插入单个数据
首先需要新建一个模型 struct:
type Student struct {
ID uint `gorm:"size:3"`
Name string `gorm:"size:8"`
Age int `gorm:"size:3"`
Gender bool
Email *string `gorm:"size:128"`
}
我们就可以在 main 函数中利用之前学到的连接数据库来创建表:
DB.AutoMigrate(&Student{})
我们如何向表中添加数据呢?
我们的表是根据结构体来创建的,所以添加数据的过程就相当于是实例化结构体。
首先先实例化一个数据:
email := "123456789@qq.com"
//添加记录,就相当于实例化结构体
s1 := Student{
Name: "cfd",
Age: 20,
Gender: true,
Email: &email, //注意 Email 的值是指针
}
err := DB.Create(&s1).Error
fmt.Println(err) //在这里直接输出
此时就将数据创建成功了。
如果我们不写 Name 的话,name 就会是空值,其他列也是一样。因为除了主键不为空,其他的都没有设置不能为空。
设置 email 为指针类型是因为这样可以更好的传递空值,直接赋值为 nil 就行
另外,Create 函数传递的是指针,所以当添加完一个数据之后,s1 的所有值也都被赋值了,比如 ID 就被赋值了。
在 Create 函数后面加上 .Error 就可以获取它的报错信息,如果为空,则没有报错,反之,就能够更好地查看报错原因。
插入多个数据
知道了如何插入单个数据后,批量插入的方式就变得很简单。
我们先定义一个切片,存放多个学生数据:
var studentList []Student
for i := 0; i < 10; i++ {
s1 := Student{
Name: fmt.Sprintf("cfd%d号", i+1), //Sprintf 的返回值为字符串
Age: 20 + i + 1,
Gender: true,
Email: nil,
}
studentList = append(studentList, s1)
}
然后使用 Create 函数即可:
DB.Create(&studentList) //将整个切片传递进去
查
查找单条数据数据可以使用 Take 函数:
var student Student
DB.Take(&student)
fmt.Println(student)
student = Student{}
在默认情况下查找的是表中的第一条数据。函数会将查询来的值放到 student 中。
还有 First 函数和 Last 函数,分别查询表中的第一条数据和最后一条数据:
DB.First(&student) //查找第一条数据
fmt.Println(student)
student = Student{}
DB.Last(&student) //查找最后一条数据
fmt.Println(student)
student = Student{}
除此之外,还可以根据条件来查询。
- 根据主键来查询:
DB.Take(&student, 4) //查询主键为 4 的数据
第二个参数为主键,也可以写成字符串形式:"4",效果是一样的。
- 根据其他条件查询:
DB.Take(&student, "name = ?", "cfd3号") //查询名字为 cfd3号 的数据
使用 ? 作为占位符,来将查询的内容放到 ? 中,可以有效地防止 sql 注入。
根据 struct 查询
我们还可以使用 struct 来查询。
例如给 Student 的一个实例 student 一个主要值:
student.ID = 5
DB.Take(&student)
fmt.Println(student)
student = Student{}
上述例子是查询 ID 为 5 的数据。但是需要注意的是,只能有一个主要值。
但是不能使用除了主键之外的值比如名字 name 来查询或者年龄 age 来查询。
获取查询结果
获取查询的记录数。
我们可以通过 Find 函数的 RowsAffected 来获取查询的记录数:
student.ID = 5
count := DB.Find(&student).RowsAffected
fmt.Println(count) //结果为 1
是否查询失败。
我们可以通过 Find 函数的 Error 来获取查询是否失败:
err := DB.Find(&student).Error
查询失败的原因有很多种,比如查询条件错误、sql 的语法错误等等。因此可以使用 gorm.ErrRecordNotFound 来进行判断:
err := DB.Take(&student, 3).Error
if err!=nil {
switch err {
case gorm.ErrRecordNotFound:
fmt.Println("没有找到")
default:
fmt.Println("sql语句错误")
}
}
查询所有数据
可以使用 Find 函数查询所有的数据:
var studentList []Student
count := DB.Find(&studentList).RowsAffected
fmt.Printf("查询条数为%d\n", count)
for _, std := range studentList {
fmt.Println(std)
}
上述代码是查询表中所有的数据全部储存到 studentList 这个切片中,然后逐一打印出来,还获取到了查询次数。
我们还可以将查询到的数据转化为 json 数据:
//将查询到的数据转化为json数据
data, _ := json.Marshal(studentList)
fmt.Printf(string(data))
由于储存的 email 是指针类型,所以直接打印的时候会打印地址。
而将其转化为 json 数据后,序列化之后就能够让我们看懂数据是什么。
根据 ID 列表查询
我们依次输入多个 ID 来进行查询,我们将它称为 ID 列表:
var studentList []Student
DB.Find(&studentList, []int{3, 4, 5, 6, 7, 8, 9})
fmt.Println(studentList)
这样我们就查询了 ID 为 3, 4, 5, 6, 7, 8, 9 的数据。当然切片类型不止为 int 类型还可以是 string 类型。
使用其他条件列表查询
除了使用 ID 列表来进行查询之外,还可以使用其他条件列表来进行查询。
例如使用名字列表来查询:
var studentList []Student
DB.Find(&studentList, "name in (?)", []string{"cfd4号", "cfd5号", "cfd6号"})
fmt.Println(studentList)
需要注意的是,中间的占位符应该加上括号,并且使用 in
改
Save
用于单个记录的全字段更新。它会保存所有字段,哪怕是零值。
比如需要更改 ID 为 3 的这一条数据,我们首先需要找出这条数据:
var student Student
DB.Take(&student, 3)
现在把数据存储到 student 里面了,我们将年龄修改成 60:
student.Age = 60
接着就可以使用 Save 函数进行更新:
DB.Save(&student)
我们看这条日志显示的 SQL 语句:
根据这个 SQL 的语句,我们发现它会把所有字段全部更新一遍。所以说,我们就可以一次性更新这条数据的许多个值。
当然,也可以选择性的只更新某一个字段,这就需要用到 Select 函数。
比如我只想更新 age 字段:
DB.Debug().Select("age").Save(&student) //实现了单个字段的更新
update
使用 update 函数可以让很多条数据的某一字段更新。
比如我们让 ID 为 3,4,5,6,7 的数据的 gender 字段全部改成 false:
var studentList []Student
DB.Find(&studentList, []int{3, 4, 5, 6, 7}).Update("gender", false)
需要注意的是,Update 函数必须在 Find 函数后面点出,否则没有用。
这是输出的日志里面的 SQL 语句:
updates
这个相比于上面一个,是可以来更新多个字段的数据。
首先看看使用结构体的方式更新数据。
比如我们可以让 ID 为 3,4,5,6,7 的数据的 age 字段的数据全部改成 50 并且将 gender 字段全部改成 true:
var studentList []Student
DB.Find(&studentList, []int{3, 4, 5, 6, 7}).Debug().Updates(Student{
Age: 60,
Gender: true,
})
updates 是传入一个结构体,写入需要更改的数据。但是他不能将数据修改为零值。比如在这个例子中,我想把 gender 字段全部改成 false,也就是零值。运行之后 gender 的值并不会改变。
另一种方式是使用 map 这个数据结构来更新数据,就可以将数据修改为零值:
var studentList []Student
DB.Find(&studentList, []int{3, 4}).Debug().Updates(map[string]any{
"gender": false,
})
这里的 "gender" 是数据库里表的字段名。这样就可以 gender 字段全部改成 false。
删
删除操作就只有一个 Delete 函数。
可以根据主键进行删除:
var student Student
DB.Delete(&student, 12)
这样就删除了 ID 为 12 的数据。
Delete 函数一般是配合查询函数一起使用,查询到某一条数据后将其删除:
var student Student
DB.Take(&student, 11)
DB.Delete(&student)
这样就删除了 ID 为 11 的数据。
Delete 函数还能批量删除,用法和前面的 Find 函数类似:
var student Student
DB.Delete(&student,[]int{3,4,5,6,7})
这样就删除了 ID 为 3,4,5,6,7 的数据。
完整代码地址:hdheid/GormStudy