使用GORM连接数据库,并实现增删改查 | 豆包MarsCode AI刷题

184 阅读9分钟

使用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中utf8uft8mb4字符集的区别:

  • 编码范围和存储空间

    • 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表情,那么应该使用utf8mb4‌3。
    • 性能‌:在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 TABLE students```(id bigint unsigned AUTO_INCREMENT,name longtext,age bigint``

    运行后显示表students创建完成了

image.png

4、显示日志

gorm的默认日志是只打印错误和慢SQL

  • 但我们可以自己设置日志的显示方式——以全局的方式显示日志:
 var mysqlLogger logger.Interface
 mysqlLogger = logger.Default.LogMode(logger.Info)
 ​
 db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
         Logger: mysqlLogger,
     })

这种方式会输出全部的日志

image.png

  • 第二种是以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的基本数据类型,实现了ScannerValuer接口的自定义类型及指针或别名组成。

定义一张表

 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

看看数据库中有没有添加成功

image.png

有两个地方需要注意一下:

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)

添加后,可以在数据库中看到批量添加的两条数据

image.png

批量插入数据

要高效地插入大量记录,请将切片传递给Create方法。 GORM 将生成一条 SQL 来插入所有数据,以返回所有主键值,并触发 Hook 方法。 当这些记录可以被分割成多个批次时,GORM会开启一个事务</0>来处理它们。

 var students = []Student{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
     DB.Create(&students)

在数据库中可以看到创建了三个只有name的数据

image.png

6.3 查询

检索单个对象

GORM 提供了 FirstTakeLast 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 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)

image.png

根据主键检索

如果主键是数字类型,您可以使用 内联条件 来检索对象。 当使用字符串时,需要额外的注意来避免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)

image.png

使用?作为占位符,将查询的内容放入?中,这样可以有效的防止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)

image.png

当使用struct进行查询时,GORM只会使用非零字段进行查询,这意味着如果字段的值为0、''、false或其他零值,则不会用于构建查询条件。

查询多条数据
 // 查询多条记录
     var studentList []Student
     count := DB.Find(&studentList).RowsAffected //获取信息的条数
     fmt.Println(count)
     for _, student := range studentList {
         fmt.Print(student)
     }

会打印出students表中的所有数据

image.png

查询多条数据,并将其转换为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)