GORM 基础 -- Gen

Posted chinusyan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GORM 基础 -- Gen相关的知识,希望对你有一定的参考价值。

https://gorm.io/gen/
github

1、GEN Guides

GEN:友好和更安全的代码生成

1.1 概述

  • 来自动态原始SQL的惯用和可重用API
  • 100%类型安全的DAO API,不使用 interface
  • Database To Struct遵循GORM约定
  • 底层的GORM,支持GORM支持的所有功能,插件,DBMS

1.2 安装

go get -u gorm.io/gen

1.3 Quick start

在你的应用程序中使用gen是非常简单的,下面是它的工作原理:

1.3.1 用golang编写配置

package main

import "gorm.io/gen"

// Dynamic SQL
type Querier interface 
  // SELECT * FROM @@table WHERE name = @nameif role !="" AND role = @roleend
  FilterWithNameAndRole(name, role string) ([]gen.T, error)


func main() 
  g := gen.NewGenerator(gen.Config
    OutPath: "../query",
    Mode: gen.WithoutContext|gen.WithDefaultQuery|gen.WithQueryInterface, // generate mode
  )

  // gormdb, _ := gorm.Open(mysql.Open("root:@(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"))
  g.UseDB(gormdb) // reuse your gorm db

  // Generate basic type-safe DAO API for struct `model.User` following conventions
  g.ApplyBasic(model.User)

  // Generate Type Safe API with Dynamic SQL defined on Querier interface for `model.User` and `model.Company`
  g.ApplyInterface(func(Querier), model.User, model.Company)

  // Generate the code
  g.Execute()

1.3.1 Generate Code

go run main.go

1.3.3 在项目中使用生成的代码

import "your_project/query"

func main() 
  // Basic DAO API
  user, err := query.User.Where(u.Name.Eq("modi")).First()

  // Dynamic SQL API
  users, err := query.User.FilterWithNameAndRole("modi", "admin")

2、动态SQL (Dynamic SQL)

Gen允许从原始SQL(Raw SQL)生成完全类型安全的惯用Go代码,它使用在接口上的注释,这些接口可以在代码生成期间应用于多个模型(model)。

不仅您调优的SQL查询,而且SQL片段也允许共享和重用,让我们举一个例子:

2.1 Overview

2.1.1 Raw SQL

type Querier interface 
  // SELECT * FROM @@table WHERE id=@id
  GetByID(id int) (gen.T, error) // GetByID query data by id and return it as *struct*

  // GetUsersByRole query data by roles and return it as *slice of pointer*
  //   (The below blank line is required to comment for the generated method)
  //
  // SELECT * FROM @@table WHERE role IN @rolesName
  GetByRoles(rolesName ...string) ([]*gen.T, error)

  // InsertValue insert value
  //
  // INSERT INTO @@table (name, age) VALUES (@name, @age)
  InsertValue(name string, age int) error


g := gen.NewGenerator(gen.Config
  // ... some config
)

// Apply the interface to existing `User` and generated `Employee`
g.ApplyInterface(func(Querier) , model.User, g.GenerateModel("employee"))

g.Execute()

运行上面的配置程序为你的应用程序生成查询接口代码,并使用生成的代码如下:

import "your_project/query"

func main() 
  user, err := query.User.GetByID(10)

  employees, err := query.Employee.GetByRoles("admin", "manager")

  err := query.User.InsertValue("modi", 18)

2.1.2 代码片段

代码段通常与DAO接口一起使用

type Querier interface 
  // FindByNameAndAge query data by name and age and return it as map
  //
  // where("name=@name AND age=@age")
  FindByNameAndAge(name string, age int) (gen.M, error)


g := gen.NewGenerator(gen.Config
  // ... some config
)

// Apply the interface to existing `User` and generated `Employee`
g.ApplyInterface(func(Querier) , model.User, g.GenerateModel("employee"))

g.Execute()

use:

import "your_project/query"

func main() 
  userMap, err := query.User.Where(query.User.Name.Eq("modi")).FilterWithNameAndRole("modi", "admin")

2.1.3 More control

Gen支持有条件的注解和自定义返回结果,参考注解了解更多

2.2 注解语法(Annotation Syntax)

注解(Annotation )是在接口的方法上的注释,Gen将解析它们并为应用的结构体生成 查询API

Gen为动态有条件SQL支持提供了一些约定,让我们从三个方面介绍它们:

  • 返回的结果
  • 模板占位符(Template Placeholder)
  • 模板表达式(Template Expression)

2.2.1 返回的结果

Gen允许配置返回的结果类型,它目前支持以下四种基本类型

OptionDescription
gen.Treturns struct
gen.Mreturns map
gen.RowsAffectedreturns rowsAffected returned from database (type: int64)
errorreturns error if any

e.g:

type Querier interface 
  // SELECT * FROM @@table WHERE id=@id
  GetByID(id int) (gen.T, error) // returns struct and error

  // SELECT * FROM @@table WHERE id=@id
  GetByID(id int) gen.T // returns data as struct

  // SELECT * FROM @@table WHERE id=@id
  GetByID(id int) (gen.M, error) // returns map and error

   // INSERT INTO @@table (name, age) VALUES (@name, @age)
  InsertValue(name string, age int) (gen.RowsAffected, error) // returns affected rows count and error

这些基本类型可以与其他符号组合,如*[],例如:

type Querier interface 
  // SELECT * FROM @@table WHERE id=@id
  GetByID(id int) (*gen.T, error) // returns data as pointer and error

  // SELECT * FROM @@table WHERE id=@id
  GetByID(id int) (*[]gen.T, error) // returns data as pointer of slice and error

  // SELECT * FROM @@table WHERE id=@id
  GetByID(id int) ([]*gen.T, error) // returns data as slice of pointer and error

  // SELECT * FROM @@table WHERE id=@id
  GetByID(id int) ([]gen.M, error) // returns data as slice of map and error

2.2.2 模板占位符

Gen提供了一些占位符来生成动态和安全的SQL

NameDescription
@@tableescaped & quoted table name
@@escaped & quoted table/column name from params
@SQL query params from params

e.g:

type Filter interface 
  // SELECT * FROM @@table WHERE @@column=@id
  FilterWithColumn(column string, value string) (gen.T, error)


// Apply the `Filter` interface to `User`, `Company`
g.ApplyInterface(func(Filter) , model.User, model.Company)

生成代码后,可以在应用程序中像这样使用它:

import "your_project/query"

func main() 
  user, err := query.User.FilterWithColumn("name", "jinzhu")
  // similar like db.Exec("SELECT * FROM `users` WHERE `name` = ?", "jinzhu")

  company, err := query.Company.FilterWithColumn("name", "tiktok")
  // similar like db.Exec("SELECT * FROM `companies` WHERE `name` = ?", "tiktok")

2.2.3 表达式模板

Gen为动态条件SQL提供了强大的表达式支持,目前支持以下表达式:

  • if/else
  • where
  • set
  • for

if/else

if/else表达式允许使用golang语法作为条件,它可以写成这样:

if cond1
  // do something here
else if cond2
  // do something here
else
  // do something here
end

例如:

type Querier interface 
  // SELECT * FROM users WHERE
  //  if name !=""
  //      username=@name AND
  //  end
  //  role="admin"
  QueryWith(name string) (gen.T,error)

一个更复杂的例子:

type Querier interface 
  // SELECT * FROM users
  //  if user != nil
  //      if user.ID > 0
  //          WHERE id=@user.ID
  //      else if user.Name != ""
  //          WHERE username=@user.Name
  //      end
  //  end
  QueryWith(user *gen.T) (gen.T, error)

如何使用:

query.User.QueryWith(&UserName: "zhangqiang")
// SELECT * FROM users WHERE username="zhangqiang"

where

where表达式让你更容易为SQL查询编写where子句,让我们以一个简单的例子为例:

type Querier interface 
  // SELECT * FROM @@table
  //  where
  //      id=@id
  //  end
  Query(id int) gen.T

使用生成的代码,你可以像这样使用它:

query.User.Query(10)
// SELECT * FROM users WHERE id=10

这里是另一个复杂的情况,在这种情况下,您将了解到WHERE子句只在任何子表达式匹配时插入,并且它可以巧妙地修剪WHERE子句中不必要的and, or, xor,

type Querier interface 
  // SELECT * FROM @@table
  //  where
  //    if !start.IsZero()
  //      created_time > @start
  //    end
  //    if !end.IsZero()
  //      AND created_time < @end
  //    end
  //  end
  FilterWithTime(start, end time.Time) ([]gen.T, error)

生成的代码可以像这样使用:

var (
  since = time.Date(2022, 10, 1, 0, 0, 0, 0, time.UTC)
  end   = time.Date(2022, 10, 10, 0, 0, 0, 0, time.UTC)
  zero  = time.Time
)

query.User.FilterWithTime(since, end)
// SELECT * FROM `users` WHERE created_time > "2022-10-01" AND created_time < "2022-10-10"

query.User.FilterWithTime(since, zero)
// SELECT * FROM `users` WHERE created_time > "2022-10-01"

query.User.FilterWithTime(zero, end)
// SELECT * FROM `users` WHERE created_time < "2022-10-10"

query.User.FilterWithTime(zero, zero)
// SELECT * FROM `users`

set

用于为SQL查询生成set子句的set表达式,它将自动删除不必要的, 例如:

// UPDATE @@table
//  set
//    if user.Name != "" username=@user.Name, end
//    if user.Age > 0 age=@user.Age, end
//    if user.Age >= 18 is_adult=1 else is_adult=0 end
//  end
// WHERE id=@id
Update(user gen.T, id int) (gen.RowsAffected, error)

生成的代码可以像这样使用:

query.User.Update(UserName: "jinzhu", Age: 18, 10)
// UPDATE users SET username="jinzhu", age=18, is_adult=1 WHERE id=10

query.User.Update(UserName: "jinzhu", Age: 0, 10)
// UPDATE users SET username="jinzhu", is_adult=0 WHERE id=10

query.User.Update(UserAge: 0, 10)
// UPDATE users SET is_adult=0 WHERE id=10

for

for表达式遍历一个切片以生成SQL,让我们通过示例进行解释:

// SELECT * FROM @@table
// where
//   for _,user:=range user
//     if user.Name !="" && user.Age >0
//       (username = @user.Name AND age=@user.Age AND role LIKE concat("%",@user.Role,"%")) OR
//     end
//   end
// end
Filter(users []gen.T) ([]gen.T, error)

使用:

query.User.Filter([]User
        Name: "jinzhu", Age: 18, Role: "admin",
        Name: "zhangqiang", Age: 18, Role: "admin",
        Name: "modi", Age: 18, Role: "admin",
        Name: "songyuan", Age: 18, Role: "admin",
)
// SELECT * FROM users WHERE
//   (username = "jinzhu" AND age=18 AND role LIKE concat("%","admin","%")) OR
//   (username = "zhangqiang" AND age=18 AND role LIKE concat("%","admin","%"))
//   (username = "modi" AND age=18 AND role LIKE concat("%","admin","%")) OR
//   (username = "songyuan" AND age=18 AND role LIKE concat("%","admin","%"))

3、DAO

3.1 Overview

Gen遵循配置即代码(Configuration As Code)实践来生成DAO接口,下面是对配置的介绍。

3.1.1 配置

您需要将配置编写为可运行的golang程序,通常,该程序将被组织在应用程序的子目录中。

/ configuration.go
package main

import (
  "gorm.io/gen"
  "gorm.io/gorm"
  "gorm.io/driver/sqlite"
)

func main() 
  // Initialize the generator with configuration
  g := gen.NewGenerator(gen.Config
     OutPath: "../dal", // output directory, default value is ./query
     Mode:    gen.WithDefaultQuery | gen.WithQueryInterface,
     FieldNullable: true,
  )

  // Initialize a *gorm.DB instance
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config)

  // Use the above `*gorm.DB` instance to initialize the generator,
  // which is required to generate structs from db when using `GenerateModel/GenerateModelAs`
  g.UseDB(db)

  // Generate default DAO interface for those specified structs
  g.ApplyBasic(model.Customer, model.CreditCard, model.Bank, model.Passport)

  // Generate default DAO interface for those generated structs from database
  companyGenerator := g.GenerateModelAs("company", "MyCompany"),
  g.ApplyBasic(
    g.GenerateModel("users"),
    companyGenerator,
    g.GenerateModelAs("people", "Person",
      gen.FieldIgnore("deleted_at"),
      gen.FieldNewTag("age", `json:"-"`),
    ),
  )

  // Execute the generator
  g.Execute()

运行上面的程序,它将生成代码到目录../dal,你可以在你的应用程序中导入dal包,并使用它的接口来查询数据:

3.1.2 gen.Config

type Config struct 
  OutPath      string // query code path
  OutFile      string // query code file name, default: gen.go
  ModelPkgPath string // generated model code's package name
  WithUnitTest bool   // generate unit test for query code

  FieldNullable     bool // generate pointer when field is nullable
  FieldCoverable    bool // generate pointer when field has default value, to fix problem zero value cannot be assign: https://gorm.io/docs/create.html#Default-Values
  FieldSignable     bool // detect integer field's unsigned type, adjust generated data type
  FieldWithIndexTag bool // generate with gorm index tag
  FieldWithTypeTag  bool // generate with gorm column type tag

  Mode GenerateMode // generator modes

输出选项

Option NameDescription
OutPathOutput destination folder for the generator, default value: ./query
OutFileQuery code file name, default value: gen.go
ModelPkgPathGenerated DAO package’s package name, default value: model
WithUnitTestGenerate unit tests for the DAO package, default value: false

生成结构体选项

Option NameDescription
FieldNullable如果列在数据库中可为空(nullable ),则生成字段类型的指针
FieldCoverable如果列在数据库中有默认值,则生成字段类型的指针, 避免零值问题, e.g: https://gorm.io/docs/create.html#Default-Values
FieldSignable基于列的数据库数据类型,使用有符号类型作为字段类型
FieldWithIndexTagGenerate with gorm index tag
FieldWithTypeTagGenerate with gorm type tag, for example: gorm:"type:varchar(12)", default value: false

请参考数据库到结构体以获取更多选项

生成器模式

Tag NameDescription
gen.WithDefaultQueryGenerate global variable Q as DAO interface, then you can query data like: dal.Q.User.First()
gen.WithQueryInterface生成查询api接口而不是结构体,通常用于模拟测试
gen.WithoutContext在没有上下文约束的情况下生成代码,然后您可以在不使用上下文的情况下查询数据,如: dal.User.First(), or you have to query with the context, e.g: dal.User.WithContext(ctx).First()

DAO Interface

一个生成DAO查询接口的示例

type IUserDo interface 
  // Create
  Create(values ...*model.User) error
  CreateInBatches(values []*model.User, batchSize int) error
  Save(values ...*model.User) error

  // Query
  Clauses(conds ...clause.Expression) IUserDo
  As(alias string) gen.Dao
  Columns(cols ...field.Expr) gen.Columns
  Not(conds ...gen.Condition) IUserDo
  Or(conds ...gen.Condition) IUserDo
  Select(conds ...field.Expr) IUserDo
  Where(conds ...gen.Condition) IUserDo
  Order(conds ...field.Expr) IUserDo
  Distinct(cols ...field.Expr) IUserDo
  Omit(cols ...field.Expr) IUserDo
  Join(table schema.Tabler, on ...field.Expr) IUserDo
  LeftJoin(table schema.Tabler, on ...field.Expr) IUserDo
  RightJoin(table schema.Tabler, on ...field.Expr) IUserDo
  Group(cols ...field.Expr) IUserDo
  Having(conds ...gen.Condition) IUserDo
  Limit(limit int) IUserDo
  Offset(offset int) IUserDo
  Scopes(funcs ...func(gen.Dao) gen.Dao) IUserDo
  Unscoped() IUserDo
  Pluck(column field.Expr, dest interface) error
  Attrs(attrs ...field.AssignExpr) IUserDo
  Assign(attrs ...field.AssignExpr) IUserDo
  Joins(fields ...field.RelationField) IUserDo
  Preload(fields ...field.RelationField) IUserDo

  Count() (count int64, err error)
  FirstOrInit() (*model.User1、Create 

1.1 创建纪录

user := UserName: "Jinzhu", Age: 18, Birthday: time.Now()

result := db.Create(&user) // pass pointer of data to Create

user.ID             // 回填插入数据的主键
result.Error        // 返回的 error 信息
result.RowsAffected // 返回插入记录的个数

1.2 用选定的字段创建记录

创建一个记录并为指定的字段分配值

db.Select("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18, "2020-07-04 11:05:21.775")

创建一个记录并忽略传递给要忽略的字段的值。

db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")

1.3 Batch Insert

为了有效地插入大量的记录,将一个切片传递给Create方法。GORM将生成一条SQL语句来插入所有数据并回填主键值,钩子方法(hook methods)也将被调用。

var users = []UserName: "jinzhu1", Name: "jinzhu2", Name: "jinzhu3"
db.Create(&users)

for _, user := range users 
  user.ID // 1,2,3

你可以在创建CreateInBatches时指定批大小,例如:

var users = []UserName: "jinzhu_1", ...., Name: "jinzhu_10000"

// batch size 100
db.CreateInBatches(users, 100)

批量插入也支持 UpsertCreate With Associations时,

CreateBatchSize选项初始化GORM,所有INSERT在创建记录和关联时都会遵守这个选项

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config
  CreateBatchSize: 1000,
)

db := db.Session(&gorm.SessionCreateBatchSize: 1000)

users = [5000]UserName: "jinzhu", Pets: []Petpet1, pet2, pet3...

db.Create(&users)
// INSERT INTO users xxx (5 batches)
// INSERT INTO pets xxx (15 batches)

1.4 创建钩子(Hooks)

GORM允许用户定义钩子实现BeforeSave, BeforeCreate, AfterSave, AfterCreate。这些钩子方法将在创建记录时被调用,有关生命周期的详细信息请参阅钩子

func (u *User) BeforeCreate(tx *gorm.DB) (err error) 
  u.UUID = uuid.New()

  if u.Role == "admin" 
    return errors.New("invalid role")
  
  return

如果你想跳过Hooks方法,你可以使用SkipHooks会话模式(Session mode),例如:

DB.Session(&gorm.SessionSkipHooks: true).Create(&user)

DB.Session(&gorm.SessionSkipHooks: true).Create(&users)

DB.Session(&gorm.SessionSkipHooks: true).CreateInBatches(users, 100)

1.5 Create From Map

GORM支持从map[string]interface[]map[string]interface创建,例如:

db.Model(&User).Create(map[string]interface
  "Name": "jinzhu", "Age": 18,
)

// batch insert from `[]map[string]interface`
db.Model(&User).Create([]map[string]interface
  "Name": "jinzhu_1", "Age": 18,
  "Name": "jinzhu_2", "Age": 20,
)

当从map创建时,钩子不会被调用,关联不会被保存,主键值也不会被返回填充

1.6 从 SQL Expression/Context Valuer 创建

GORM允许用SQL表达式插入数据,有两种方法来实现这个目标,从map[string]interface自定义数据类型创建,例如:

// Create from map
db.Model(User).Create(map[string]interface
  "Name": "jinzhu",
  "Location": clause.ExprSQL: "ST_PointFromText(?)", Vars: []interface"POINT(100 100)",
)
// INSERT INTO `users` (`name`,`location`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"));

// Create from customized data type
type Location struct 
  X, Y int


// Scan implements the sql.Scanner interface
func (loc *Location) Scan(v interface) error 
  // Scan a value into struct from database driver


func (loc Location) GormDataType() string 
  return "geometry"


func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr 
  return clause.Expr
    SQL:  "ST_PointFromText(?)",
    Vars: []interfacefmt.Sprintf("POINT(%d %d)", loc.X, loc.Y),
  


type User struct 
  Name     string
  Location Location


db.Create(&User
  Name:     "jinzhu",
  Location: LocationX: 100, Y: 100,
)
// INSERT INTO `users` (`name`,`location`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"))

1.7 进阶

1.7.1 Create With Associations

在创建带有关联的数据时,如果其关联值不是零,则这些关联将被插入,并且将调用其Hooks方法。

type CreditCard struct 
  gorm.Model
  Number   string
  UserID   uint


type User struct 
  gorm.Model
  Name       string
  CreditCard CreditCard


db.Create(&User
  Name: "jinzhu",
  CreditCard: CreditCardNumber: "411111111111"
)
// INSERT INTO `users` ...
// INSERT INTO `credit_cards` ...

您可以使用SelectOmit 跳过保存关联,例如:

db.Omit("CreditCard").Create(&user)

// skip all associations
db.Omit(clause.Associations).Create(&user)

1.7.2 默认值

你可以为使用default标签为字段定义默认值,例如:

type User struct 
  ID   int64
  Name string `gorm:"default:galeone"`
  Age  int64  `gorm:"default:18"`

然后,将零值字段插入数据库时将使用默认值

任何零值,如0"false将不会保存到数据库中那些字段定义的默认值,你可能想使用指针类型或Scanner/Valuer来避免这种情况,例如:

type User struct 
  gorm.Model
  Name string
  Age  *int           `gorm:"default:18"`
  Active sql.NullBool `gorm:"default:true"`

你必须为数据库中有默认值或虚拟/生成值的字段设置默认标记,如果你想在迁移时跳过默认值定义,你可以使用default:(-),例如:

type User struct 
  ID        string `gorm:"default:uuid_generate_v3()"` // db func
  FirstName string
  LastName  string
  Age       uint8
  FullName  string `gorm:"->;type:GENERATED ALWAYS AS (concat(firstname,' ',lastname));default:(-);"`

当使用虚拟/生成(virtual/generated)值时,你可能需要禁用它的创建/更新权限,检查字段级权限

1.8 Upsert / On Conflict

GORM为不同的数据库提供了兼容的Upsert支持

import "gorm.io/gorm/clause"

// Do nothing on conflict
db.Clauses(clause.OnConflictDoNothing: true).Create(&user)

// Update columns to default value on `id` conflict
db.Clauses(clause.OnConflict
  Columns:   []clause.ColumnName: "id",
  DoUpdates: clause.Assignments(map[string]interface"role": "user"),
).Create(&users)
// MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET ***; SQL Server
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE ***; MySQL

// Use SQL expression
db.Clauses(clause.OnConflict
  Columns:   []clause.ColumnName: "id",
  DoUpdates: clause.Assignments(map[string]interface"count": gorm.Expr("GREATEST(count, VALUES(count))")),
).Create(&users)
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `count`=GREATEST(count, VALUES(count));

// Update columns to new value on `id` conflict
db.Clauses(clause.OnConflict
  Columns:   []clause.ColumnName: "id",
  DoUpdates: clause.AssignmentColumns([]string"name", "age"),
).Create(&users)
// MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET "name"="excluded"."name"; SQL Server
// INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age"; PostgreSQL
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age`=VALUES(age); MySQL

// Update all columns to new value on conflict except primary keys and those columns having default values from sql func
db.Clauses(clause.OnConflict
  UpdateAll: true,
).Create(&users)
// INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age", ...;
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age`=VALUES(age), ...; MySQL

高级查询中查看FirstOrInit, FirstOrCreate

更多细节请检出原始SQL和SQL Builder

2、Query

2.1 检索单个对象

GORM提供了FirstTakeLast方法来从数据库中检索单个对象,它在查询数据库时添加了LIMIT 1条件,如果没有找到记录,它将返回错误ErrRecordNotFound

// Get the first record ordered by primary key
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;

// Get one record, no specified order
db.Take(&user)
// SELECT * FROM users LIMIT 1;

// Get last record, ordered by primary key desc
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;

result := db.First(&user)
result.RowsAffected // returns count of records found
result.Error        // returns error or nil

// check error ErrRecordNotFound
errors.Is(result.Error, gorm.ErrRecordNotFound)

如果你想避免ErrRecordNotFound错误,你可以像db.Limit(1).Find(&user)一样使用Find, Find方法同时接受结构数据和切片数据

使用 Find而不限制单个对象db.Find(&user)将查询整个表并只返回第一个非性能和不确定性的对象

FirstLast方法将根据主键顺序分别查找第一个和最后一个记录。只有当指向目标结构的指针作为参数传递给方法时,或者当使用db.Model()指定模型时,它们才有效。此外,如果相关模型没有定义主键,则模型将按第一个字段排序。例如:

var user User
var users []User

// works because destination struct is passed in
db.First(&user)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

// works because model is specified using `db.Model()`
result := map[string]interface
db.Model(&User).First(&result)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

// doesn't work
result := map[string]interface
db.Table("users").First(&result)

// works with Take
result := map[string]interface
db.Table("users").Take(&result)

// no primary key defined, results will be ordered by first field (i.e., `Code`)
type Language struct 
  Code string
  Name string

db.First(&Language)
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1

用主键检索多个对象

如果主键是数字,则可以通过内联条件使用主键检索对象。当使用字符串时,需要特别注意避免SQL注入;详细信息请查看安全部分。

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

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

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

如果主键是一个字符串(例如,像uuid),查询将被写成如下:

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

当目标对象有一个主键时,主键将被用于构建条件,例如:

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

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

2.2 检索所有对象

// Get all records
result := db.Find(&users)
// SELECT * FROM users;

result.RowsAffected // returns found records count, equals `len(users)`
result.Error        // returns error

2.3 条件(Conditions)

2.3.1 String Conditions

// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;

// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';

// IN
db.Where("name IN ?", []string"jinzhu", "jinzhu 2").Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');

// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';

// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;

// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';

// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';

如果已经设置了对象的主键,则条件查询不会覆盖主键的值,而是将其用作’ and '条件。例如:

var user = UserID: 10
db.Where("id = ?", 20).First(&user)
// SELECT * FROM users WHERE id = 10 and id = 20 ORDER BY id ASC LIMIT 1

查询将给出record not found错误。因此,在你想使用变量(如user)从数据库获取新值之前,将主键属性(如id)设置为nil

2.3.2 Struct & Map Conditions

// Struct
db.Where(&UserName: "jinzhu", Age: 20).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;

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

// Slice of primary keys
db.Where([]int6420, 21, 22).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22

当使用struct查询时,GORM只会查询非零字段,这意味着如果你的字段值为0'',false或其他零值,它将不会被用来构建查询条件,例如:

db.Where(&UserName: "jinzhu", Age: 0).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu";

要在查询条件中包含零值,您可以使用map,它将包括所有键值作为查询条件,例如:

db.Where(map[string]interface"Name": "jinzhu", "Age": 0).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;

有关详细信息,请参见Specify Struct search fields

2.3.3 指定Struct搜索字段

当使用struct进行搜索时,你可以在查询条件中通过指定使用struct中的特定值来将相关的字段名或dbname传递给Where(),例如:

db.Where(&UserName: "jinzhu", "name", "Age").Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;

db.Where(&UserName: "jinzhu", "Age").Find(&users)
// SELECT * FROM users WHERE age = 0;
以上是关于GORM 基础 -- Gen的主要内容,如果未能解决你的问题,请参考以下文章

Go开源世界主流成熟ORM框架gorm实践分享

Go语言使用gorm对MySQL进行性能测试

gorm基本使用

Gorm 操作 MySQL

Grails + GORM:GORM 中默认的 equals() 实现是啥?

golang gorm 操作mysql