GO orm 框架GORM学习

1. 介绍

gorm是一个使用Go语言编写的ORM框架。它文档齐全,对开发者友好,支持主流数据库。官方中文文档 https://gorm.io/zh_CN/docs/index.html

2. 安装

go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite

3. 连接MYSQL

GORM 官方支持的数据库类型有:MySQL, PostgreSQL, SQlite, SQL Server,这里只讲常见的MYSQL。其他类型数据库参见官方文档。

3.1 安装mysql依赖

go get -u gorm.io/driver/mysql

3.2 建立连接

a. 默认连接
// 默认连接
func ConnectMysqlByDefault(host, port, user, pass, dbname string) (*gorm.DB, error) {
 // user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local
 dns := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
  user, pass, host, port, dbname)
 return gorm.Open(mysql.Open(dns), &gorm.Config{})
}
b. 自定义配置连接
// 自定义配置连接
func ConnectMysqlByCustom(host, port, user, pass, dbname string) (*gorm.DB, error) {
 // user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local
 dns := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
  user, pass, host, port, dbname)
 return gorm.Open(mysql.New(mysql.Config{
  DSN:                       dns,
  DefaultStringSize:         256,   // string 类型字段的默认长度
  DisableDatetimePrecision:  true,  //禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
  DontSupportRenameIndex:    true,  //重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
  DontSupportRenameColumn:   true,  //用change重命名列,MySQL8之前的数据库和 MariaDB 不支持重命名列
  SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
 }))
}
c. 基于database/sql连接
// 基于database/sql连接,进行二次封装
func ConnectMysqlByDatabaseSql(host, port, user, pass, dbname string) (*gorm.DB, error) {
 dns := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
  user, pass, host, port, dbname)
 db, err := sql.Open("mysql", dns)
 if err != nil {
  return nil, err
 }
 return gorm.Open(mysql.New(mysql.Config{Conn: db}))
}

gorm.Config配置使用,参见官方文档: https://gorm.io/zh_CN/docs/gorm_config.html

4. 声明模型

4.1 什么是模型

模型其实就是标准的结构体(struct),其属性和表结构的字段按照 gorm约定 一一对应。约定如下:

  • gorm使用ID作为主键,对应表中的id
  • 使用结构体名的 蛇形复数 作为表名。对于结构体 User,根据约定,其表名为 users
  • 使用 CreatedAtUpdatedAt 字段追踪创建、更新时间。

gorm 定义一个 gorm.Model 结构体,其包括字段 IDCreatedAtUpdatedAtDeletedAt,其他结构体直接匿名嵌套即可。

4.2 字段标签

标签名 说明
column 指定 db 列名
type 列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not nullsize, autoIncrement… 像 varbinary(8) 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT
size 指定列大小,例如:size:256
primaryKey 指定列为主键
unique 指定列为唯一
default 指定列的默认值
not null 指定列为 NOT NULL
autoIncrement 指定列为自动增长
comment 迁移时为字段添加注释
index 根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看 索引 获取详情

更多约定,请查看官方文档

4.3 创建用户模型

按照gorm约定创建用户模型,便于后面使用。用户模型如下

// 用户模型
type User struct {
 gorm.Model
 NickName     string         `gorm:"type:varchar(20);not null;default:'';comment:昵称"`
 Age          uint8          `gorm:"size:4;comment:年龄"`
 Phone        string         `gorm:"type:char(11);index:un_phone;comment:手机号"`
 MemberNumber string         `gorm:"type:varchar(20);index:un_phone;comment:会员编号"`
 Birthday     sql.NullString `gorm:"type:varchar(10);comment:生日"`
 ActivatedAt  sql.NullTime   `gorm:"comment:激活时间"`
}

5. 迁移

5.1 根据模型生成表结构

根据上面创建的用户模型User,生成对应的表结构

a. 代码

// 自动迁移schema,(根据结构体创建或者更新schema)
func GormAutoMigrate(host, port, use, pass, database string) error {
  // ConnectMysqlByDefault 代码参见上面的: 默认连接
 mysqlByDefault, err := ConnectMysqlByDefault(host, port, use, pass, database)
 if err != nil {
  return err
 }
 // 指定引擎和表备注
 err = mysqlByDefault.Set("gorm:table_options", "ENGINE=InnoDB COMMENT='用户表'").AutoMigrate(&User{})
 if err != nil {
  return err
 }
 return nil
}

// 测试迁移
func TestAutoMigrate(t *testing.T) {
 host := "127.0.0.1"
 use,pass,port,database := "root","root","3306","test"
 err := gorme.GormAutoMigrate(host, port, use, pass, database)
 if err != nil {
  t.Error(err)
 }
 fmt.Println("创建表结构完成!")
}

b. 生成的表结构如下:

CREATE TABLE `users` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `created_at` datetime(3) DEFAULT NULL,
  `updated_at` datetime(3) DEFAULT NULL,
  `deleted_at` datetime(3) DEFAULT NULL,
  `nick_name` varchar(20) COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '昵称',
  `age` tinyint unsigned DEFAULT NULL COMMENT '年龄',
  `phone` char(11) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '手机号',
  `member_number` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '会员编号',
  `birthday` varchar(10) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '生日',
  `activated_at` datetime(3) DEFAULT NULL COMMENT '激活时间',
  PRIMARY KEY (`id`),
  KEY `idx_users_deleted_at` (`deleted_at`),
  KEY `un_phone` (`phone`,`member_number`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='用户表';

6.CRUD

6.1 初始化客户端

// 定义包内全局变量
var mysqlClient *gorm.DB
// 初始化mysql客户端
func init()  {
 // 连接客户端
 host := "127.0.0.1"
 user, pass, port, database := "root", "root", "3306", "test"
 databaseSql, _ := gorme.ConnectMysqlByDatabaseSql(host, port, user, pass, database)
 mysqlClient = databaseSql
}

6.2 单条插入

// 保存一条记录
func TestAddOne(t *testing.T) {
 // 初始化结构体
 userRow := gorme.User{
  NickName:     "李四",
  Age:          18,
  Phone:        "12340000",
  MemberNumber: "A0001",
  Birthday:     sql.NullString{String: "1991-03-04",Valid: true},
  ActivatedAt:  sql.NullTime{Time: time.Now(),Valid: true},
 }
 // 传入指针
 result := mysqlClient.Create(&userRow)
 fmt.Println(result)
 fmt.Println("id: ",userRow.ID)
 fmt.Println("插入记录错误: ",result.Error)
 fmt.Println("插入记录的条数: ",result.RowsAffected)
}
/*** 输出
=== RUN   TestAddOne
&{0xc000184240 <nil> 1 0xc0001a21c0 0}
id:  1
插入记录错误:  <nil>
插入记录的条数:  1
--- PASS: TestAddOne (0.04s)
PASS
*/

6.3 批量插入

// 批量插入
func TestBatchInsert(t *testing.T) {
 // 定义user 切片
  userRows := []gorme.User{
  {NickName: "路人甲", Age: 20, Phone: "20000000", MemberNumber: "A0002"},
  {NickName: "路人乙", Age: 22, Phone: "30000000", MemberNumber: "A0003"},
  {NickName: "路人丙", Age: 24, Phone: "40000000", MemberNumber: "A0004"},
 }
 // 保存
 result := mysqlClient.Create(&userRows)
 fmt.Println("插入记录错误: ", result.Error)
 fmt.Println("插入记录的条数: ", result.RowsAffected)
 // 打印ID
 for _, row := range userRows {
  fmt.Println("插入ID: ", row.ID)
 }
}
/** 输出
=== RUN   TestBatchInsert
插入记录错误:  <nil>
插入记录的条数:  3
插入ID:  4
插入ID:  5
插入ID:  6
--- PASS: TestBatchInsert (0.02s)
PASS
*/

更新插入方式,参见文档: https://gorm.io/zh_CN/docs/create.html

6.4 查询

a. 单条记录: FirstTakeLast
  • First: 获取第一条记录(主键升序)
  • Take: 获取一条记录,没有指定排序字段
  • Last: 获取最后一条记录(主键降序)
// 使用提供的First、Take、Last,查询单条记录
func TestGetOne(t *testing.T) {
 // 定义对应的结构体变量存储结果
 var firstUser gorme.User
 var taskUser gorme.User
 var lastUser gorme.User
 var result *gorm.DB
 // 获取第一条记录(主键升序) SELECT * FROM users ORDER BY id LIMIT 1;
 result = mysqlClient.First(&firstUser)
 fmt.Printf("First Result: %+v\n", result.RowsAffected)
 // 获取一条记录,没有指定排序字段 SELECT * FROM users LIMIT 1;
 result = mysqlClient.Take(&taskUser)
 fmt.Printf("Take Result: %+v\n", result.RowsAffected)
 // 获取最后一条记录(主键降序)SELECT * FROM users ORDER BY id DESC LIMIT 1;
 result = mysqlClient.Last(&lastUser)
 fmt.Printf("lastUser Result: %+v\n", result.RowsAffected)
}

@注意: 如果表里没有数据,会报错result.Error返回:ErrRecordNotFound,可以使用下面语句检查错误。如果想避免ErrRecordNotFound错误,你可以使用Find,如:db.Limit(1).Find(&user)

// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
b. 多条记录: Find
// 使用Find(默认查询的是检索全部)
func TestGetByFind(t *testing.T) {
 var userList []gorme.User
 // 指针查询字段
 result := mysqlClient.Select("id", "nick_name").Find(&userList)
 for _, user := range userList {
  fmt.Printf("id: %d nick_name: %s \n", user.ID,user.NickName)
 }
 fmt.Println("查询记录数: ", result.RowsAffected)
 fmt.Println("查询错误: ", result.Error)
}
/** 输出
=== RUN   TestGetByFind
id: 2 nick_name: 李四 
id: 3 nick_name: 张三 
id: 4 nick_name: 路人甲 
id: 5 nick_name: 路人乙 
id: 6 nick_name: 路人丙 
查询记录数:  5
查询错误:  <nil>
--- PASS: TestGetByFind (0.00s)
PASS
*/
c. 条件查询

1. String条件

// 根据String条件查询
func TestGetByStringWhere(t *testing.T) {
 // 定义对应的结构体变量
 var user gorme.User
 var userList []gorme.User
 var result *gorm.DB
 // 字符串条件查询一条
 result = mysqlClient.Where("nick_name = ?", "张三").First(&user)
 fmt.Printf("Res1: %v err:%v \n", result.RowsAffected,result.Error)
 // 字符串条件查询多条
 result = mysqlClient.Where("nick_name <> ?", "张三").Find(&userList)
 fmt.Printf("Res2: %v err:%v \n", result.RowsAffected,result.Error)
 // 多个条件
 result = mysqlClient.Where("nick_name = ? and age >= ?", "张三", 18).First(&user)
 fmt.Printf("Res3: %v err:%v \n", result.RowsAffected,result.Error)
}
/** 输出
=== RUN   TestGetByStringWhere
Res1: 1 err:<nil> 
Res2: 4 err:<nil> 
Res3: 1 err:<nil> 
--- PASS: TestGetByStringWhere (0.02s)
PASS
*/

2. Struct和Map条件

// 根据struct和map 条件查询结果
func TestGetByStructAndMapWhere(t *testing.T) {
 // 定义对应的结构体变量
 var user gorme.User
 var userList []gorme.User
 var result *gorm.DB
 // 结构体条件
 result = mysqlClient.Where(&gorme.User{NickName: "张三",Age: 18}).First(&user)
 fmt.Printf("结构体条件: %+v err:%v \n", result.RowsAffected,result.Error)
 // map条件
 result = mysqlClient.Where(map[string]interface{}{"age":18}).Find(&userList)
 fmt.Printf("map条件: %+v err:%v \n", result.RowsAffected,result.Error)
 // 主键切片
 result = mysqlClient.Where([]int64{2,3,4,5}).Find(&userList)
 fmt.Printf("主键切片: %+v err:%v \n", result.RowsAffected,result.Error)
}
/** 输出
=== RUN   TestGetByStructAndMapWhere
结构体条件: 1 err:<nil> 
map条件: 3 err:<nil> 
主键切片: 4 err:<nil> 
--- PASS: TestGetByStructAndMapWhere (0.01s)
PASS
*/

@注意 当使用结构作为条件查询时,GORM 只会查询非零值字段。这意味着如果您的字段值为 0''false 或其他 零值,该字段不会被用于构建查询条件,例如:

db.Where(&User{NickName: "张三", Age: 0}).Find(&users)
// SELECT * FROM users WHERE name = "张三";

更多查询规则和方法,见: https://gorm.io/zh_CN/docs/query.html

d. and or 复合查询
参见:Advanced Query | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
db.Where(
  db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
).Or(
  db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"),
).Find(&Pizza{}).Statement

// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge")

6.5 更新

a. 更新单个字段
// 更新单个字段
func TestUpdateColumn(t *testing.T) {
 var result *gorm.DB
 // 字符串条件更新
 // UPDATE users SET nick_name='张三A', updated_at=当前时间 WHERE nick_name='张三;
 result = mysqlClient.Model(&gorme.User{}).Where("nick_name = ?", "张三").
  Update("nick_name", "张三A")
 fmt.Printf("条件更新: %+v err:%v \n", result.RowsAffected, result.Error)
 // 结构体条件更新
 // UPDATE users SET age=28, updated_at=当前时间 WHERE member_number='A0001;
 result = mysqlClient.Model(&gorme.User{}).Where(&gorme.User{MemberNumber: "A0001"}).Update("age",28)
 fmt.Printf("结构体条件更新: %+v err:%v \n", result.RowsAffected, result.Error)
}
/** 输出
=== RUN   TestUpdateColumn
条件更新: 1 err:<nil> 
结构体条件更新: 1 err:<nil> 
--- PASS: TestUpdateColumn (0.06s)
PASS
*/
b. 更新多个字段
// 更新多个字段
func TestUpdateMultipleColumn(t *testing.T) {
 var result *gorm.DB
 // 使用map
 updateMap := map[string]interface{}{
  "age":      32,
  "birthday": "1991-01-05",
 }
 // UPDATE users SET age=32,birthday='1991-01-05',updated_at=当前时间 WHERE id=6;
 result = mysqlClient.Model(&gorme.User{}).Where("id = ?", 6).Updates(updateMap)
 fmt.Printf("使用map结构更新: %+v err:%v \n", result.RowsAffected, result.Error)
 // 使用结构体(不使用Select)
 updateUser := gorme.User{
  Birthday: sql.NullString{String: "1993-10-10", Valid: true},
  Age:      0,
 }
 // @注意这里的age=0不会更新到MySQL
 // UPDATE users SET birthday='1993-09-09',updated_at=当前时间 WHERE id=5;
 result = mysqlClient.Model(&gorme.User{}).Where("id = ?", 5).Updates(updateUser)
 fmt.Printf("使用struct结构更新: %+v err:%v \n", result.RowsAffected, result.Error)
 // 使用结构体(使用Select)
 updateUser2 := gorme.User{
  Birthday: sql.NullString{String: "1993-09-09", Valid: true},
  Age:      0,
 }
 // UPDATE users SET birthday='1993-09-09',age=0,updated_at=当前时间 WHERE id=4;
 result = mysqlClient.Model(&gorme.User{}).
  Select("birthday", "age"). //指定要更新的字段
  Where("id = ?", 4).Updates(updateUser2)
 fmt.Printf("使用struct结构更新2: %+v err:%v \n", result.RowsAffected, result.Error)
}

6.6 删除

a. 软删除
// 删除数据
func TestSoftDel(t *testing.T) {
 var result *gorm.DB
 // 根据主键,删除一条记录
 result = mysqlClient.Delete(&gorme.User{},1)
 fmt.Printf("根据主键删除一条: %+v err:%v \n", result.RowsAffected, result.Error)
 // 根据主键切片,删除多条记录
 result = mysqlClient.Delete(&gorme.User{},[]int64{2,3})
 fmt.Printf("根据主键切片删除多条: %+v err:%v \n", result.RowsAffected, result.Error)
 // 根据条件删除
 result = mysqlClient.Where("age = ?",0).Delete(&gorme.User{})
 fmt.Printf("根据条件删除: %+v err:%v \n", result.RowsAffected, result.Error)
}

执行上面代码后,发现表里面的记录依然存在,而DeletedAt 置为当前时间。原因是user模型中包含了一个 gorm.DeletedAt 字段,它将自动获得软删除的能力!

b. 永久删除

可以使用 Unscoped 永久删除匹配的记录。

// 删除数据(硬删除)
func TestStrongDel(t *testing.T) {
 var result *gorm.DB
 result = mysqlClient.Unscoped().Delete(&gorme.User{}, 1)
 fmt.Printf("硬删除: %+v err:%v \n", result.RowsAffected, result.Error)
}

6.7 事务

a. 使用Transaction
// 事务使用(Transaction)
func TestTransaction(t *testing.T) {
 err := mysqlClient.Transaction(func(tx *gorm.DB) error {
  //在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
  // 创建用户记录
  user := gorme.User{NickName: "老王", Age: 48}
  if err := tx.Create(&user).Error; err != nil {
   // 回滚事务
   return err
  }
  // 创建用户地址
  userAddress := gorme.UserAddress{Uid: user.ID, Province: "北京", City: "北京", Area: "海淀区"}
  if err := tx.Create(&userAddress).Error; err != nil {
   // 回滚事务
   return err
  }
  return nil
 })
 if err != nil {
  t.Error(err)
 }
 fmt.Println("执行完成")
}
b. 手动事务
// 手动事务
func TestUseManualTx(t *testing.T) {
    // 用户表
 user := gorme.User{NickName: "小丽", Age: 19}
 // 开启事务
 tx := mysqlClient.Begin()
 // 添加用户
 if err := tx.Create(&user).Error; err != nil {
  // 遇到错误时回滚事务
  fmt.Println("添加用户失败: ",err)
  tx.Rollback()
 }
 // 用户地址表
 userAddress := gorme.UserAddress{Uid: user.ID, Province: "北京", City: "北京", Area: "昌平区"}
 // 添加用户地址
 if err := tx.Create(&userAddress).Error; err != nil {
  // 遇到错误时回滚事务
  fmt.Println("添加用户地址失败: ",err)
  tx.Rollback()
 }
 // 提交事务
 tx.Commit()
 fmt.Println("执行完成")
}

@注意: 在实践中发现,执行时虽然事务失败,但是主键依然递增。具体看下图效果:

图片

7、gorm 反向生成 struct

db2struct --host rdsfjnifbfjnifbo.mysql.rds.aliyuncs.com  -d bigbusiness  -t  customer  --package main --struct customer -p -u bigbusiness
db2struct [-H] [-p] [-v] --package pkgName --struct structName --database databaseName --table tableName
Options:
-H, --host= Host to check mariadb status of
–mysql_port=3306 Specify a port to connect to
-t, --table= Table to build struct from
-d, --database=nil Database to for connection
-u, --user=user user to connect to database
-v, --verbose Enable verbose output
–package= name to set for package
–struct= name to set for struct
–json Add json annotations (default)
–no-json Disable json annotations
–gorm Add gorm annotations (tags)
–guregu Add guregu null types
–target= Save file path
-p, --password= Mysql password
-h, --help Show usage message
–version Show version

8、参考操作

插入
//插入数据
func (user *User) Insert()  {
	//这里使用了Table()函数,如果你没有指定全局表名禁用复数,或者是表名跟结构体名不一样的时候
	//你可以自己在sql中指定表名。这里是示例,本例中这个函数可以去除。
	db.Table("user").Create(user)
}
更新
//注意,Model方法必须要和Update方法一起使用
//使用效果相当于Model中设置更新的主键key(如果没有where指定,那么默认更新的key为id),Update中设置更新的值
//如果Model中没有指定id值,且也没有指定where条件,那么将更新全表
//相当于:update user set name='xiaoming' where id=1;
user := User{Id: 1,Name:"xiaoming"}
db.Model(&user).Update(user)

//注意到上面Update中使用了一个Struct,你也可以使用map对象。
//需要注意的是:使用Struct的时候,只会更新Struct中这些非空的字段。
//对于string类型字段的"",int类型字段0,bool类型字段的false都被认为是空白值,不会去更新表

//下面这个更新操作只使用了where条件没有在Model中指定id
//update user set name='xiaohong' wehre sex=1
db.Model(&User{}).Where("sex = ?",1).Update("name","xiaohong")

如果你想手动将某个字段set为空值, 可以使用单独选定某些字段的方式来更新:

user := User{Id: 1}
db.Model(&user).Select("name").Update(map[string]interface{}{"name":"","age":0})

忽略掉某些字段:

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

user := User{Id: 1,Name:"xioaming",Age:12}
db.Model(&user).Omit("name").Update(&user)
删除
//delete from user where id=1;
user := User{Id: 1}
db.Delete(&user)

//delete from user where id > 11;
db.Delete(&User{},"id > ?",11)
事务
func CreateAnimals(db *gorm.DB) err {
  tx := db.Begin()
  // 注意,一旦你在一个事务中,使用tx作为数据库句柄

  if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
     tx.Rollback()
     return err
  }

  if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
     tx.Rollback()
     return err
  }

  tx.Commit()
  return nil
}
查询:
func (user *User) query() (u []User) {
	//查询所有记录
	db.Find(&u)

	//Find方法可以带 where 参数
	db.Find(&u,"id > ? and age > ?",2,12)

	//带where 子句的查询,注意where要在find前面
	db.Where("id > ?", 2).Find(&u)

	// where name in ("xiaoming","xiaohong")
	db.Where("name in (?)",[]string{"xiaoming","xiaohong"}).Find(&u)

	//获取第一条记录,按照主键顺序排序
	db.First(&u)

	//First方法可以带where 条件
	db.First(&u,"where sex = ?",1)

	//获取最后一条记录,按照主键顺序排序
	//同样 last方法也可以带where条件
	db.Last(&u)

	return u
}

注意:方法中带的&u表示是返回值用u这个对象来接收。

上面的查询都将返回表中所有的字段,如果你想指定查询某些字段该怎么做呢?

指定查询字段-Select
//指定查询字段
db.Select("name,age").Where(map[string]interface{}{"age":12,"sex":1}).Find(&u)
使用Struct和map作为查询条件
//使用Struct,相当于:select * from user where age =12 and sex=1
db.Where(&User{Age:12,Sex:1}).Find(&u)

//等同上一句
db.Where(map[string]interface{}{"age":12,"sex":1}).Find(&u)
not 条件的使用
//where name not in ("xiaoming","xiaohong")
db.Not("name","xiaoming","xiaohong").Find(&u)

//同上
db.Not("name",[]string{"xiaoming","xiaohong"}).Find(&u)
or 的使用
//where age > 12 or sex = 1
db.Where("age > ?",12).Or("sex = ?",1).Find(&u)
order by 的使用
//order by age desc
db.Where("age > ?",12).Or("sex = ?",1).Order("age desc").Find(&u)
limit 的使用
//limit 10
db.Not("name",[]string{"xiaoming","xiaohong"}).Limit(10).Find(&u)
offset 的使用
//limit 300,10
db.Not("name",[]string{"xiaoming","xiaohong"}).Limit(10).Offset(300).Find(&u)
count(*)
//count(*)
var count int
db.Table("user").Where("age > ?",0).Count(&count)

注意:这里你在指定表名的情况下sql为:select count(*) from user where age > 0;

如上代码如果改为:

var count int
var user []User
db.Where("age > ?",0).Find(&user).Count(&count)

相当于你先查出来[]User,然后统计这个list的长度。跟你预期的sql不相符。

group & having
rows, _ := db.Table("user").Select("count(*),sex").Group("sex").
		Having("age > ?", 10).Rows()
for rows.Next() {
    fmt.Print(rows.Columns())
}
join
db.Table("user u").Select("u.name,u.age").Joins("left join user_ext ue on u.user_id = ue.user_id").Row()

如果有多个连接,用多个Join方法即可。

原生函数
db.Exec("DROP TABLE user;")
db.Exec("UPDATE user SET name=? WHERE id IN (?)", "xiaoming", []int{11,22,33})
db.Exec("select * from user where id > ?",10).Scan(&user)
一些函数

FirstOrInit 和 FirstOrCreate

获取第一个匹配的记录,若没有,则根据条件初始化一个新的记录:

//注意:where条件只能使用Struct或者map。如果这条记录不存在,那么会新增一条name=xiaoming的记录
db.FirstOrInit(&u,User{Name:"xiaoming"})
//同上
db.FirstOrCreate(&u,User{Name:"xiaoming"})

Attrs

如果没有找到记录,则使用Attrs中的数据来初始化一条记录:

//使用attrs来初始化参数,如果未找到数据则使用attrs中的数据来初始化一条
//注意:attrs 必须 要和FirstOrInit 或者 FirstOrCreate 连用
db.Where(User{Name:"xiaoming"}).Attrs(User{Name:"xiaoming",Age:12}).FirstOrInit(&u)

Assign

//不管是否找的到,最终返回结构中都将带上Assign指定的参数
db.Where("age > 12").Assign(User{Name:"xiaoming"}).FirstOrInit(&u)

Pluck

如果user表中你只想查询age这一列,该怎么返回呢,gorm提供了Pluck函数用于查询单列,返回数组:

var ages []int
db.Find(&u).Pluck("age",&ages)

Scan

Scan函数可以将结果转存储到另一个结构体中。

type SubUser struct{
    Name string
    Age int
}

db.Table("user").Select("name,age").Scan(&SubUser)

sql.Row & sql.Rows

row和rows用户获取查询结果。

//查询一行
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有内置的日志记录器支持,默认情况下,它会打印发生的错误。

// 启用Logger,显示详细日志
db.LogMode(true)

// 禁用日志记录器,不显示任何日志
db.LogMode(false)

// 调试单个操作,显示此操作的详细日志
db.Debug().Where("name = ?", "xiaoming").First(&User{})

参考文档:

go语言手册

Gorm 详细教程

Go orm框架gorm学习

Go ORM 概览

Go ORM 反向生成struct 实例:https://blog.csdn.net/qq_39458593/article/details/106223700

Shelnutt2/db2struct: Converts a mysql table into a golang struct (github.com)

Gorm 使用实例

posted @ 2022-06-27 13:42  timelesszhuang  阅读(1912)  评论(0编辑  收藏  举报