一.数据处理
一.引入
1.配置开发环境
首先在创建项目时增添GOPROXY
路径
1
GOPROXY=http://goproxy.cn,direct
之后获取GORM
文件以及gin
文件
1
2
3
go get gorm.io/driver/mysql
go get gorm.io/gorm
go get github.com/gin-gonic/gin
2.连接
连接MySQL
数据库首先需要DSN
字符串。
DSN
(Data Source Name)是一串用于在数据库连接中标识和定位数据库的特殊字符串。这串字符串包含了数据库连接所需的关键信息,以便应用程序能够准确地连接到目标数据库。
1
2
3
4
5
6
username := "root"
password := "123456"
host := "127.0.0.1"
port := 3306
Dbname := "gorm"
timeout := "10s"
上述数据是我们连接数据库必须的数据。
1
2
3
4
5
6
7
8
9
10
11
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")
}
一般使用的dsn
即上述代码中Sprintf
的内容。
此时便可以定义一个全局变量来存储db
的信息
1
var DB *gorm.DB
为保证数据的一致性,GORM
会在事务里执行写入操作(创建、更新、删除),若无此需求则可进行关闭,来获得性能提升。
1
2
3
db,err := gorm.Open(mysql.Open("dsn"),&gorm.Config{
SkipDefaultTransaction:true,
})
3.日志
通过设置日志,可以查看GORM
的SQL
语句和错误信息
1
mysqllogger := logger.Default.LogMode(logger.Info)
其中mysqllogger
的数据类型为logger.Interface
之后只需将该日志接入数据库中即可
1
2
3
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger:mysqllogger,
})
我们也可以不在初始化的时候引入日志,而是创建会话日志
1
2
3
DB = DB.Session(&gorm.Session{
Logger: mysqllogger,
})
或者我们可以直接使用GORM
提供的Debug()
方法来实现日志,分割线下方是Debug()
方法的源码,可以发现Debug
也是使用会话日志来实现的。
1
2
3
4
5
6
7
8
_ = DB.Debug().AutoMigrate(&Student{})
/*----------------------------------------------*/
func (db *DB) Debug() (tx *DB) {
tx = db.getInstance()
return tx.Session(&Session{
Logger: db.Logger.LogMode(logger.Info),
})
}
二.创建表格
1.创建
在GORM
内,一个结构体(类)对应一个表,因此,我们可以通过定义结构体来定义一个表。
1
2
3
4
5
6
type Student struct {
ID int
Name string
Age int
}
_ = DB.AutoMigrate(&Student{})
此处AutoMigrate
便会根据传入的空结构体的字段信息来创建表,表的名称便是该结构体的名称。
创建表的过程存在一定的自定义内容,其位于schema.NamingStrategy
中
1
2
3
4
5
6
7
type NamingStrategy struct {
TablePrefix string
SingularTable bool
NameReplacer Replacer
NoLowerCase bool
IdentifierMaxLength int
}
其中,TablePrefix
可以让创建的表增添某一特定前缀,SingularTable
即命名表时更倾向于使用单数表名,NoLowerCase
则禁止将大写转为小写。
此外,我们可以通过定义指针类型来实现数据库中的NULL
,如下述代码中Email
字段为*string
类型,此时可以向该字段传空值。
1
2
3
4
5
6
type Student struct {
ID int
Name string
Email *string
}
_ = DB.AutoMigrate(&Student{})
AutoMigrate
的逻辑是只新增,不删除、不修改。
2.自定义类型
但我们会发现,通过默认设置的AutoMigrate
创建的表存在一定问题
1
CREATE TABLE `students` (`id` bigint AUTO_INCREMENT,`name` longtext,`age` bigint,PRIMARY KEY (`id`))
其数据类型使用的是bigint
、longtext
,占用的存储空间过大。
此时,我们可以通过在结构体中增添gorm
标签来限制大小
1
2
3
4
5
6
7
8
9
10
11
type Student struct {
ID int `gorm:"size:10"`
Name string `gorm:"size:16"`
Age int `gorm:"size:3"`
}
//或者直接定义数据类型
type Student struct {
ID int `gorm:"type:int"`
Name string `gorm:"type:varchar(20)"`
Age int `gorm:"type:tinyint(3)"`
}
此时再运行
1
2
ALTER TABLE `students` MODIFY COLUMN `name` varchar(16);
ALTER TABLE `students` MODIFY COLUMN `age` tinyint;
常见的gorm
标签还包括:
标签 | 作用 |
---|---|
type | 设置字段的数据类型 |
size | 设置字段的数据大小(按位数计算) |
column | 自定义字段的名称 |
primaryKey | 设置主键 |
unique | 设置唯一性 |
autoIncrement | 数据自增 |
not null | 不为空 |
default | 设置缺省值 |
precision和scale | 设置DECIMAL 的精度和小数位数 |
comment | 增添注释 |
check | 增添CHECK 约束 |
连用多个标签时,中间用;
隔开。
三.单表操作
1.单表插入
单表插入的数据需要我们自行实例化一个结构体,并将其地址传入DB.Create()
内
1
2
3
4
5
6
7
8
s1 := Student{
Name: "Outer",
Age: 18,
}
err := DB.Create(&s1).Error
if err != nil {
fmt.Println(err)
}
这样我们便得到的一段代码行
1
INSERT INTO `students` (`name`,`age`) VALUES ('Outer',18)
注意:
同之前所述,若定义结构体时字段类型为指针,则默认空值为NULL
,若不为指针,则字符串对应的空值为'\0'
(空字符串),整型对应的空值为0
,且不能传nil
值(指针类型可以)。
1
2
3
4
5
6
7
8
9
10
s1 := Student{
Name: nil,
Age: 18,
}
err := DB.Create(&s1).Error
if err != nil {
fmt.Println(err)
}
/*--------------------------------------------*/
ERROR:cannot use nil as string value in struct literal
且DB.Create()
可批量插入数据,我们可以选择传入一个切片的指针。
1
2
3
4
5
6
7
8
9
slice := make([]Student, 0)
for i := 0; i < 100; i++ {
s1 := Student{
Name: "Outer" + strconv.Itoa(i) + "号",
Age: 18,
}
slice = append(slice, s1)
}
DB.Create(&slice)
2.单表查询
可以通过多种方法来查询单行数据
其中最简单的便是DB.First
和DB.last
,通过对主键进行排序(升序),来获取第一行和最后一行的数据。
1
2
3
4
5
6
7
DB.First(&student)
fmt.Println(student)
DB.Last(&student)
fmt.Println(student)
//输出结果
{1 Outer0号 18}
{100 Outer99号 18}
此外,我们可以通过DB.Take()
来进行查询
1
2
3
4
5
var student Student
DB.Take(&student)
fmt.Println(student)
//输出内容:
{1 Outer0号 18}
通过DB.Take()
方法来获取该结构体对应的表的第一行数据
也可以向Take()
内增添主键的值来查询对应主键的数据。
1
2
3
4
5
var student Student
DB.Take(&student, 84)
fmt.Println(student)
//输出
{84 Outer83号 18}
若超出查询范围,则会返回Record not found
此外也可以通过格式化查询来实现其他字段的查询操作。
1
2
3
4
DB.Take(&student, "name = ?", "Outer8号")
DB.Take(&student, fmt.Sprintf("name = '%s'","Outer8号"))
//对应SQL语句
SELECT * FROM Students WHERE name = 'Outer8号';
同样的,若Take()
内的结构体有一个字段已确定(只能为主键),他会根据该主键字段的信息进行查询。
1
2
3
4
5
6
var student Student
student.ID = 56
DB.Take(&student)
fmt.Println(student)
//输出结果
{56 Outer55号 18}
通过增添RowAffected
字段可以返回有多少行受到影响
1
count := DB.Take(&student).RowsAffected
DB.Find()
可以实现数据的多行查询
1
2
3
4
5
6
7
8
9
var slice []Student
DB.Find(&slice, []int{45, 23, 54, 67})
fmt.Println(slice)
//或
var slice []Student
DB.Find(&slice, "name in (?)", []string{"Outer23号", "Outer87号"})
fmt.Println(slice)
//对应SQL语句
SELECT * FROM `students` WHERE name in ('Outer23号','Outer87号');
Take
和Find
的区别
1
2
3
4
5
DB.Take(&student, 2)
DB.Find(&student, 2)
//对应的SQL语句
SELECT * FROM `students` WHERE `students`.`id` = 2 LIMIT 1;
SELECT * FROM `students` WHERE `students`.`id` = 2;
二者均能将对应行的数据存入结构体,但Find
也可以将数据存入切片。
当Find
进行批量操作时,只能将切片的第一个值存入结构体,若传入的是切片,则可以将所有查询结果传入切片。
1
2
3
4
5
6
7
8
9
DB.Find(&student, []int{1, 2, 3})
fmt.Println(student)
DB.Find(&slice, "ID in ?", []int{1, 2, 3})
fmt.Println(slice)
//对应的SQL语句
SELECT * FROM `students` WHERE `students`.`id` IN (1,2,3);
//输出数据
{1 Outer1号 18}
[{1 Outer1号 20} {2 Outer2号 20} {3 Outer3号 20}]
3.更新
我们可以通过Save
操作来实现某个结构体数据的保存,将其绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
var student Student
DB.Take(&student, 11)
fmt.Println(student)
student.Age = 19
DB.Save(&student)
DB.Take(&student, 11)
fmt.Println(student)
//对应SQL语句
UPDATE `students` SET `name`='Outer11号',`age`=19 WHERE `id` = 11;
//返回值
{11 Outer10号 18}
{11 Outer10号 19}
但注意,Save
是根据主键来修改的,当存在主键时,他会将主键对应的数据进行修改,当主键不存在时则在表的最后创建新的行。
1
2
3
4
5
6
var student Student
student.Name = "Outer2号"
DB.Save(&student)
DB.Find(&student, 2)
//返回值
{103 Outer2号 0}
我们要选择某个特定字段来进行更改的话,可以使用Select
语句
1
DB.Select("Name").Save(&student)
上述语句便只能将student
的Name
字段进行更改。
此外,我们可以通过Update
语句来对Find
查询的数据进行更改
1
2
3
4
5
DB.Find(&student, 2).Update("age", 19)
//对应SQL语句
UPDATE `students` SET `age`=19 WHERE `students`.`id` = 2 AND `id` = 2;
//输出结果
{2 Outer2号 19}
与其对应的是Updates
,可以对数据进行批量的更改
1
2
3
4
var slice []Student
DB.Find(&slice, "ID in ?", []int{1, 2, 3, 4, 5, 6, 7, 8}).Updates(Student{Age: 20})
//对应SQL语句
UPDATE `students` SET `age`=20 WHERE ID in (1,2,3,4,5,6,7,8) AND `id` IN (1,2,3,4,5,6,7,8)
此处的结构体Student{Age: 20}
也可以通过映射map[string]interface{}{"Age": 20}
来代替。
但该映射一定要求是map[string]interface{}
或map[string]any
类型
Update
和Updates
的区别
1
2
3
4
5
6
7
8
9
10
11
12
//Update的源码
func (db *DB) Update(column string, value interface{}) (tx *DB) {
tx = db.getInstance()
tx.Statement.Dest = map[string]interface{}{column: value}
return tx.callbacks.Update().Execute(tx)
}
//Updates的源码
func (db *DB) Updates(values interface{}) (tx *DB) {
tx = db.getInstance()
tx.Statement.Dest = values
return tx.callbacks.Update().Execute(tx)
}
不难发现,Update
将一个映射拆成了两部分,因此用Update
传入值的时候格式为Update(string,any)
,且只能进行单个字段的更改。
而Updates
则是只能接受结构体或映射。
4.删除
我们可以通过Delete
语句进行删除操作
1
2
3
4
var student Student
DB.Delete(&student, 3)
//对应的SQL语句
DELETE FROM `students` WHERE `students`.`id` = 3;
同理,Delete
也可以传入切片来实现批量删除
1
2
3
4
var student Student
DB.Delete(&student, []int{1, 2, 3, 4, 5, 6, 7, 8})
//对应的SQL语句
DELETE FROM `students` WHERE `students`.`id` IN (1,2,3,4,5,6,7,8);