GORM
下载驱动
go get gorm.io/driver/mysql
go get gorm.io/gorm
连接数据库
var DB *gorm.DB
func init() {
username := "root" // 账号
password := "Qza040203@quantumgameing" //密码
host := "127.0.0.1" // 数据库地址
port := 3306 //端口号
Dbname := "qza" //数据库名
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), &gorm.Config{
SkipDefaultTransaction: true, //是否开启默认事物,默认为false,配置为true可取消系统默认事物,若没有需求禁用可提升60%性能
Logger: logger.Default.LogMode(logger.Info),
NamingStrategy: schema.NamingStrategy{
TablePrefix: "tb_",//表名前缀
SingularTable: true,//是否单数命名
NoLowerCase: true,//不进行小写转换
},
})
if err != nil {
panic("数据库连接失败,error=" + err.Error())
}
DB = db
}
模型定义
模型是标准的 struct,由 Go 的基本数据类型、实现了 Scanner 和 Valuer 接口的自定义类型及其指针或别名组成。在创建模型时,可以给字段设置 tag 来对该字段一些属性进行定义。比如我们把ID设置成主键。
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
}
type Student struct {
gorm.Model
Name string
Age int
}
标签名 | 说明 |
---|---|
column | 指定 db 列名 |
size | 指定列大小,例如:size:256 |
primaryKey | 指定列为主键 |
unique | 指定列为唯一 |
default | 指定列的默认值 |
type | 列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not null、size, autoIncrement… 像 varbinary(8) 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INSTREMENT |
primaryKey | 指定列为主键 |
unique | 指定列为唯一 |
default | 指定列的默认值 |
precision | 指定列的精度 |
scale | 指定列大小 |
not null | 指定列为 NOT NULL |
autoIncrement | 指定列为自动增长 |
embedded | 嵌套字段 |
embeddedPrefix | 嵌入字段的列名前缀 |
autoCreateTime | 创建时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano /milli 来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nano |
autoUpdateTime | 创建/更新时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano /milli 来追踪纳秒、毫秒时间戳,例如:autoUpdateTime:milli |
index | 根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看 索引 获取详情 |
uniqueIndex | 与 index 相同,但创建的是唯一索引 |
check | 创建检查约束,例如 check:age > 13 ,查看 约束 获取详情 |
<- | 设置字段写入的权限, <-:create 只创建、<-:update 只更新、<-:false 无写入权限、<- 创建和更新权限 |
-> | 设置字段读的权限,->:false 无读权限 |
- | 忽略该字段,- 无读写权限 |
自动创建表
在数据库的表尚未初始化时,gorm 可以根据指定的结构体自动建表。
通过 DB.AutoMigrate
方法根据Student结构体,自动创建 Students 表。如果表已存在,该方法不会有任何动作。
DB.AutoMigrate(&Student{})
建表的规则会把 Student 调整为复数,并自动添加 gorm.Model 中的几个字段。由于很多数据库是不区分大小写的,如果采用 camelCase 风格命名法,在迁移数据库时会遇到很多问题,所以数据库的字段命名风格都是采用 underscorecase 风格命名法,gorm 会自动帮我们转换。
使用 DB.Create
方法,传入结构体的指针创建。
student := Student{Age: 18, Name: "Alice"}
students := []Student{
{Name: "Bob",Age: 20},
{Name: "Jack",Age: 19},
}
DB.Create(&student)
DB.Create(students)
相当于
INSERT INTO
`students` ( `created_at`, `updated_at`, `deleted_at`, `name`, `age` )
VALUES
(
'2023-09-27 19:17:47.921',
'2023-09-27 19:17:47.921',
NULL,
'Alice',
18
),
(
'2023-09-27 19:17:47.921',
'2023-09-27 19:17:47.921',
NULL,
'Bob',
20
),
(
'2023-09-27 19:17:47.921',
'2023-09-27 19:17:47.921',
NULL,
'Jack',
19
)
gorm 会自动维护 created_at、updated_ad 和 deleted_at 三个字段。
通过 Select 选择指定字段。
使用 Omit 方法过滤一些字段。
student := Student{Age: 18, Name: "zhangsan"}
DB.Select("Name").Create(&student)
student = Student{Age: 18, Name: "lisi"}
DB.Omit("Age").Create(&student)
gorm 提供了 First、Take、Last 方法。它们都是通过 LIMIT 1
来实现的,分别是主键升序、不排序和主键降序。
student := Student{}
DB.First(&student)
// SELECT * FROM students ORDER BY id LIMIT 1;
DB.Take(&student)
// SELECT * FROM students LIMIT 1;
DB.Last(&student)
// SELECT * FROM students ORDER BY id DESC LIMIT 1;
fmt.Println(student)
在 First/Take/Last 等函数中设置第二个参数,该参数被认作是 ID。可以选择 int 或 string 类型。
DB.First(&student, 2)
如果没有查询到对象,会返回 ErrRecordNotFound 错误。
使用 Find 方法查询多个对象。
返回值会映射到 users 切片上。
依然可以通过访问返回值上的 Error 和 RowsAffected 字段获取异常和影响的行号。
students := []Student{}
res := DB.Find(&students)
fmt.Println(students)
fmt.Println(res.Error)
fmt.Println(res.RowsAffected)
设置查询条件 Where
gorm 提供了万能的 Where 方法,可以实现 =、<>、IN、LIKE、AND、>、<、BETWEEN 等方法,使用 ? 来占位。
DB.Where("Name = ?", "Alice").First(&students)
//SELECT * FROM students WHERE name = "Alice" ORDER BY id LIMIT 1;
fmt.Println(students)
DB.Where("Name <> ?", "Alice").Find(&students)
//SELECT * FROM students WHERE name <> "Alice";
fmt.Println(students)
DB.Where("Age in ?", []int{18, 20}).Find(&students)
//SELECT * FROM students WHERE age name IN (18,20);
fmt.Println(students)
DB.Where("Name like ?", "%l%").Find(&students)
// SELECT * FROM students WHERE name LIKE '%l%';
fmt.Println(students)
DB.Where("Name = ? AND Age = ?", "Alice", 18).Find(&students)
// SELECT * FROM students WHERE name = 'Alice' AND age = 18;
fmt.Println(students)
DB.Where("Age between ? and ?", 18, 20).Find(&students)
// SELECT * FROM students WHERE age BETWEEN 18 AND 20;
fmt.Println(students)
传递 Struct、Map 和 切片时,可以实现更简便的设置条件。
DB.Where(&Student{Name: "Alice"}).Find(&students)
DB.Where(map[string]interface{}{"Name":"Ailce"}).Find(students)
结构体和 Map 的效果几乎是相等的,两者唯一的不同之处在于 struct 中的零值字段不会查询。比如 0、“”、false。切片是查询主键。所有的查询,gorm 都会默认设置 tabel.deleted_at IS NULL
查询条件。
DB.Where([]int{1,3}).Find(&students)
SELECT
*
FROM
`students`
WHERE
`id` IN ( 10, 11 )
AND `deleted_at` IS NULL
选取特定字段 Select
DB.Select("ID").Where(&Student{Age: 18}).Find(&students)
SELECT
`ID`
FROM
`students`
WHERE
.`name` = 'lzq'
AND `deleted_at` IS NULL
类似于select,我们还可以链式操作排序 Order、分页 Limit Offset。
DB.Order("Age DESC").Find(&students)
DB.Limit(3).Offset(2).Find(&students)
使用 Save 方法更新所有字段,即使是零值也会更新。
student := Student{}
DB.Where("Name = ?", "Alice").First(&student)
student.Name = "Alice1"
DB.Save(&student)
UPDATE `students`
SET `created_at` = '2023-09-27 20:15:59.062',
`updated_at` = '2023-09-29 14:28:59.719',
`deleted_at` = NULL,
`user_name` = 'Alice1',
`age` = 18
WHERE
`Name` = "Alice"
使用 Model 和 Update 方法更新单列。
可以使用结构体作为选取条件,如仅选择 ID。
student.ID = 2
DB.Model(&student).Update("Name","Bob1")
也可以在 Model 中设置空结构体,使用 Where 方法自己选取条件。
DB.Model(&Student{}).Where("ID",2).Update("Name", "Bob1")
UPDATE `students`
SET `name` = 'Bob1',
`updated_at` = '2023-09-29 14:32:56.165'
WHERE
`id` = '2'
使用 Updates 方法进行更新多列。支持 struct 和 map 更新。当更新条件是 struct 时,零值不会更新,如果确保某列必定更新,使用 Select 选择该列。
DB.Model(&Student{}).Where("ID", 2).Updates(map[string]interface{}{
"Name": "Jack1",
"Age": 20,
})
使用 Delete 方法删除单条数据。但需要指定 ID,不然会批量删除
student := Student{}
student.ID = 5
DB.Delete(&student)
UPDATE `students`
SET `deleted_at` = '2023-09-29 14:49:43.106'
WHERE
`ID` = 5
AND `users`.`deleted_at` IS NULL
根据主键删除,也可以使用切片 []int、[]string 进行根据 ID 批量删除。
DB.Delete(&Student{},1)
DB.Delete(&Student{},[]int{2,3})
如果结构体包含 gorm.DeletedAt 字段,会自动获取软删除的能力,在调用所有的 Delete 方法时,会自动变为 update 语句,在查询时会自动忽略软删除的数据。
使用 Unscoped 方法查找被软删除的数据。
DB.Unscoped().Where("ID", 5).Find(&student)
fmt.Println(student)
使用 Unscoped 方法永久删除数据。
DB.Unscoped().Delete(&Student{},5)
除了上面的封装方法外,gorm 还提供了执行原生 SQL 的能力。执行 SQL 并将结果映射到变量上,使用 Raw 方法配合 Scan 方法。可以查询单条数据扫描并映射到结构体或 map 上。如果返回结果和传入的映射变量类型不匹配,那么变量的值不会有变化。
DB.Raw("select * from students where id = ?", 1).Scan(&student)
fmt.Println(student)
gorm 提供了 钩子Hook 功能。可以在创建、查询、更新和删除之前和之后自动执行某些逻辑。
假设现在需要添加一个 RecordID,并且在每次创建时生成一个 16 位的 uuid,还希望在存储之前打印生成的 uuid,在存储之后打印创建后的 id。实现方式就是给模型结构体 User 重写 BeforeCreate 和 AfterCreate 两个方法。
func (stu *Student) BeforeCreate(tx *gorm.DB) error {
stu.RecordID = uuid.New().String()
fmt.Println("开始创建,UUID =",stu.RecordID)
return nil
}
func (stu *Student)AfterCreate(tx *gorm.DB) error {
fmt.Println("创建结束,UUID =",stu.RecordID)
return nil
}
更新的 Hook 是 BeforeUpdate、AfterUpdate 和 BeforeSave、AfterSave,用法与创建一致。
查询的 Hook 是 AfterFind,用法与创建一致。
删除的 Hook 是 BeforeDelete 和 AfterDelete,用法与创建一致。
除了查询的 Hook 外,其他 Hook 都是在事务上运行的,一旦在函数中 return error 时,就会触发事务回滚。