likes
comments
collection
share

GORM GEN的CRUD

作者站长头像
站长
· 阅读数 21

推荐有时间的同学可以将系列文章都过一遍有个印象、没有时间的同学可以通过关键词搜索找到自己需要的内容。

GORM GEN的CRUD

前面详细介绍代码生成的能力,包括直接同步数据库表生成、基于已有的model生成以及通过建表SQL生成三部分。本文将介绍如使用GEN生成的代码执行CRUD操作。

创建/插入

示例代码:github.com/go-gorm/gen…

1.1 初始化

这里可以看下前一篇代码生成,GEN推荐大家配置WithDefaultQuery模式,这样会生成全局单例变量,初始化一次其他地方可以直接使用。初始化建议调用一次,通常是直接写在init里面。

DefaultQuery模式生成的代码示例:

var (
   Q    = new(Query)
   Role *role
   User *user
)

func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
   *Q = *Use(db, opts...)
   Role = &Q.Role
   User = &Q.User
}

新建一个init.go,调用初始化逻辑SetDefault


func init() {
   mysql.Init()
   SetDefault(mysql.DB(context.Background()))
}

1.2【批量】创建

主要包括创建/选择或者忽略字段的创建/批量的创建用法

package handler

import (
   "context"

   "github.com/go-gorm/gendemo/biz/dal/model"
   "github.com/go-gorm/gendemo/biz/dal/query"
   "github.com/go-gorm/gendemo/logs"
   "gorm.io/gorm/clause"
)

// SimpleCreate 简单的创建
func SimpleCreate(ctx context.Context) {
   vd := query.User
   user := &model.User{Name: "1", Phone: "12xxx"}
   //简单创建
   err := vd.WithContext(ctx).Create(user)
   if err != nil {
      logs.CtxError(ctx, "[SimpleCreate] Create err=%s", err.Error())
      return
   }
}

// SelectCreate 选择字段创建
func SelectCreate(ctx context.Context) {
   vd := query.User
   user := &model.User{Name: "1", Phone: "12xxx"}
   //创建语句只会写shop_id 字段
   err := vd.WithContext(ctx).Select(vd.Name).Create(user)
   if err != nil {
      logs.CtxError(ctx, "[SelectCreate] Create err=%s", err.Error())
      return
   }
}

// IgnoreCreate 忽略字段创建
func IgnoreCreate(ctx context.Context) {
   vd := query.User
   user := &model.User{Name: "1", Phone: "12xxx"}
   //创建语句忽略shop_id 字段
   err := vd.WithContext(ctx).Omit(vd.Name).Create(user)
   if err != nil {
      logs.CtxError(ctx, "[IgnoreCreate] Create err=%s", err.Error())
      return
   }
}

// BatchCreate 批量创建
func BatchCreate(ctx context.Context) {
   vd := query.User
   users := []*model.User{
      {Name: "1", Phone: "11xxx"},
      {Name: "2", Phone: "12xxx"},
      {Name: "3", Phone: "13xxx"},
   }
   //批量创建
   err := vd.WithContext(ctx).Create(users...)
   if err != nil {
      logs.CtxError(ctx, "[BatchCreate] Create err=%s", err.Error())
      return
   }
   //批量创建,设置批量创建数量2,即一次创建两条
   err = vd.WithContext(ctx).CreateInBatches(users, 2)
   if err != nil {
      logs.CtxError(ctx, "[BatchCreate] Create err=%s", err.Error())
      return
   }
}

1.3 创建更新(upsert)

主要介绍如何处理,创建冲突的情况(主键和唯一键冲突);包括全量更新/部分更新以及忽略不更新

// Upsert 更新创建
func Upsert(ctx context.Context) {
   vd := query.User
   users := []*model.User{
      {Name: "1", Phone: "11xxx"},
      {Name: "2", Phone: "12xxx"},
      {Name: "3", Phone: "13xxx"},
   }
   //创建时冲突直接忽略
   err := vd.WithContext(ctx).Clauses(clause.OnConflict{DoNothing: true}).Create(users...)
   if err != nil {
      logs.CtxError(ctx, "[Upsert] Create err=%s", err.Error())
      return
   }
   //创建时冲突,更新所有字段
   err = vd.WithContext(ctx).Clauses(clause.OnConflict{UpdateAll: true}).Create(users...)
   if err != nil {
      logs.CtxError(ctx, "[Upsert] Create err=%s", err.Error())
      return
   }
   //创建时冲突,更新指定字段
   err = vd.WithContext(ctx).Clauses(clause.OnConflict{
      DoUpdates: clause.Assignments(map[string]interface{}{"shop_id": "group_id"}),
   }).Create(users...)
   if err != nil {
      logs.CtxError(ctx, "[Upsert] Create err=%s", err.Error())
      return
   }
}

更新

示例代码:github.com/go-gorm/gen…

2.1 简单更新

这部分主要介绍,如何更新单个字段,如何选择要更新的字段,以及如何更新struct的零值字段等

package handler

import (
   "context"

   "github.com/go-gorm/gendemo/biz/dal/model"
   "github.com/go-gorm/gendemo/biz/dal/query"
   "github.com/go-gorm/gendemo/logs"
)

// SimpleUpdate 简单的更新
func SimpleUpdate(ctx context.Context) {
   vd := query.User
   //只更新单个字段
   update, err := vd.WithContext(ctx).Where(vd.ID.Eq(1)).Update(vd.Name, "1")
   if err != nil {
      logs.CtxError(ctx, "[SimpleUpdate] Update err=%s", err.Error())
      return
   }
   if update.RowsAffected <= 0 {
      logs.CtxWarn(ctx, "[SimpleUpdate] Update RowsAffected=0")
   }
   //更新有限多字段
   update, err = vd.WithContext(ctx).Where(vd.ID.Eq(1)).
      UpdateSimple(vd.Name.Value("2"), vd.Extra.Zero())
   if err != nil {
      logs.CtxError(ctx, "[SimpleUpdate] Update err=%s", err.Error())
      return
   }
   if update.RowsAffected <= 0 {
      logs.CtxWarn(ctx, "[SimpleUpdate] Update RowsAffected=0")
   }

   //通过struct更新,默认是会忽略零值,也就是这里只会更新 shop_id和group_id
   user := &model.User{Name: "1", Phone: "12xxx"}
   update, err = vd.WithContext(ctx).Where(vd.ID.Eq(1)).
      Updates(user)
   if err != nil {
      logs.CtxError(ctx, "[SimpleUpdate] Update err=%s", err.Error())
      return
   }
   if update.RowsAffected <= 0 {
      logs.CtxWarn(ctx, "[SimpleUpdate] Update RowsAffected=0")
   }

   //通过struct更新,还想更新非零值,可以加sleet
   update, err = vd.WithContext(ctx).Where(vd.ID.Eq(1)).
      Select(vd.Name,vd.Extra).Updates(user)
   //或者直接更新所有,select *
   update, err = vd.WithContext(ctx).Where(vd.ID.Eq(1)).
      Select(vd.ALL).Updates(user)
   if err != nil {
      logs.CtxError(ctx, "[SimpleUpdate] Update err=%s", err.Error())
      return
   }
   if update.RowsAffected <= 0 {
      logs.CtxWarn(ctx, "[SimpleUpdate] Update RowsAffected=0")
   }

   //当然除了上面的方式,也可以用map,会更新map里的所有字段
   update, err = vd.WithContext(ctx).Where(vd.ID.Eq(1)).
      Updates(map[string]interface{}{"name": "3", "phone": 3})
   if err != nil {
      logs.CtxError(ctx, "[SimpleUpdate] Update err=%s", err.Error())
      return
   }
   if update.RowsAffected <= 0 {
      logs.CtxWarn(ctx, "[SimpleUpdate] Update RowsAffected=0")
   }
}

2.2 基于查询更新

主要适用于,本次更新的字段的值 来源于另一个查询的结果

// UpdateFromQuery 查询更新
//!!!这里只是举例如何根据查询更新,没有任何业务意义甚至看起来更新很不合理
func UpdateFromQuery(ctx context.Context) {
   vd := query.User
   co := query.Role
   update, err := vd.WithContext(ctx).Debug().Where(vd.ID.Eq(1)).
      Update(vd.Name, co.WithContext(ctx).Select(co.Name).Where(co.ID.Eq(3)))
   //UPDATE `user` SET `name`=(SELECT `role`.`name` FROM `role` WHERE `role`.`id` = 3)
   //WHERE `user`.`id` = 1
   if err != nil {
      logs.CtxError(ctx, "[UpdateFromQuery] Update err=%s", err.Error())
      return
   }
   if update.RowsAffected <= 0 {
      logs.CtxWarn(ctx, "[UpdateFromQuery] Update RowsAffected=0")
   }

   //多个字段更新
   vdt := vd.As("vdt")
   ua := vd.As("ua")
   update, err = ua.WithContext(ctx).Debug().
      UpdateFrom(vdt.WithContext(ctx).Select(vd.Name, vd.Phone).Where(vd.ID.Eq(1))).
      Where(ua.ID.Eq(2)).UpdateSimple(
      ua.Name.SetCol(vdt.Name), ua.Phone.SetCol(vdt.Phone))
   //UPDATE `user` AS `ua`,
   //(SELECT `user`.`name`,`user`.`phone` FROM `user` WHERE `user`.`id` = 1) AS `vdt`
   //SET `ua`.`name`=`vdt`.`name`,`ua`.`phone`=`vdt`.`phone` WHERE `ua`.`id` = 2
   if err != nil {
      logs.CtxError(ctx, "[UpdateFromQuery] Update err=%s", err.Error())
      return
   }
   if update.RowsAffected <= 0 {
      logs.CtxWarn(ctx, "[UpdateFromQuery] Update RowsAffected=0")
   }

}

删除

示例代码:github.com/go-gorm/gen…

删除这里没有太多的逻辑,一般就是带上条件直接执行删除就行;可能唯一想要关注的就是软删除。

package handler

import (
   "context"

   "github.com/go-gorm/gendemo/biz/dal/query"
   "github.com/go-gorm/gendemo/logs"
)

// SimpleDelete 简单的删除
func SimpleDelete(ctx context.Context) {
   vd := query.User
   //指定条件
   result, err := vd.WithContext(ctx).Where(vd.Name.Eq("1")).Delete()
   if err != nil {
      logs.CtxError(ctx, "[SimpleDelete] Delete err=%s", err.Error())
      return
   }
   if result.RowsAffected == 0 {
      //no deleted record
   }

   //如果你的结构里有软删除字段,默认逻辑都是走软删除
   //具体的可以参考 https://gorm.io/docs/delete.html#Find-soft-deleted-records
   //软删除的情况下,也是可以实现物理删除的,加上 Unscoped()
   result, err = vd.WithContext(ctx).Where(vd.Name.Eq("1")).Unscoped().Delete()
   if err != nil {
      logs.CtxError(ctx, "[SimpleDelete] Delete err=%s", err.Error())
      return
   }
}

事务

示例代码: github.com/go-gorm/gen…

事务推荐使用Transaction方法自动管理事务的开始和提交/回滚;当然也支持手动管理事务,切记手动开事务之后一定要判断err;同时开完事务一定要记得提交或者回滚。

package handler

import (
   "context"

   "github.com/go-gorm/gendemo/biz/dal/model"
   "github.com/go-gorm/gendemo/biz/dal/query"
   "github.com/go-gorm/gendemo/logs"
)

// AutoTransAction 自动事务
func AutoTransAction(ctx context.Context) {
   //通过Transaction,开事务,只要返回err就会自动回滚,没有err就会自动提交
   err := query.Q.Transaction(func(tx *query.Query) error {
      //事务里要用事务的query操作(tx)
      err := tx.Role.WithContext(ctx).Create(&model.Role{Name: "test"})
      if err != nil { //有err返回
         return err
      }
      err = tx.User.WithContext(ctx).Create(&model.User{Name: "1"})
      if err != nil { //有err返回自动回滚
         return err
      }
      return err
   })
   if err != nil {
      logs.CtxError(ctx, "[AutoTransAction] Transaction err=%s", err.Error())
   }

   //事务也支持嵌套
   err = query.Q.Transaction(func(tx *query.Query) error {
      //事务里要用事务的query操作(tx)
      cerr := tx.Role.WithContext(ctx).Create(&model.Role{Name: "test"})
      if cerr != nil { //有err返回
         return cerr
      }
      tx.Transaction(func(tx2 *query.Query) error {
         uerr := tx2.User.WithContext(ctx).Create(&model.User{Name: "1"})
         if uerr != nil { //有err返回自动回滚
            return uerr
         }
         return tx2.User.WithContext(ctx).Create(&model.User{Name: "2"})
      })

      return err
   })
   if err != nil {
      logs.CtxError(ctx, "[AutoTransAction] Transaction err=%s", err.Error())
   }
}

// ManualTransAction 手动事务
func ManualTransAction(ctx context.Context) {
   //开启事务
   tx := query.Q.Begin()
   //记得判断err
   if tx.Error != nil {
      //事务开失败了
      return
   }
   var err error
   defer func() {
      if recover() != nil || err != nil {
         //回滚事务
         _ = tx.Rollback()
      }
   }()

   err = tx.Role.WithContext(ctx).Create(&model.Role{Name: "test"})
   if err != nil {
      return
   }
   //提交事务
   err = tx.Commit()

}

查询

示例: github.com/go-gorm/gen…

5.1 字段类型和操作

查询部分主要是构建查询条件,所以这里先列举下每种dao字段类型拥有的操作。

  1. 通用字段类型(Field):IsNull/IsNotNull/Count/Eq/Neq/Gt/Gte/Lt/Lte/Like/Value/Sum/IfNull
id := field.NewField("user", "id")
anotherID := field.NewField("another", "id")
// `user`.`id` = `another`.`id`
id.EqCol(anotherID)
// user`.`id` IS NULL
id.IsNull()
  1. 整形(Int/UInt):Eq/Neq/Gt/Gte/Lt/Lte/In/NotIn/Between/NotBetween/Like/NotLike/Add/Sub/Mul/Div/Mod/FloorDiv/RightShift/LeftShift/BitXor/BitAnd/BitOr/BitFlip/Value/Zero/Sum/IfNull
// int field
f := field.NewInt("user", "id")
// `user`.`id` = 123
f.Eq(123)
// `user`.`id` DESC
f.Desc()
// `user`.`id` AS `user_id`
f.As("user_id")
// COUNT(`user`.`id`)
f.Count()
// SUM(`user`.`id`)
f.Sum()
// SUM(`user`.`id`) > 123
f.Sum().Gt(123)
// ((`user`.`id`+1)*2)/3
f.Add(1).Mul(2).Div(3),
// `user`.`id` <<< 3
f.LeftShift(3)
  1. 浮点型(Float):Eq/Neq/Gt/Gte/Lt/Lte/In/NotIn/Between/NotBetween/Like/NotLike/Add/Sub/Mul/Div/FloorDiv/Floor/Value/Zero/Sum/IfNull

  2. 字符串(String):Eq/Neq/Gt/Gte/Lt/Lte/Between/NotBetween/In/NotIn/Like/NotLike/Regexp/NotRegxp/FindInSet/FindInSetWith/Value/Zero/IfNull

name := field.NewString("user", "name")
// `user`.`name` = "modi"
name.Eq("modi")
// `user`.`name` LIKE %modi%
name.Like("%modi%")
// `user`.`name` REGEXP .*
name.Regexp(".*")
// `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
name.FindInSet("modi,jinzhu,zhangqiang")
// `uesr`.`name` CONCAT("[",name,"]")
name.Concat("[", "]")
  1. 布尔型(Bool):Not/Is/And/Or/Xor/BitXor/BitAnd/BitOr/Value/Zero
active := field.NewBool("user", "active")
// `user`.`active` = TRUE
active.Is(true)
// NOT `user`.`active`
active.Not()
// `user`.`active` AND TRUE
active.And(true)
  1. 时间类型(Time):Eq/Neq/Gt/Gte/Lt/Lte/Between/NotBetween/In/NotIn/Add/Sub/Date/DateDiff/DateFormat/Now/CurDate/CurTime/DayName/MonthName/Month/Day/Hour/Minute/Second/MicroSecond/DayOfWeek/DayOfMonth/FromDays/FromUnixtime/Value/Zero/Sum/IfNull
birth := field.NewString("user", "birth")
// `user`.`birth` = ? (now)
birth.Eq(time.Now())
// DATE_ADD(`user`.`birth`, INTERVAL ? MICROSECOND)
birth.Add(time.Duration(time.Hour).Microseconds())
// DATE_FORMAT(`user`.`birth`, "%W %M %Y")
birth.DateFormat("%W %M %Y")

5.2 单值查询

GORM Gen 提供了三个查询单值的方法,First/Take/Last,默认都会加上limit 1的限制,同时在没有查到的时候会返回ErrRecordNotFound错误;其中First还会加上主键排序,Last会加上主键倒排。

如果就不希望返回这个找不到的错误,可以选择手动加Limit(1),然后用Find/Scan查询。

// SingleValueQuery 单值查询
func SingleValueQuery(ctx context.Context) {
   u := query.User

   // 查用户1的第一条
   user, err := u.WithContext(ctx).Where(u.ID.Eq(1)).First()
   // SELECT * FROM user  WHERE `user`.`id` = 1 ORDER BY id LIMIT 1;
   if err != nil {
      logs.CtxError(ctx, "[SingleValueQuery] First err=%s", err.Error())
   }
   fmt.Println(user)

   // 查一条,走数据库的默认排序
   user, err = u.WithContext(ctx).Take()
   // SELECT * FROM users LIMIT 1;
   if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
      logs.CtxError(ctx, "[SingleValueQuery] Take err=%s", err.Error())
   }
   fmt.Println(user)

   // 查用户1的最后一条
   user, err = u.WithContext(ctx).Where(u.ID.Eq(1)).Last()
   // SELECT * FROM user  WHERE `user`.`id` = 1 ORDER BY id DESC LIMIT 1;
   if err != nil {
      logs.CtxError(ctx, "[SingleValueQuery] Last err=%s", err.Error())
   }
   fmt.Println(user)

   // 走主库查询
   user, err = u.WithContext(ctx).WriteDB().Last()

   // check error ErrRecordNotFound
   errors.Is(err, gorm.ErrRecordNotFound)

   //如果不希望返回err,可以用limit+find/scan
   users, err := u.WithContext(ctx).Where(u.ID.Eq(1)).Limit(1).Find()
   if len(users) > 0 {
      fmt.Println(users[0])
   }

   var User *model.User
   err = u.WithContext(ctx).Where(u.ID.Eq(1)).Limit(1).Scan(&User)
   fmt.Println(User)
}

5.3 简单查询

这部分主要是一些简单的查询使用,比如分页,统计、分组、JSON查询

// SimpleQuery 简单查询
func SimpleQuery(ctx context.Context) {
   u := query.User

   //单条件,用户1的数据
   users, err := u.WithContext(ctx).Where(u.ID.Eq(1)).Limit(10).Find()
   if err != nil {
      logs.CtxError(ctx, "[SimpleQuery] Find err=%s", err.Error())
   }
   fmt.Println(users)

   //统计
   total, err := u.WithContext(ctx).Where(u.ID.Eq(1)).Count()
   if err != nil {
      logs.CtxError(ctx, "[SimpleQuery] Count err=%s", err.Error())
   }
   fmt.Println(total)

   //分页查询,排序
   users, total, err = u.WithContext(ctx).Where(u.ID.Eq(1)).Order(u.ID.Desc(), u.Phone).FindByPage(0, 10)
   if err != nil {
      logs.CtxError(ctx, "[SimpleQuery] Count err=%s", err.Error())
   }
   fmt.Println(total)
   fmt.Println(users)

   //多条件AND查询 用户1
   users, total, err = u.WithContext(ctx).Where(u.Name.Eq("1")).FindByPage(0, 10)

   //OR条件查询
   // (`user`.`id` = 1 AND `user`.`name` = 1) OR (`user`.`id` = 2)
   users, total, err = u.WithContext(ctx).Where(u.ID.Eq(1), u.Name.Eq("1")).Or(u.ID.Eq(2)).FindByPage(0, 10)

   // 元组查询,多个字段的条件组合
   //查询用户1类型 以及用户2类型2的
   users, err = u.WithContext(ctx).Where(u.Columns(u.Name, u.Phone).In(field.Values([][]interface{}{{"1", "1"}, {"2", "2"}}))).Find()
   // SELECT * FROM `user` WHERE (`user.name`, `user.phone`) IN ((1,1),(2,2);

   //JSON查询
   users, err = u.WithContext(ctx).Where(gen.Cond(datatypes.JSONQuery("extra").HasKey("xx_id"))...).Find()
   // SELECT * FROM `user` WHERE JSON_EXTRACT(`attributes`,'$.user_id') IS NOT NULL;

   //选择查询的字段
   users, err = u.WithContext(ctx).Select(u.ID, u.Name).Where(u.ID.Eq(1)).Find()
   //去重
   users, err = u.WithContext(ctx).Distinct(u.ID, u.Phone).Order(u.ID, u.Name.Desc()).Find()

   //分组
   var Tuser []struct {
      Name  string
      Total int
   }
   err = u.WithContext(ctx).Select(u.ID, u.ID.Count().As("total")).Group(u.ID).Scan(&Tuser)
   //加上Having
   err = u.WithContext(ctx).Select(u.ID, u.ID.Count().As("total")).Group(u.ID).Having(u.Name.Eq("10")).Scan(&Tuser)

}

5.4 复杂查询

复杂查询将会介绍,JOIN连表、子查询、查询创建等使用方式

// ComplexQuery 复杂查询
func ComplexQuery(ctx context.Context) {
   u := query.User
   c := query.Role
   //连表查询(示例没有业务含义)
   type Result struct {
      RoleName string
      Phone    string
      ID       int64
   }

   var result Result
   //和其他表连接
   err := u.WithContext(ctx).Select(u.ID, u.Phone, c.Name.As("role_name")).LeftJoin(c, c.ID.EqCol(u.RoleID)).Scan(&result)
   // SELECT `user`.`phone`,`role`.`name` as role_name FROM `user` left join `role` on `role`.`id` = `user`.`role_id`
   if err != nil {
      logs.CtxError(ctx, "[ComplexQuery] LeftJoin Scan err=%s", err.Error())
   }
   fmt.Println(result)

   // 同一个表做连接
   var result2 Result
   u2 := u.As("u2")
   err = u.WithContext(ctx).Select(u.ID, u2.Phone).LeftJoin(u2, u2.ID.EqCol(u.ID)).Scan(&result2)
   // SELECT users.id, u2.phone FROM `user` left join `user` u2 on u2.id = user.id

   //子查询的连接
   var result3 Result
   c2 := c.As("c2")
   err = u.WithContext(ctx).Select(u.ID, c2.Name).LeftJoin(c.WithContext(ctx).Select(c.ID, c.Name).Where(c.ID.Gt(100)).As("c2"), c2.ID.EqCol(u.RoleID)).Scan(&result3)
   // SELECT `user`.`id`,c2.`name` FROM `user` left join (select id,name from role  where id > 100) as c2 on c2.id = `user`.`role_id`

}

//子查询
o := query.Order
u := query.User

orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")

// Select users with orders between 100 and 200
subQuery1 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(100))
subQuery2 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(200))
u.WithContext(ctx).Exists(subQuery1).Not(u.WithContext(ctx).Exists(subQuery2)).Find()
// SELECT * FROM `users` WHERE EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id`= `users`.`id` AND `orders`.`amount` > 100 AND `orders`.`deleted_at` IS NULL) AND NOT EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 200 AND `orders`.`deleted_at` IS NULL) AND `users`.`deleted_at` IS NULL


//查询不存在赋值(不会创建)
// User not found, initialize it with given conditions and Attrs
u.WithContext(ctx).Attrs(field.Attrs(&model.User{Age: 20})).Where(u.Name.Eq("non_existing")).FirstOrInit()
// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// user -> User{Name: "non_existing", Age: 20}

// User not found, initialize it with given conditions and Attrs
u.WithContext(ctx).Attrs(u.Age.Value(20).Where(u.Name.Eq("non_existing")).FirstOrInit()
// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// user -> User{Name: "non_existing", Age: 20}

// Found user with `name` = `gen`, attributes will be ignored
u.WithContext(ctx).Attrs(field.Attrs(&model.User{Age: 20})).Where(u.Name.Eq("gen")).FirstOrInit()
// SELECT * FROM USERS WHERE name = 'gen' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "gen", Age: 18}

//查询不存在则创建
// User not found, initialize it with give conditions and Assign attributes
u.WithContext(ctx).Assign(field.Attrs(&model.User{Age: 20})).Where(u.Name.Eq("non_existing")).FirstOrCreate()
// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// INSERT INTO "users" (name, age) VALUES ("non_existing", 20);
// user -> User{ID: 112, Name: "non_existing", Age: 20}

// Found user with `name` = `gen`, update it with Assign attributes
u.WithContext(ctx).Assign(field.Attrs(&model.User{Age: 20})).Where(u.Name.Eq("gen")).FirstOrCreate()
// SELECT * FROM users WHERE name = 'gen' ORDER BY id LIMIT 1;
// UPDATE users SET age=20 WHERE id = 111;
// user -> User{ID: 111, Name: "gen", Age: 20}

// Found user with `name` = `gen`, update it with Assign attributes
u.WithContext(ctx).Assign(u.Age.Value(20)).Where(u.Name.Eq("gen")).FirstOrCreate()
// SELECT * FROM users WHERE name = 'gen' ORDER BY id LIMIT 1;
// UPDATE users SET age=20 WHERE id = 111;
// user -> User{ID: 111, Name: "gen", Age: 20}
}