Gorm的基本使用方法| 青训营

115 阅读7分钟

整理自gorm.io/zh_CN/docs/…

本文以Gorm+MySQL为例,介绍Gorm的基本使用。如果想更加深入了解,可以去看官方文档。

由于本文是在学习阶段同步整理的,如有错误,烦请指正!

1、安装

go get -u gorm.io/gorm

2、模型

模型是Gorm中的一个重要概念。可以类比为Java后端开发中的实体类定义。

模型通过结构体定义,如:

type User struct {
    ID			uint
    Username	string
    Password	string
}

上述代码定义了一个简单的User模型。

Gorm倾向于使用约定,当然它也提供了丰富的配置。默认情况下,Gorm有这么几个约定:

  • Gorm使用名为ID的字段作为主键
  • 使用结构体的蛇形复数作为标明(蛇行结构:user_name)
  • 字段名的蛇形作为列名
  • 使用CreatedAt、UpdatedAt字段作为创建、更新时间

遵循Gorm的约定,可以减少很多代码量。

2.1、gorm.Model

gorm.Model是Gorm官方定义的一个模型,其中包含了若干个有用的字段,我们可以通过“嵌入”的方式,将其嵌入到我们的模型中,如:

// gorm.Model的定义
type Model struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
}

type User struct {
    gorm.Model			// 将gorm.Model嵌入进自己的model
    Username	string
    Password	string
}

2.2、权限控制

我们可以在定义model的结构体中添加tag,来描述、控制该字段的权限。这些权限主要是读、写权限。demo如下:

type User struct {
  Name string `gorm:"<-:create"` // 允许读和创建
  Name string `gorm:"<-:update"` // 允许读和更新
  Name string `gorm:"<-"`        // 允许读和写(创建和更新)
  Name string `gorm:"<-:false"`  // 允许读,禁止写
  Name string `gorm:"->"`        // 只读(除非有自定义配置,否则禁止写)
  Name string `gorm:"->;<-:create"` // 允许读和写
  Name string `gorm:"->:false;<-:create"` // 仅创建(禁止从 db 读)
  Name string `gorm:"-"`  // 通过 struct 读写会忽略该字段
  Name string `gorm:"-:all"`        // 通过 struct 读写、迁移会忽略该字段
  Name string `gorm:"-:migration"`  // 通过 struct 迁移会忽略该字段
}

关于上述的读写权限的tag,大家可以这样理解:

  • 左箭头(<-):表示写
  • 右箭头(->):表示读

2、连接数据库

本文仅展示连接mysql数据库,若想连接其他数据库,可以去官网查询。

import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
)

func main() {
  // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
  dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}

连接数据的关键函数是gorm.Open(),函数签名如下:

func Open(dialector Dialector, opts ...Option) (db *DB, err error)

第一个参数输入的是dsn,也就是data server name

第二个参数是一些可选项,如果并没有什么需要配置的,传入&gorm.Config{}就可。

3、CRUD

因为Gorm是基于模型的思想来进行操作的,所以想要通过Gorm对DB进行CRUD操作的时候,需要使用模型(即结构体)。

本节的展示,基于以下代码:

import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
)

type User struct{
    ...			// 此处省略
}

func main() {
  // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
  dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}

3.1、创建

创建一条数据库数据,使用Create函数

user := User{Name: "user", Age: 18, Birthday: time.Now()}
result := db.Create(&user)   // 此处要传入指针

也可以一下子创建多条数据:

users := []*User{
    User{Name: "user1", Age: 18, Birthday: time.Now()},
    User{Name: "user2", Age: 19, Birthday: time.Now()},
}

result := db.Create(users)

Create()函数返回的result中有两个常用字段:

  • result.Error:返回error
  • result.RowsAffected:返回插入记录的条数

Gorm还可以选择指定字段、忽略指定字段来创建数据,详见官方文档

3.2、查询

3.2.1、基础使用

Gorm提供了First、Take、Last、Find方法, 以便我们从数据库中检索单个对象。

// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;

// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;

// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;

// 获取全部子记录
result := db.Find(&users)
// SELECT * FROM users;

可以看到,这些函数中需要传递一个参数,是结构体变量的地址,gorm会将查询到的数据存入该地址。

除了传递结构体变量的地址,我们还可以传入一些其他的参数,实现更加灵活的查找。

当主键是数字时:

db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;

db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;

db.Find(&users, []int{1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);

当主键是字符串时:

db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";

用主键进行条件查询,还有以下两种方法:

var user = User{ID: 10}
db.First(&user)
// SELECT * FROM users WHERE id = 10;

var result User
db.Model(User{ID: 10}).First(&result)
// SELECT * FROM users WHERE id = 10;

特别地,如果在自定义的模型中嵌入了gorm.Model,那么查询语句会自动增加对DeletedAt字段的判断。关于DeletedAt字段,可以看本文的2.1部分。


如果想要实现更加复杂的查询,可以使用Where()、Not()、Select()等字段

3.2.2、Where()的使用

// Find the first user with name jinzhu
db.Where("name = ?", "jinzhu").First(&user)

// Find the first user with name jinzhu and age 20
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)

// Find the first user with name jinzhu and age not equal to 20
db.Where("name = ?", "jinzhu").Where("age <> ?", "20").First(&user)

Where()函数的使用方法很多,上面列了三种使用方法,应该不难理解,这边不赘述了。

3.2.3、Not()的使用

关于Not()的使用和Where()类似,这边直接上代码演示

db.Not("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1;

// Not In
db.Not(map[string]interface{}{"name": []string{"jinzhu", "jinzhu 2"}}).Find(&users)
// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");

// Struct
db.Not(User{Name: "jinzhu", Age: 18}).First(&user)
// SELECT * FROM users WHERE name <> "jinzhu" AND age <> 18 ORDER BY id LIMIT 1;

// Not In slice of primary keys
db.Not([]int64{1,2,3}).First(&user)
// SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;

3.2.4、Select()字段

默认情况下,Gorm实现的查询都是select * from ....

但是使用Select()之后,我们可以指定我们需要什么字段

db.Select("name", "age").Find(&users)
// SELECT name, age FROM users;

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

db.Table("users").Select("COALESCE(age,?)", 42).Rows()
// SELECT COALESCE(age,'42') FROM users;

3.3、更新

Gorm有两个字段可以实现数据的更新:Save()、Update()

3.3.1、Save()使用

Save()会保存所有的字段,即使字段是零值。

db.First(&user)

user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)
// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;

使用Save()函数时需要注意给定主键的值,不然Save()会创建一个新的数据。

3.3.2、Update()使用

Update()可以实现更加灵活的更新操作。

// Update with conditions
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;

// User's ID is `111`:
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

// Update with conditions and model value
db.Model(&user).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;

如果使用了Model()函数指定了表的结构体定义,Update()函数会默认使用主键作为参数。

注意demo中Model的参数并不一样

我们还可以同时更新多列。

// Update attributes with `struct`, will only update non-zero fields
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;

// Update attributes with `map`
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

搭配使用Select()、Omit(),我们可以更新选定字段。

// Select with Map
// User's ID is `111`:
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;

db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

// Select with Struct (select zero value fields)
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;

// Select all fields (select all fields include zero value fields)
db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})

// Select all fields but omit Role (select all fields include zero value fields)
db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})

3.4、删除

Gorm使用Delete()实现删除操作。

// Email 的 ID 是 `10`
db.Delete(&email)
// DELETE from emails where id = 10;

// 带额外条件的删除
db.Where("name = ?", "jinzhu").Delete(&email)
// DELETE from emails where id = 10 AND name = "jinzhu";

Delete()函数中,可以添加第二个参数,实现根据主键删除。

db.Delete(&User{}, 10)
// DELETE FROM users WHERE id = 10;

db.Delete(&User{}, "10")
// DELETE FROM users WHERE id = 10;

db.Delete(&users, []int{1,2,3})
// DELETE FROM users WHERE id IN (1,2,3);

4、小结

本文通过官方文档提供的资料和案例,结合自己的一些理解,整理出这样一篇文章。关于Gorm的使用,远不止本文这般简单。

Gorm还可以使用Hook、错误处理、日志等功能。

这些功能,就等读者后续根据官方文档进一步学习了。