# GORM基本操作

概览

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

# 1 安装

go get -u github.com/jinzhu/gorm
1

# 2 数据库连接

更多GORM数据库连接说明

package main

import (
	"fmt"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

var (
	// 定义一个全局对象db
	db *gorm.DB
	//定义数据库连接的相关参数值
	//连接数据库的用户名
	userName string = "root"
	//连接数据库的密码
	password string = "admin"
	//连接数据库的地址
	ipAddress string = "127.0.0.1"
	//连接数据库的端口号
	port int = 3306
	//连接数据库的具体数据库名称
	dbName string = "go_test"
	//连接数据库的编码格式
	charset string = "utf8mb4"
	//想要能正确的处理 time.Time,你需要添加 parseTime 参数。
	parseTime string = "True"
	//设置时间的位置
	loc string = "Local"
)

func connectMysql() (err error) {
	//dsn="root:admin@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4&parseTime=True&loc=Local"
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=%s&loc=%s", userName, password, ipAddress, port, dbName, charset, parseTime, loc)
	db, err = gorm.Open("mysql", dsn)
	if err != nil {
		fmt.Println("连接数据库异常,err", err)
		return err
	}
	//设置最大的空闲连接数
	db.DB().SetMaxIdleConns(10)
	// 设置最大连接数
	db.DB().SetMaxOpenConns(100)
	//自动生成数据库表 ,生成的表明默认是struct名称的复数形式。如:user -》users
	// 如果不想是复数形式,需设定:
	db.SingularTable(true)
	//defer db.Close()
	fmt.Println("连接数据库成功!")
	return nil
}

func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
}
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

# 3 GORM模型定义

# 3.1 模型概念说明

模型概念

ORM框架操作数据库都需要预先定义模型,模型可以理解成数据模型,作为操作数据库的媒介。

  • 从数据库读取的数据会先保存到预先定义的模型对象,然后我们就可以从模型对象得到我们想要的数据。
  • 插入数据到数据库也是先新建一个模型对象,然后把想要保存的数据先保存到模型对象,然后把模型对象保存到数据库。

golanggorm模型定义是通过struct实现的,这样我们就可以通过gorm库实现struct类型和mysql表数据的映射。 gorm负责将对模型的读写操作翻译成sql语句,然后gorm再把数据库执行sql语句后返回的结果转化为我们定义的模型对象。

# 3.2 GORM模型定义

gorm模型定义主要就是在struct类型定义的基础上增加字段标签说明实现,下面看个完整的例子。假如有个用户表,表结构如下

CREATE TABLE `user` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `name` varchar(255) NOT NULL,
   `age` int(11) DEFAULT NULL,
   `create_time` datetime NOT NULL,
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
1
2
3
4
5
6
7

模型定义如下

type User struct {
	Id  int   `gorm:"primary_key"` //表字段名为:id,主键
	Name string   `gorm:"not null"` //模型标签 name字段不能为空
	Age  int     //年龄age字段
    //对应表中的create_time字段并且不为空
	CreateTime  time.Time	`gorm:"column:create_time;not null"`
}
1
2
3
4
5
6
7

# 3.3 GORM模型标签

通过上面的例子,大家看到可以通过类似gorm:"column:create_time"这样的标签定义语法,定义struct字段的列名(表字段名)。

//标签定义部分,多个标签定义可以使用分号(;)分隔,
//比如`gorm:"column:create_time;not null"`
`gorm`标签语法:`gorm:"标签定义"`
1
2
3

常用标签如下,更多标签请参考官网模型定义

标签 说明 例子
COLUMN 指定列名 gorm:"column:create_time"
PRIMARY_KEY 指定主键 gorm:"column:id; PRIMARY_KEY"
NOT NULL 将列指定为非 NULL gorm:"not null"
- 忽略字段 gorm:"-" 可以忽略struct字段,被忽略的字段不参与gorm的读写操作

# 4 根据GORM模型自动创建表

//创建一个和数据库表结构一样的机构体
//gorm 用 tag 的方式来标识 mysql 里面的约束
type User struct {
	Id  int   `gorm:"primary_key"`
	Name string   `gorm:"not null"`
	Age  int
	CreateTime  time.Time	`gorm:"column:create_time;not null"`
}
func main() {
       //连接数据库方法请查看上面第二点
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//如果数据库中没有user表就创建
	if !db.HasTable(&User{}) {
//直接通过 db.CreateTable 就可以创建表了,非常方便,
//还可以通过 db.Set 设置一些额外的表属性,
		if err := db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8").CreateTable(&User{}).Error; err != nil {
			panic(err)
		}
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 5 CRUD使用准备工作

下面的CRUD公用的代码是数据库连接和结构体声明,代码如下

import (
	"fmt"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	"time"
)
var (
	// 定义一个全局对象db
	db *gorm.DB
	//定义数据库连接的相关参数值
	//连接数据库的用户名
	userName string = "root"
	//连接数据库的密码
	password string = "admin"
	//连接数据库的地址
	ipAddress string = "127.0.0.1"
	//连接数据库的端口号
	port int = 3306
	//连接数据库的具体数据库名称
	dbName string = "go_test"
	//连接数据库的编码格式
	charset string = "utf8mb4"
	//想要能正确的处理 time.Time,你需要添加 parseTime 参数。
	parseTime string = "True"
	//设置时间的位置
	loc string = "Local"
)

func connectMysql() (err error) {
	//dsn="root:admin@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4&parseTime=True&loc=Local"
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=%s&loc=%s", userName, password, ipAddress, port, dbName, charset, parseTime, loc)
	db, err = gorm.Open("mysql", dsn)
	if err != nil {
		fmt.Println("连接数据库异常,err", err)
		return err
	}
	//设置最大的空闲连接数
	db.DB().SetMaxIdleConns(10)
	// 设置最大连接数
	db.DB().SetMaxOpenConns(100)
	//自动生成数据库表 ,生成的表明默认是struct名称的复数形式。如:user -》users
	// 如果不想是复数形式,需设定:
	db.SingularTable(true)
	//defer db.Close()
	fmt.Println("连接数据库成功!")
	return nil
}

//创建一个和数据库表结构一样的机构体
//gorm 用 tag 的方式来标识 mysql 里面的约束
type User struct {
	Id         int    `gorm:"primary_key"`
	Name       string `gorm:"not null"`
	Age        int
	CreateTime time.Time `gorm:"column:create_time;not null"`
}
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

# 6 插入数据

更多用法请参考官网插入数据

//插入数据
func (user *User) InsertUser() (err error) {
	//这里使用了Table()函数,如果你没有指定全局表名禁用复数,或者是表名跟结构体名不一样的时候
	//你可以自己在sql中指定表名。这里是示例,本例中这个函数可以去除。db.Create(user)也可以
	createDb := db.Table("user").Create(user)
	err = createDb.Error
	if err != nil {
		fmt.Println("新增数据错误,err", err)
		return err
	}
	return nil
}
//主函数调用
func main() {
    //连接数据库
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
    //定义需要新增的user结构体
	user := User{Name: "李四", Age: 16, CreateTime: time.Now()}
    //调用user表的插入操作
	err = user.InsertUser()
	if err != nil {
		fmt.Println("新增数据错误,err", err)
	}
	//新增结束后会返回对应结构的所有数据,比如id
	fmt.Println("新增数据成功,新增user机构体主键是", user.Id)
}
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

# 7 更新数据

更多用法请参考官网更新数据

# 7.1 更新修改字段

如果你只希望更新指定字段,可以使用Update或者Updates

//更新数据user表
func (user *User) UpdateUser()(err error) {
//注意,Model方法必须要和Update方法一起使用 注意:db.Update(user)这样更新数据不生效
//使用效果相当于Model中设置更新的主键key(如果没有where指定,那么默认更新的key为id),Update中设置更新的值
//如果Model中没有指定id值,且也没有指定where条件,那么将更新全表
	update :=db.Model(&user).Update(user)
	err = update.Error
	if err != nil {
		fmt.Println("更新数据错误,err", err)
		return err
	}
	return nil
}
func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	user := User{Id: 1, Name: "赵六",Age:28}
	err = user.UpdateUser()
	if err != nil {
		fmt.Println("更新数据错误,err", err)
	}
	fmt.Println("更新后的数据", user)
}
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

其余更新操作再举例

func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//这个更新操作只使用了where条件没有在Model中指定id
	//等价于 update user set name='hello' wehre age=10
	db.Model(&User{}).Where("AGE = ?", 10).Update("name", "hello")
	//多个条件写法 等价于 update user set name='hello' wehre age=10 and name='helloWorld'
	db.Model(&User{}).Where("AGE = ?", 18).Where("name=?","helloWorld").Update("name", "hello")
	//等价于 update user set name='helloWorld' , age=18  使用 map 更新多个属性,只会更新其中有变化的属性
	db.Model(&User{}).Updates(map[string]interface{}{"name": "helloWorld", "age": 18})
1
2
3
4
5
6
7
8
9
10
11
12
13

# 7.2 更新所有字段

db.Save(&user)会更新所有字段,即使你没有赋值。一般建议使用db.Model(&user).Update(user)

func (user *User) SaveUser()(err error) {
	//Save会更新所有字段,即使你没有赋值
	// 一般建议使用db.Model(&user).Update(user)
	update :=db.Save(&user)
	err = update.Error
	if err != nil {
		fmt.Println("更新数据错误,err", err)
		return err
	}
	return nil
}
func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	user := User{Id: 1, Name: "蜡笔小新"}
	//等价于 UPDATE user SET name='蜡笔小新', age=0, create_time='0000-00-00 00:00:00' WHERE id=1;
	user.SaveUser()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 7.3 更新选定字段

当你的更新的参数为结构体,而结构体中某些字段你又不想去更新,那么可以使用Omit方法过滤掉这些不想update到库的字段。 当你的更新的参数为结构体,你只想更新某些字段可以使用Select。举例如下:

func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//定义需要更新的结构体
	user := User{Id: 1, Name: "蜡笔小新",Age:222,CreateTime:time.Now()}
	//更新时不对name字段进行修改
    //等价于 UPDATE user SET  age=12 ,create_time= NOW() WHERE id=1;
	db.Model(&user).Omit("name").Update(&user)
	//只对name字段进行修改
    //等价于 UPDATE user SET name='蜡笔小新' WHERE id=1;
	db.Model(&user).Select("name").Update(&user)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 7.4 无 Hooks 更新

上面的更新操作会自动运行modelBeforeUpdate,AfterUpdate方法,更新UpdatedAt时间戳, 在更新时保存其Associations,如果你不想调用这些方法,你可以使用UpdateColumn,UpdateColumns。举例如下

func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//定义需要更新的结构体
	user := User{Id: 1}
	// 更新单个属性,类似于 `Update` 
    //等价sql  UPDATE user SET name='hello' WHERE id = 1;
	db.Model(&user).UpdateColumn("name", "hello")
	// 更新多个属性,类似于 `Updates`  
    //等价sql  UPDATE user SET name='hello', age=18 WHERE id = 1;
	db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 7.5 批量更新

批量更新时 Hooks 不会运行

func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}

	//批量更新 等价于 UPDATE users SET name='hello', age=9000 WHERE id IN (1, 2);
	db.Table("user").Where("id IN (?)", []int{1, 2}).Updates(map[string]interface{}{"name": "hello", "age": 9000})

	// 使用 struct 更新时,只会更新非零值字段,若想更新所有字段,请使用map[string]interface{}
	// UPDATE users SET name='hello', age=18;
	db.Model(User{}).Updates(User{Name: "hello", Age: 18})

	// 使用 `RowsAffected` 获取更新记录总数
	updateTotal := db.Model(User{}).Updates(User{Name: "hello", Age: 18}).RowsAffected
	fmt.Println("更新的记录数量是",updateTotal)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 7.6 使用SQL表达式更新

gorm提供了Expr函数用于设置表达式

func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//定义需要更新的结构体
	user := User{Id: 1}
	//先计算年龄的值在执行更新操作
	//UPDATE user SET age = age * '2' + '100' WHERE id = 1;
	db.Model(&user).Update("age", gorm.Expr("age * ? + ?", 2, 100))

	//UPDATE user SET age = age * '2' + '100' WHERE id = 1;
	db.Model(&user).Updates(map[string]interface{}{"age": gorm.Expr("age * ? + ?", 2, 100)})

	//UPDATE user SET age = age - 10 WHERE id = 1;
	db.Model(&user).UpdateColumn("age", gorm.Expr("age - ?", 10))

	//UPDATE user SET age = age - 10 WHERE id = 1 and age>12;
	db.Model(&user).Where("age > 12").UpdateColumn("age", gorm.Expr("age - ?", 10))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 8 删除数据

# 8.1 删除记录

警告

删除记录时,请确保主键字段有值,GORM会通过主键去删除记录,如果主键为空,GORM会删除该model的所有记录。

func (user *User) DeleteUser() (err error) {
	//Save会更新所有字段,即使你没有赋值
	// 一般建议使用db.Model(&user).Update(user)
	deleteUser := db.Delete(&user)
	err = deleteUser.Error
	if err != nil {
		fmt.Println("删除数据错误,err", err)
		return err
	}
	return nil
}
func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//定义需要删除的结构体,主键字段必须有值
	user := User{Id: 1}
	//delete from user where id=1;
	user.DeleteUser()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 8.2 批量删除

删除全部匹配的记录

func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}

	// DELETE from user where name LIKE "%福小林%";
	db.Where("name LIKE ?", "%福小林%").Delete(User{})

	//DELETE from user where name LIKE "%福小林%";
	db.Delete(User{}, "name LIKE ?", "%福小林%")
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 8.3 物理删除

func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//定义需要删除的结构体,主键字段必须有值
	user := User{Id: 1}
	// Unscoped 方法可以物理删除记录
	//DELETE FROM user WHERE id=1;
	db.Unscoped().Delete(&user)
}
1
2
3
4
5
6
7
8
9
10
11
12

# 9 查询数据

gorm查询说明

  • gorm查询数据本质上就是提供一组函数,帮我们快速拼接sql语句,尽量减少编写sql语句的工作量。
  • gorm查询结果我们一般都是保存到结构体(struct)变量,所以在执行查询操作之前需要根据自己想要查询的数据定义结构体类型。
  • gorm库是协程安全的,gorm提供的函数可以并发的在多个协程安全的执行。

# 9.1 查询方法

更多用法请参考官网查询方法,示例如下

操作含义 golang示例 等价sql
根据主键查询第一条记录 db.First(&user) SELECT * FROM user ORDER BY id LIMIT 1;
随机获取一条记录 db.Take(&user) SELECT * FROM user LIMIT 1;
根据主键查询最后一条记录 db.Last(&user) SELECT * FROM user ORDER BY id DESC LIMIT 1;
查询所有的记录 db.Find(&users) SELECT * FROM user
查询指定的某条记录,仅当主键为整型时可用 db.First(&user, 10) SELECT * FROM user WHERE id = 10;
func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//定义接收查询结果的结构体变量
	user := User{}

	//定义接收查询所有表结果的结构体切片
	var users []User
	// 根据主键查询第一条记录  SELECT * FROM user ORDER BY id LIMIT 1;
	db.First(&user)

	// 随机获取一条记录 SELECT * FROM user LIMIT 1;
	db.Take(&user)

	// 根据主键查询最后一条记录 SELECT * FROM user ORDER BY id DESC LIMIT 1;
	db.Last(&user)

	// 查询所有的记录 SELECT * FROM user
	db.Find(&users)

	// 查询指定的某条记录(仅当主键为整型时可用)
	// SELECT * FROM user WHERE id = 3;
	db.First(&user, 3)

	fmt.Println("查询的单条数据为",user)
	db.Find(&users, )
	for _, v := range users {
		fmt.Println(v)
	}
}
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

# 9.2 查询错误处理

通过db.Error属性判断查询结果是否出错, Error属性不等于nil表示有错误发生。如下

if err := db.Take(&user).Error; err != nil {
    fmt.Println("查询失败", err)
}
1
2
3

# 9.2.1 错误特例

查询不到数据,gorm也会当成错误处理,如果查询不到数据,上面的例子会打印查询失败record not found 在实际开发中查询不到数据,我们不一定会当成错误处理,gorm库提供了下面两种办法检测Error是不是查询不到数据

# 9.2.2 gorm.IsRecordNotFoundError

err := db.Take(&user).Error
if gorm.IsRecordNotFoundError(err) {
    fmt.Println("查询不到数据")
} else if err != nil {
//如果err不等于record not found错误,又不等于nil,那说明sql执行失败了。
	fmt.Println("查询失败", err)
}
1
2
3
4
5
6
7

# 9.2.3 db.RecordNotFound

//链式操作,先查询,然后检测查询结果
if db.Take(&user).RecordNotFound {
    fmt.Println("查询不到数据")
} else if err != nil {
 //如果err不等于record not found错误,又不等于nil,那说明sql执行失败了。
 	fmt.Println("查询失败", err)
 }
1
2
3
4
5
6
7

# 9.3 Where条件查询

上面的例子都没有指定where条件,这里介绍下如何设置where条件,主要通过db.Where函数设置条件。函数说明如下:

 db.Where(query interface{}, args ...interface{})
1

参数说明:

参数名 说明
query sql语句的where子句, where子句中使用问号(?)代替参数值,则表示通过args参数绑定参数
args where子句绑定的参数,可以绑定多个参数
func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//定义接收查询结果的结构体变量
	user := User{}
	//定义接收查询所有表结果的结构体切片
	var users []User

	// 获取第一条匹配的数据
	// SELECT * FROM user WHERE name = 'hello' limit 1;
	db.Where("name = ?", "hello").First(&user)

	// 获取所有匹配的数据,接受者是一个对应类型的切片[]User
	// SELECT * FROM user WHERE name = 'hello';
	db.Where("name = ?", "hello").Find(&users)

	// SELECT * FROM user WHERE name <> 'hello';
	db.Where("name <> ?", "hello").Find(&users)

	// 拼接IN条件sql语句
	// SELECT * FROM user WHERE name in ('hello','helloWorld');
	db.Where("name IN (?)", []string{"hello", "helloWorld"}).Find(&users)

	// 拼接LIKE模糊查询
	// SELECT * FROM users WHERE name LIKE '%hel%';
	db.Where("name LIKE ?", "%hel%").Find(&users)

	// AND条件拼接
	// SELECT * FROM user WHERE name = 'hello' AND age >= 22;
	db.Where("name = ? AND age >= ?", "hello", "22").Find(&users)

	// Time条件
	// SELECT * FROM users WHERE create_time > '2020-01-01 00:00:00';
	db.Where("create_time < ?", time.Now()).Find(&users)

	// BETWEEN
	// SELECT * FROM user WHERE age BETWEEN 10 AND 20;
	db.Where("age BETWEEN ? AND ?", 10, 20).Find(&users)

    // Struct结构体 SELECT * FROM user WHERE name = "xiaoxin" AND age = 20 LIMIT 1;
    db.Where(&User{Name: "xiaoxin", Age: 20}).First(&user)

    // Map类型 SELECT * FROM user WHERE name = "jinzhu" AND age = 20;
    db.Where(map[string]interface{}{"name": "xiaoxin", "age": 20}).Find(&users)

    // 主键的切片 SELECT * FROM user WHERE id IN (20, 21, 22);
    db.Where([]int64{20, 21, 22}).Find(&users)
}
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

提示:当通过结构体进行查询时,GORM将会只通过非零值字段查询,这意味着如果你的字段值为0,'',false或者其他零值时,将不会被用于构建查询条件.例如

// SELECT * FROM users WHERE name = "xiaoxin";
db.Where(&User{Name: "xiaoxin", Age: 0}).Find(&users)
1
2

你可以使用指针或实现Scanner/Valuer 接口来避免这个问题

// 使用指针
type User struct {
    Id         int    `gorm:"primary_key"`
	Name       string `gorm:"not null"`
	Age        *int
	CreateTime time.Time `gorm:"column:create_time;not null"`
}

// 使用 Scanner/Valuer
type User struct {
    Id         int    `gorm:"primary_key"`
  	Name       string `gorm:"not null"`
  	Age        sql.NullInt64  // sql.NullInt64 实现了 Scanner/Valuer 接口
  	CreateTime time.Time `gorm:"column:create_time;not null"`
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 9.4 Not条件

作用与Where类似,举例如下

func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//定义接收查询结果的结构体变量
	user := User{}
	//定义接收查询所有表结果的结构体切片
	var users []User

	// SELECT * FROM user WHERE name <> "xiaoxin" LIMIT 1;
	db.Not("name", "xiaoxin").First(&user)

	// Not In 等价sql:SELECT * FROM user WHERE name NOT IN ("hello", "hello2");
	db.Not("name", []string{"hello", "hello2"}).Find(&users)

	// Not In 在切片中的应用。主键的切片
	// SELECT * FROM user WHERE id NOT IN (1,2,3)  LIMIT 1;
	db.Not([]int64{1,2,3}).First(&user)

	// SELECT * FROM user  LIMIT 1;
	db.Not([]int64{}).First(&user)

	// SELECT * FROM user WHERE NOT(name = "张三")  LIMIT 1;
	db.Not("name = ?", "张三").First(&user)

	// Struct结构体  SELECT * FROM user WHERE name <> "张三"  LIMIT 1;
	db.Not(User{Name: "张三"}).First(&user)
	
	fmt.Println(user)
	fmt.Println(users)
}
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

# 9.5 Or条件

func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//定义接收查询所有表结果的结构体切片
	var users []User

	// SELECT * FROM user WHERE name = '张三' OR name = '李四';
	db.Where("name = ?", "张三").Or("name = ?", "李四").Find(&users)

	// Struct结构体  // SELECT * FROM user WHERE name = '张三' OR name = '李四';
	db.Where("name = '张三'").Or(User{Name: "李四"}).Find(&users)

	// Map类型 SELECT * FROM user WHERE name = '张三' OR name = '李四';
	db.Where("name = '张三'").Or(map[string]interface{}{"name": "李四"}).Find(&users)

	fmt.Println(users)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 9.6 select子句

Select,指定你想从数据库中检索出的字段,默认会选择全部字段。举例如下

func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//定义接收查询所有表结果的结构体切片
	var users []User

	//查询指定字段,其他字段为每个类型的零值
	// SELECT name, age FROM user;
	db.Select("name, age").Find(&users)

	//查询指定字段,并添加where条件
	//  SELECT name, age FROM user WHERE age=12;
	db.Select("name, age").Find(&users,"age=?",12)

	// SELECT name, create_time FROM user;
	db.Select([]string{"name", "create_time"}).Find(&users)

	// 指定表名和指定表字段查询 SELECT COALESCE(age,'42') FROM user;
	db.Table("user").Select("COALESCE(age,?)", 42).Rows()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 9.7 order排序

设置排序语句,order by子句。指定从数据库中检索出记录的顺序。设置第二个参数reordertrue,可以覆盖前面定义的排序条件。

func (s *DB) Order(value interface{}, reorder ...bool) *DB {
	return s.clone().search.Order(value, reorder...).db
}
1
2
3
func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//定义接收查询结果的结构体变量
	//定义接收查询所有表结果的结构体切片
	var users []User
	var users1 []User
	var users2 []User

	// SELECT * FROM user ORDER BY age desc;
	db.Order("age desc").Find(&users)

	// 多字段排序 SELECT * FROM user ORDER BY age desc, name;
	db.Order("age desc").Order("name").Find(&users)

	// 覆盖排序
	//// SELECT * FROM user ORDER BY age desc; (users1)
	//// SELECT * FROM user ORDER BY age; (users2)
	db.Order("age desc").Find(&users1).Order("age", true).Find(&users2)
	//[{3 王五 78 2020-02-05 16:37:58 +0800 CST} {4 赵六 56 2020-02-05 16:37:58 +0800 CST} {2 李四 24 2020-02-05 16:37:58 +0800 CST} {1 张三 12 2020-02-05 16:37:58 +0800 CST}]
	fmt.Println(users1)
	//[{1 张三 12 2020-02-05 16:37:58 +0800 CST} {2 李四 24 2020-02-05 16:37:58 +0800 CST} {4 赵六 56 2020-02-05 16:37:58 +0800 CST} {3 王五 78 2020-02-05 16:37:58 +0800 CST}]
	fmt.Println(users2)
}
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

# 9.8 Limit & Count

Limit指定从数据库检索出的最大记录数。Countmodel能获取的记录总数。

func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//定义接收查询所有表结果的结构体切片
	var users []User
	var users1 []User
	var users2 []User
	var count int 

	// SELECT * FROM user LIMIT 3;
	db.Limit(3).Find(&users)

	// -1 取消 Limit 条件
	// SELECT * FROM user LIMIT 10; (users1)
	// SELECT * FROM user; (users2)
	db.Limit(10).Find(&users1).Limit(-1).Find(&users2)

	// SELECT * from user WHERE name = '张三' OR name = '李四'; (users)
	// SELECT count(*) FROM user WHERE name = '张三' OR name = '李四'; (count)
	db.Where("name = ?", "张三").Or("name = ?", "李四").Find(&users).Count(&count)

	// SELECT count(*) FROM user WHERE name = '张三'; (count)
	db.Model(&User{}).Where("name = ?", "张三").Count(&count)

	//指定表名 SELECT count(*) FROM user;
	db.Table("user").Count(&count)

	// 统计有条件 SELECT count( distinct(name) ) FROM user; (count)
	db.Table("user").Select("count(distinct(name))").Count(&count)
}
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

# 9.9 Group & Having

注意点

  • Group函数必须搭配Select函数一起使用
  • 下面例子scan类似Find都是用于执行查询语句,然后把查询结果赋值给结构体变量,区别在于scan不会从传递进来的结构体变量提取表名.
  • 下面例子重新定义了一个结构体用于保存结果,但是这个结构体并没有绑定user表,所以这里只能使用scan查询函数。
func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	//定一个Result结构体类型,用来保存查询结果
	type Result struct {
		Date  time.Time
		Total int
	}
	// SELECT date(create_time) as date, sum(age) as total FROM user GROUP BY date(create_time)
	rows, err := db.Table("user").Select("date(create_time) as date, sum(age) as total").Group("date(create_time)").Rows()
	for rows.Next() {
		var result Result
		err := rows.Scan(&result.Date,&result.Total)
		if err != nil{
			fmt.Println("scan失败,err",err)
			return
		}
		fmt.Println(result)
		//{2020-02-01 00:00:00 +0800 CST 24}
		//{2020-02-05 00:00:00 +0800 CST 146}
	}

	//SELECT date(create_time) as date, sum(age) as total FROM user GROUP BY date(create_time) HAVING (sum(age)>100)
	rows, err = db.Table("user").Select("date(create_time) as date, sum(age) as total").Group("date(create_time)").Having("sum(age) > ?", 100).Rows()
	for rows.Next() {
		//……取值操作请参考14--24行
	}
	// 下面两种方式类似
	//scan类似Find都是用于执行查询语句,然后把查询结果赋值给结构体变量,区别在于scan不会从传递进来的结构体变量提取表名.
	//这里因为我们重新定义了一个结构体用于保存结果,但是这个结构体并没有绑定user表,所以这里只能使用scan查询函数。
	var results []Result
	//SELECT date(create_time) as date, sum(age) as total FROM user GROUP BY date(create_time) HAVING (sum(age)>100)
	db.Model(User{}).Select("date(create_time) as date, sum(age) as total").Group("date(create_time)").Having("sum(age) > ?",100).Scan(&results)
	fmt.Println(results) //[{2020-02-01 00:00:00 +0800 CST 24}]

	db.Table("user").Select("date(create_time) as date, sum(age) as total").Group("date(create_time)").Having("sum(age) > ?", 100).Scan(&results)
	fmt.Println(results) //[{2020-02-01 00:00:00 +0800 CST 24}]
}
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
35
36
37
38
39
40
41

# 9.10 连接join

Joins,指定连接条件

func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	type Result struct {
		Name  string
		Email string
	}

	//SELECT users.name, emails.email FROM users LEFT JOIN emails on emails.user_id = users.id
	rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows()
	for rows.Next() {
		//...
	}
	var results []Result
	//SELECT users.name, emails.email FROM users LEFT JOIN emails on emails.user_id = users.id
	db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)

	var user User
	// 多连接及参数
	db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "ourlang@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 9.11 直接执行sql语句

对于复杂的查询,例如多表连接查询,我们可以直接编写sql语句,然后执行sql语句。 gorm通过db.Raw设置sql语句,通过Scan执行查询

func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	type Result struct {
		Name  string
		Age   int
		Total int
	}
	var result []Result
	db.Table("user").Select("name, age").Where("name = ?", "赵六").Scan(&result)

	// 原生 SQL
	db.Raw("SELECT name, age FROM user WHERE name = ?", "赵六").Scan(&result)
	sqlStr := "SELECT name , COUNT(*) AS total FROM user WHERE create_time>? GROUP BY name HAVING(total >0)"

	//因为sql语句使用了一个问号(?)作为绑定参数, 所以需要传递一个绑定参数(Raw第二个参数).
	//Raw函数支持绑定多个参数
	db.Raw(sqlStr, "2019-01-01 00:00:00").Scan(&result)
	fmt.Println(result)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 10 SQL生成器

# 10.1 执行原生SQL

执行原生SQL时,不支持与其它方法的链式操作。更多用法请参考官网SQL生成器

func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}

	//修改 新增 删除用db.Exec()
	db.Exec("DROP TABLE user;")
	db.Exec("UPDATE user SET create_time=? WHERE id IN (?)", time.Now(), []int64{11,22,33})

	// Scan
	type Result struct {
		Name string
		Age  int
	}

	//查询使用db.Raw()
	var result[] Result
	db.Raw("SELECT name, age FROM user WHERE id > ?", 3).Scan(&result)
	fmt.Println(result)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 10.2 sql.Row和sql.Rows

通过*sql.Row*sql.Rows 获取查询结果

func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	var user User
	row := db.Table("user").Where("name = ?", "张三").Select("name, age").Row() // (*sql.Row)
	row.Scan(&user.Name, &user.Age)

	rows, err := db.Model(&User{}).Where("name = ?", "张三").Select("name, age").Rows() // (*sql.Rows, error)
	defer rows.Close()
	for rows.Next() {
		var u User
		rows.Scan(&u.Name, &u.Age)
		fmt.Println(u.Name,u.Age)
	}

	// 原生 SQL
	rows, err = db.Raw("select name, age  from user where name = ?", "张三").Rows() // (*sql.Rows, error)
	defer rows.Close()
	for rows.Next() {
		var u User
		rows.Scan(&u.Name, &u.Age)
		fmt.Println(u.Name,u.Age)
	}
}
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

# 10.3 将sql.Rows扫描至model

func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	rows, err := db.Model(&User{}).Where("name = ?", "张三").Select("name, age").Rows() // (*sql.Rows, error)
	defer rows.Close()

	for rows.Next() {
		var user User
		// ScanRows 扫描一行记录到 user
		db.ScanRows(rows, &user)
		// do something
		fmt.Println(user.Name,user.Age)
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 11 事务处理

GORM默认会将单个的create, update, delete操作封装在事务内进行处理,以确保数据的完整性。 如果你想把多个create, update, delete操作作为一个原子操作,Transaction就是用来完成这个的。 在MySQL中只有使用了Innodb数据库引擎的数据库或表才支持事务

# 11.1 事务流程说明

func CreateUsers(db *gorm.DB) error {
  return db.Transaction(func(tx *gorm.DB) error {
    // 在事务中做一些数据库操作 (这里应该使用 'tx' ,而不是 'db')
    if err := tx.Create(&User{Name: "zhangsan"}).Error; err != nil {
      // 返回任意 err ,整个事务都会 rollback
      return err
    }

    if err := tx.Create(&User{Name: "lisi"}).Error; err != nil {
      return err
    }

    // 返回 nil 提交事务
    return nil
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 11.2 事务(手动控制)

// 开启事务
tx := db.Begin()

// 在事务中做一些数据库操作 (这里应该使用 'tx' ,而不是 'db')
tx.Create(...)

// ...

// 有错误时,手动调用事务的 Rollback()
tx.Rollback()

// 无错误时,手动调用事务的 Commit()
tx.Commit()
1
2
3
4
5
6
7
8
9
10
11
12
13

# 11.3 事务示例

package main

import (
	"fmt"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	"time"
)

var (
	// 定义一个全局对象db
	db *gorm.DB
	//定义数据库连接的相关参数值
	//连接数据库的用户名
	userName string = "root"
	//连接数据库的密码
	password string = "admin"
	//连接数据库的地址
	ipAddress string = "127.0.0.1"
	//连接数据库的端口号
	port int = 3306
	//连接数据库的具体数据库名称
	dbName string = "go_test"
	//连接数据库的编码格式
	charset string = "utf8mb4"
	//想要能正确的处理 time.Time,你需要添加 parseTime 参数。
	parseTime string = "True"
	//设置时间的位置
	loc string = "Local"
)

func connectMysql() (err error) {
	//dsn="root:admin@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4&parseTime=True&loc=Local"
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=%s&loc=%s", userName, password, ipAddress, port, dbName, charset, parseTime, loc)
	db, err = gorm.Open("mysql", dsn)
	if err != nil {
		fmt.Println("连接数据库异常,err", err)
		return err
	}
	//设置最大的空闲连接数
	db.DB().SetMaxIdleConns(10)
	// 设置最大连接数
	db.DB().SetMaxOpenConns(100)
	//自动生成数据库表 ,生成的表明默认是struct名称的复数形式。如:user -》users
	// 如果不想是复数形式,需设定:
	db.SingularTable(true)
	//defer db.Close()
	fmt.Println("连接数据库成功!")
	return nil
}

//创建一个和数据库表结构一样的机构体
//gorm 用 tag 的方式来标识 mysql 里面的约束
type User struct {
	Id         int    `gorm:"primary_key"`
	Name       string `gorm:"not null"`
	Age        int
	CreateTime time.Time `gorm:"column:create_time;not null"`
}

//CreateUsers 批量插入用户数据
//如果一个报错,则回滚已经做过的操作
func CreateUsers() error {
	// 请注意,事务一旦开始,你就应该使用 tx 作为数据库句柄
	tx := db.Begin()
	defer func() {
		if r := recover(); r != nil {
			tx.Rollback()
		}
	}()

	if err := tx.Error; err != nil {
		return err
	}

	if err := tx.Create(&User{Name: "zhangsan",Age:17}).Error; err != nil {
		tx.Rollback()
		return err
	}

	if err := tx.Create(&User{Name: "lisi",Age:38}).Error; err != nil {
		tx.Rollback()
		return err
	}

	return tx.Commit().Error
}
func main() {
	err := connectMysql()
	if err != nil {
		fmt.Println("初始化数据库失败,err", err)
		return
	}
	err = CreateUsers()
	if err != nil {
		fmt.Println("事务操作失败,err", err)
		return
	}
}
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99

# 12 GORM更多资料

GORM中文文档