目录

Golang orm框架gorm学习

GORM

  • ORM(Object Relation Mapping),对象关系映射,实际上就是对数据库的操作进行封装,对上层开发人员屏蔽数据操作的细节,开发人员看到的就是一个个对象,大大简化了开发工作,提高了生产效率
  • O = Object对象(程序中的对象/实例 如:Struct结构体)
  • R = Relational关系(关系型数据库 如:mysql)
  • M = Mapping映射
    • 结构体 -> 数据表
    • 结构体实例 -> 数据行
    • 结构体字段 -> 表字段
  • ORM 优缺点
    • 优点 - 提高开发效率,不需要使用SQL语句。
    • 缺点 - 执行性能比较差、灵活性较差、弱化了SQL能力

GORM概览

  1. 全功能ORM(无线接近)
  2. 关联(Has One, Has Many,Belongs To,Many To Many,多态)
  3. 钩子(在创建/保存/更新/删除/查找之前或之后)
  4. 预加载
  5. 事务
  6. 复合主键
  7. SQL生成器
  8. 数据库自动迁移
  9. 自定义日志
  10. 可扩展性,可基于GORM回调编写插件
  11. 所有功能都被测试覆盖
  12. 开发者友好

安装

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{})