GORM
- ORM(Object Relation Mapping),对象关系映射,实际上就是对数据库的操作进行封装,对上层开发人员屏蔽数据操作的细节,开发人员看到的就是一个个对象,大大简化了开发工作,提高了生产效率
- O = Object对象(程序中的对象/实例 如:Struct结构体)
- R = Relational关系(关系型数据库 如:mysql)
- M = Mapping映射
- 结构体 -> 数据表
- 结构体实例 -> 数据行
- 结构体字段 -> 表字段
- ORM 优缺点
- 优点 - 提高开发效率,不需要使用SQL语句。
- 缺点 - 执行性能比较差、灵活性较差、弱化了SQL能力
GORM概览
- 全功能ORM(无线接近)
- 关联(Has One, Has Many,Belongs To,Many To Many,多态)
- 钩子(在创建/保存/更新/删除/查找之前或之后)
- 预加载
- 事务
- 复合主键
- SQL生成器
- 数据库自动迁移
- 自定义日志
- 可扩展性,可基于GORM回调编写插件
- 所有功能都被测试覆盖
- 开发者友好
安装
1
|
go get -u github.com/jinzhu/gorm
|
数据库连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
)
func main(){
//dsn格式 user:pass@tcp(ip:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local
dsn := "root:root@tcp(192.168.116.90:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db,err := gorm.Open("mysql",dsn)
if err != nil{
fmt.Printf("gorm open failed ,error: %v\n",err)
return
}
//设置最大连接数
db.DB().SetMaxOpenConns(100)
//设置最大空闲连接数
db.DB().SetMaxIdleConns(50)
defer db.Close()
}
|
数据模型定义
表名、列名如何对应结构体
在Gorm中,表名是结构体名的复数形式,列名是字段名的蛇形小写
即,如果有一个user表,那么如果你定义的结构体名为:User ,gorm会默认表名为users而不是user。
例如有数据库如下表结构定义
1
2
3
4
5
6
7
8
9
10
11
12
13
|
CREATE TABLE `areas` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`area_id` int(11) NOT NULL COMMENT '区县id',
`area_name` varchar(45) NOT NULL COMMENT '区县名',
`city_id` int(11) NOT NULL COMMENT '城市id',
`city_name` varchar(45) NOT NULL COMMENT '城市名称',
`province_id` int(11) NOT NULL COMMENT '省份id',
`province_name` varchar(45) NOT NULL COMMENT '省份名称',
`area_status` tinyint(3) NOT NULL DEFAULT '1' COMMENT '该条区域信息是否可用 : 1:可用 2:不可用',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_IN
|
那么他在gorm中对应的结构体定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
type Area struct {
Id int
AreaId int
AreaName string
CityId int
CityName string
ProvinceId int
ProvinceName string
AreaStatus int
CreatedAt time.Time
UpdatedAt time.Time
}
|
- 从上面实例可以看到结构体Area 对应的数据库表名为 areas
- 结构体的字段如 AreaId 对应的则是 数据表中字段area_id
如何全局禁用表名复数呢?
可以在创建数据库连接的时候设置如下参数:
1
2
|
// 全局禁用表名复数
db.SingularTable(true) //如果设置为true,结构体User的默认表名为user,使用TableName 设置的表名不会受到影响
|
这样的话,表名默认即为结构体的首字母小写形式。
CRUD使用
环境准备
创建一张User表来做CRUD,表结构如下:
1
2
3
4
5
6
7
8
9
10
|
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(30) NOT NULL DEFAULT '',
`age` int(3) NOT NULL DEFAULT '0',
`sex` tinyint(3) NOT NULL DEFAULT '0',
`phone` varchar(40) NOT NULL DEFAULT '',
`create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4;
|
首先初始化数据库连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
)
var db *gorm.DB
type User struct {
Id int
Name string
Age int
Sex byte
Phone string
}
func init() {
var err error
//dsn格式 user:pass@tcp(ip:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local
dsn := "root:root@tcp(192.168.116.90:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db,err = gorm.Open("mysql",dsn)
if err != nil{
panic(err)
}
//设置最大连接数
db.DB().SetMaxOpenConns(100)
//设置最大空闲连接数
db.DB().SetMaxIdleConns(50)
//设置全局表名禁用复数
db.SingularTable(true)
}
|
下面所有的操作都是在上面的初始化连接上执行的操作
插入
1
2
3
4
5
6
7
8
9
10
11
12
13
|
func(user *User) Insert(){
//这里使用Table()函数,如果没有指定全局表名禁用复数,或者是表名跟结构体名不一样的时候
//可以在自己的sql中指定表名。如下示例
db.Table("user").Create(user)
}
func main(){
var u =User{
Name: "xieys",
Age: 20,
}
u.Insert()
}
|
更新
- 注意,Model方法必须要和Update方法一起使用
- 使用效果相当于Model中设置更新的主键key(如果没有where指定,那么默认更新的key为id),Update中设置更新的值
- 如果Model中没有指定id值,且也没有指定where条件,那么将更新全表
示例1:
1
2
3
4
5
6
7
8
9
|
func main(){
var u =User{
Id: 10, //注意如果是更新,并且没有where条件的话,必须制定id值,否则会更新全表
Name: "xieys",
Age: 21,
}
db.Model(&u).Update(u)
}
相当于sql语句:update user set name="xieys",age="21" where id=10
|
示例2:
1
2
3
4
5
6
7
8
|
func main(){
var u =User{
Age: 24,
}
db.Model(&u).Where("id = 8").Update(u)
}
这里没有携带Id,所以在更新的时候需要加Where条件,否则更新全表
相当于sql语句:update user set age=24 where id=8
|
注意到上面Update中使用了一个Struct,也可以使用map对象。
需要注意的是:使用struct的时候,只会更新struct中这些非空的字段。对于string类型字段的"",int类型字段0,bool类型字段的false都会认为是空白纸,不会去更新表
示例3
1
2
3
4
5
6
7
8
9
|
func main(){
db.Model(&User{}).Where("age = ?",22).Update("name","xxx")
}
相当于sql语句:update user set name="xxx" where age = 22
func main(){
db.Model(&User{}).Where("name = ?","xxx").Update("age",0)
}
相当于sql语句:update user set age = 0 where name="xxx"
|
示例4
1
2
3
4
5
6
|
Select 指定只更新某个字段,没有指定的字段将被忽略
func main(){
db.Model(&u).Select("name").Update(map[string]interface{}{"name":"","age":0}) //这里age字段将被忽略,不会被更新,更新的只有name字段
// 如果想要更新name和age字段应该这样写
//db.Model(&u).Select("name","age").Update(map[string]interface{}{"name":"","age":0})
}
|
示例5
1
2
3
4
5
6
7
8
9
10
11
12
|
忽略掉某些字段
当你的更新的参数为结构体,而结构体中某些字段你又不想去更新,那么可以使用Omit方法过滤掉这些不想update到库的字段,忽略字段使用Select也可以实现,具体看示例4
func main(){
var u =User{
Id: 8,
Name: "xx"
Age: 24,
}
db.Model(&u).Omit("name").Update(&u) //这里name字段不会更新,只会更新age字段
//db.Model(&u).Select("name").Update(&u) //这里是name字段会更新,age字段被忽略
}
|
删除
单记录删除
1
2
3
4
5
|
func main(){
user := User{Id : 8}
db.Delete(&user)
}
// delete user where id = 8
|
批量删除
1
2
3
4
|
func main(){
db.Delete(&User{},"id > ?",8)
}
// delete user where id > 8
|
事务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
func CreateUsers()error{
tx := db.Begin()
// 注意,一旦在事务中,使用tx作为数据库句柄
if err := tx.Create(&User{Name: "XXX"}).Error;err != nil{
tx.Rollback() // 回滚
return err
}
if err := tx.Create(&User{Name: "yyy"}).Error;err != nil{
tx.Rollback()
return err
}
tx.Commit()
return nil
}
func main(){
CreateUsers()
}
|
查询
查询所有字段
查询所有记录
1
2
3
4
5
|
func main(){
var user []User
db.Find(&user)
fmt.Printf("%v\n",user)
}
|
带where条件的查询
1
2
3
4
5
6
|
func main(){
var user []User
// 带where子句的查询,注意where要再find的前面
db.Where("id > ?",11).Find(&user)
fmt.Printf("%v\n",user)
}
|
in 查询
1
2
3
4
5
6
|
func main(){
var user []User
// 带where子句的查询,注意where要再find的前面
db.Where("id in (?)",[]int{11,12}).Find(&user)
fmt.Printf("%v\n",user)
}
|
获取第一条记录,按照主键顺序排序
1
2
3
4
5
|
func main(){
var user []User
db.First(&user)
fmt.Printf("%v\n",user)
}
|
获取第一条记录,带where条件
1
2
3
4
5
|
func main(){
var user []User
db.First(&user,"id = ?",12)
fmt.Printf("%v\n",user)
}
|
获取最后一条记录
1
2
3
4
5
6
7
8
9
10
11
|
func main(){
var user []User
db.Last(&user)
fmt.Printf("%v\n",user)
}
同样也可以加where条件
func main(){
var user []User
db.Last(&user,"id = ?",11)
fmt.Printf("%v\n",user)
}
|
查询指定字段 - Select
示例
1
2
3
4
5
6
7
8
|
//指定查询字段
func main(){
var user []User
db.Select("name,age").Where(map[string]interface{}{"age":0,"sex":0}).Find(&user)
fmt.Printf("%v\n",user)
}
//相当于sql语句
select name,age from user where age = 0 and sex = 0
|
使用struct或map作为查询条件
示例 Struct
1
2
3
4
5
6
|
func main(){
var user []User
//使用struct,相当于: select * from user where age = 10 and sex = 0
db.Where(&User{Age: 10,Sex: 0}).Find(&user)
fmt.Printf("%v\n",user)
}
|
示例 map
1
2
3
4
5
6
|
func main(){
var user []User
db.Where(map[string]interface{}{"age":10,"sex":0}).Find(&user)
fmt.Printf("%v\n",user)
}
相当于: select * from user where age = 10 and sex = 0
|
not in 条件的使用
示例
1
2
3
4
5
6
|
func main(){
var user []User
db.Not("name",[]string{"xxx","aaa"}).Find(&user)
fmt.Printf("%v\n",user)
}
//相当于 : select * from user where name not in ("xxx","aaa")
|
or的使用
示例
1
2
3
4
5
6
|
func main(){
var user []User
db.Where("age > ?",10).Or("sex = ?",0).Find(&user)
fmt.Printf("%v\n",user)
}
相当于 : select * from user where age > 10 or sex = 0
|
order by的使用
1
2
3
4
5
6
|
func main(){
var user []User
db.Where("age > ?",10).Or("sex = ?",0).Order("id desc").Find(&user)
fmt.Printf("%v\n",user)
}
相当于 : select * from user where age > 10 or sex = 0 order by id desc
|
limit的使用
示例
1
2
3
4
5
6
|
func main(){
var user []User
db.Not("name",[]string{"aa","bb"}).Limit(1).Find(&user)
fmt.Printf("%v\n",user)
}
相当于 : select * from user where name not in ("aa","bb") limit 1
|
offset的使用
示例
1
2
3
4
5
6
|
func main(){
var user []User
db.Not("name",[]string{"aa","bb"}).Limit(1).Offset(1).Find(&user)
fmt.Printf("%v\n",user)
}
相当于 : select * from user where name not in ("aa","bb") limit 1,1 相当于从偏移量为1,从第二条开始取1条数据
|
count(*)
示例
1
2
3
4
5
6
|
func main(){
var count int
db.Table("user").Where("age = ?",0).Count(&count)
fmt.Println(count)
}
相当于 select count(*) from user where age = 0
|
group & having
示例
1
2
3
4
5
6
7
8
9
10
11
|
func main(){
//var user []User
rows,err := db.Table("user").Select("count(age),sex").Group("sex").Having("count(age)>0").Rows()
if err != nil{
panic(err)
}
for rows.Next(){
fmt.Println(rows.Columns())
}
}
相当于sql : select count(age),sex from user group by sex having count(age) > 0
|
join
示例
1
2
3
4
5
6
7
8
9
10
11
|
新建一个关联表
CREATE TABLE `user_ext` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `address` varchar(50) default '', `uid` bigint(20) ,PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4;
insert into user_ext(address,uid) values('xxxx',11);
func main(){
var res []Result
db.Table("user u").Select("u.name,u.age,ue.address").Joins("left join user_ext ue on u.id = ue.uid").Find(&res)
fmt.Println(res)
}
相当于sql : SELECT u.name,u.age,ue.address from user u left join user_ext ue on u.id = ue.uid;
|
如果有多个连接,可以用多个Joins方法
原生函数
示例
1
2
3
4
5
6
7
8
9
10
11
|
func main(){
db.Exec("UPDATE user SET name = ? where id IN (?)","xxxx",[]int{11,13})
}
func main(){
var users []User
db.Exec("select * from user where id > ?",10).Find(&users)
fmt.Println(users)
//db.Exec("DROP TABLE user;")
}
|
其他函数
FirstOrInit 和 FirstOrCreate
获取第一个匹配的记录,若没有,则根据条件初始化一个新的记录(并不会存到数据库里):
1
2
3
4
|
//注意:where条件只能使用Struct或者map。如果这条记录不存在,那么会新增一条name=xiaoming的记录
db.FirstOrInit(&u,User{Name:"xiaoming"})
//同上
db.FirstOrCreate(&u,User{Name:"xiaoming"})
|
Attrs
如果没有找到记录,则使用Attrs中的数据来初始化一条记录:
1
2
3
|
//使用attrs来初始化参数,如果未找到数据则使用attrs中的数据来初始化一条
//注意:attrs 必须 要和FirstOrInit 或者 FirstOrCreate 连用
db.Where(User{Name:"xiaoming"}).Attrs(User{Name:"xiaoming",Age:12}).FirstOrInit(&u)
|
Assign
1
2
|
//不管是否找的到,最终返回结构中都将带上Assign指定的参数
db.Where("age > 12").Assign(User{Name:"xiaoming"}).FirstOrInit(&u)
|
Pluck
如果user表中你只想查询age这一列,该怎么返回呢,gorm提供了Pluck函数用于查询单列,返回数组:
1
2
|
var ages []int
db.Find(&u).Pluck("age",&ages)
|
Scan
Scan函数可以将结果转存储到另一个结构体中。
1
2
3
4
5
6
|
type SubUser struct{
Name string
Age int
}
db.Table("user").Select("name,age").Scan(&SubUser)
|
sql.Row & sql.Rows
row和rows用户获取查询结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//查询一行
row := db.Table("user").Where("name = ?", "xiaoming").Select("name, age").Row() // (*sql.Row)
//获取一行的结果后,调用Scan方法来将返回结果赋值给对象或者结构体
row.Scan(&name, &age)
//查询多行
rows, err := db.Model(&User{}).Where("sex = ?",1).Select("name, age, phone").Rows() // (*sql.Rows, error)
defer rows.Close()
for rows.Next() {
...
rows.Scan(&name, &age, &email)
...
}
|
日志
Gorm有内置的日志记录器支持,默认情况下,它会打印发生的错误。
1
2
3
4
5
6
7
8
|
// 启用Logger,显示详细日志
db.LogMode(true)
// 禁用日志记录器,不显示任何日志
db.LogMode(false)
// 调试单个操作,显示此操作的详细日志
db.Debug().Where("name = ?", "xiaoming").First(&User{})
|