1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/gorm-gen

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
README.ZH_CN.md 49 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 30.11.2024 09:41 00901a8

GORM/GEN

На основе GORM, более безопасный и удобный инструмент ORM.

Обзор

  • Автоматическое создание CRUD и DIY методов.
  • Автоматическая генерация модели на основе структуры таблицы.
  • Полная совместимость с GORM.
  • Более безопасный и удобный.
  • Различные режимы генерации кода.

Содержание

  • GORM/GEN.
    • Обзор.
    • Содержание.
    • Установка.
    • Быстрый старт.
      • Путь к проекту.
    • Примеры API.
      • Генерация.
        • Создание модели.
        • Тип отображения.
      • Выражение поля.
        • Создание поля.
      • Интерфейс CRUD.
        • Создание.
          • Создание записи.
          • Выбор поля для создания.
          • Пакетное создание.
        • Запрос.
          • Запрос одиночных данных.
          • Запрос данных по первичному ключу.
          • Запрос всех данных.
          • Условия.
            • Базовый запрос.
            • Not.
            • Or.
            • Group.
            • Указание поля запроса.
            • Кортеж запроса.
            • JSON-запрос.
            • Order.
            • Limit & Offset.
            • Group By & Having.
            • Distinct.
            • Joins.
          • Подзапрос.
            • From подзапрос.
            • Обновление подзапроса.
            • Многопольное обновление подзапроса.
          • Транзакция.
            • Вложенная транзакция.
            • Ручная транзакция.
            • Точка сохранения.
          • Расширенный запрос.
            • Итерация.
            • Пакетный запрос.
            • Pluck.
            • Scopes.
            • Count.
            • FirstOrInit.
            • FirstOrCreate.
        • Связь.
          • Relation.
            • Связывание существующей модели.
            • Связывание с таблицей базы данных.
            • Конфигурация связи.
          • Операция.
            • Пропуск автоматического создания связи.
            • Запрос связи.
            • Добавление связи.
            • Замена связи.
            • Удаление связи.
            • Очистка связи.
            • Статистика связи.
            • Удаление указанной связи.
          • Предварительная загрузка.
            • Preload.
            • Preload All.
            • Предварительная загрузка по условию.
            • Скрытая предварительная загрузка.
        • Обновление.
          • Обновление одного поля.
          • Обновление нескольких полей.
          • Выбор обновляемых полей.
        • Удаление.
          • Удаление записи.
          • Удаление по первичному ключу.
          • Пакетное удаление.
          • Мягкое удаление.
          • Запрос записей с мягким удалением.
          • Постоянное удаление.
      • DIY методы.
        • Определение интерфейса.
          • Синтаксис.
            • Заполнитель.
            • Шаблон.
            • If clause.
            • Where clause.
            • Set clause.
          • Пример интерфейса метода.
        • Интеллектуальный выбор поля.
      • Продвинутое руководство. ## Установка

Для установки GEN необходимо сначала установить GO и настроить рабочую среду.

  1. После установки Go версии 1.14 или выше, выполните следующую команду для установки gen:
go get -u gorm.io/gen
  1. Импортируйте в свой проект:
import "gorm.io/gen"

Быстрый старт

Внимание: все учебные пособия здесь написаны в режиме WithContext. Если вы используете режим WithoutContext, то можете удалить все WithContext(ctx), чтобы код выглядел более лаконично.

# предположим, что следующий код находится в файле generate.go
$ cat generate.go
package main

import "gorm.io/gen"

// генерируем код
func main() {
    // указываем выходной каталог (по умолчанию: "./query")
    /* если вы хотите выполнять запросы без ограничений контекста, установите режим gen.WithoutContext */
    g := gen.NewGenerator(gen.Config{
        OutPath: "../dal/query",
        /* Mode: gen.WithoutContext|gen.WithDefaultQuery*/
        // если вы хотите, чтобы свойство генерации поля с нулевым значением было типом указателя, установите FieldNullable true
        /* FieldNullable: true,*/
        // если вы хотите генерировать теги индекса из базы данных, установите FieldWithIndexTag true
        /* FieldWithIndexTag: true,*/
        // если вы хотите генерировать теги типа из базы данных, установите FieldWithTypeTag true
        /* FieldWithTypeTag: true,*/
        // если вам нужны модульные тесты для кода запроса, установите WithUnitTest true
        /* WithUnitTest: true, */
    })
  
    // повторно используйте соединение с базой данных в проекте или создайте соединение здесь
    // если вы хотите использовать GenerateModel/GenerateModelAs, UseDB является обязательным, иначе произойдет паника
    // db, _ := gorm.Open(mysql.Open("root:@(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"))
    g.UseDB(db)
  
    // применяем базовые API CRUD к структурам или табличным моделям, которые указаны по имени таблицы с помощью функции
    // GenerateModel/GenerateModelAs. Генератор сгенерирует код моделей таблиц при вызове Excute.
    g.ApplyBasic(model.User{}, g.GenerateModel("company"), g.GenerateModelAs("people", "Person", gen.FieldIgnore("address")))
    
    // применяем DIY интерфейсы к структурам или табличным моделям
    g.ApplyInterface(func(method model.Method) {}, model.User{}, g.GenerateModel("company"))

    // выполняем действие генерации кода
    g.Execute()
}

Генерация модели:

  • gen.WithoutContext — режим без WithContext генерирует
  • gen.WithDefaultQuery — генерирует глобальную переменную запроса по умолчанию

Структура проекта

Рекомендуемая структура проекта:

demo
├── cmd
│   └── generate
│       └── generate.go # выполнить его, чтобы сгенерировать коды
├── dal
│   ├── dal.go # создать здесь соединения с сервером базы данных
│   ├── model
│   │   ├── method.go # DIY интерфейсы методов
│   │   └── model.go  # сохранить структуру, соответствующую таблице базы данных
│   └── query  # каталог сгенерированных кодов
|       ├── user.gen.go # сгенерированный код для пользователя
│       └── gen.go # сгенерированный код
├── biz
│   └── query.go # вызвать функцию в dal/gorm_generated.go и запросить базы данных
├── config
│   └── config.go # DSN для сервера базы данных
├── generate.sh # оболочка для выполнения cmd/generate
├── go.mod
├── go.sum
└── main.go

Примеры API

Генерация

Генерация модели

// сгенерировать модель структуры, сопоставленную с таблицей `people` в базе данных
g.GenerateModel("people")

// сгенерировать структуру и указать имя структуры
g.GenerateModelAs("people", "People")

// добавить опцию игнорирования поля
g.GenerateModel("people", gen.FieldIgnore("адрес"), gen.FieldType("id", "int64"))

// сгенерировать все таблицы, например: g.ApplyBasic(g.GenerateAllTable()...)
g.GenerateAllTable()

Опции генерации полей:

  • FieldNew — создать новое поле;
  • FieldIgnore — игнорировать поле;
  • FieldIgnoreReg — игнорировать поле (совпадение с регулярным выражением);
  • FieldRename — переименовать поле в структуре;
  • FieldType — указать тип поля;
  • FieldTypeReg — указать тип поля (совпадение с регулярным выражением);
  • FieldTag — указать тег gorm и json;
  • FieldJSONTag — указать тег json;
  • FieldGORMTag — указать тег gorm;
  • FieldNewTag — добавить новый тег;
  • FieldNewTagWithNS — указать новый тег со стратегией именования;
  • FieldTrimPrefix — обрезать префикс столбца;
  • FieldTrimSuffix — обрезать суффикс столбца;
  • FieldAddPrefix — добавить префикс к имени члена структуры;
  • FieldAddSuffix — добавить суффикс к имени члена структуры. Тип сопоставления

Сопоставление пользовательских типов полей в базе данных и типов Go.

dataMap := map[string]func(detailType string) (dataType string){
  "int": func(detailType string) (dataType string) { return "int64" },
  // bool mapping
  "tinyint": func(detailType string) (dataType string) {
    if strings.HasPrefix(detailType, "tinyint(1)") {
      return "bool"
    }
    return "int8"
  },
}

g.WithDataTypeMap(dataMap)

Выражение поля

Фактически вам нужно вручную создавать поля, потому что все они будут автоматически созданы при генерации кода.

Тип поля Детальный тип Функция создания Поддерживаемый метод запроса
generic field NewField IsNull/IsNotNull/Count/Eq/Neq/Gt/Gte/Lt/Lte/Like
int int/int8/.../int64 NewInt/NewInt8/.../NewInt64 Eq/Neq/Gt/Gte/Lt/Lte/In/NotIn/Between/NotBetween/Like/NotLike/Add/Sub/Mul/Div/Mod/FloorDiv/RightShift/LeftShift/BitXor/BitAnd/BitOr/BitFlip
uint uint/uint8/.../uint64 NewUint/NewUint8/.../NewUint64 То же, что и int
float float32/float64 NewFloat32/NewFloat64 Eq/Neq/Gt/Gte/Lt/Lte/In/NotIn/Between/NotBetween/Like/NotLike/Add/Sub/Mul/Div/FloorDiv
string string/[]byte NewString/NewBytes Eq/Neq/Gt/Gte/Lt/Lte/Between/NotBetween/In(val/NotIn(val/Like/NotLike/Regexp/NotRegxp/FindInSet/FindInSetWith
bool bool NewBool Not/Is/And/Or/Xor/BitXor/BitAnd/BitOr
time time.Time NewTime Eq/Neq/Gt/Gte/Lt/Lte/Between/NotBetween/In/NotIn/Add/Sub

Пример создания поля:

import "gorm.io/gen/field"

// создать новую общую карту полей для `generic_a`
a := field.NewField("table_name", "generic_a")

// создать карту поля для `id`
i := field.NewInt("user", "id")

// создать карту поля для `address`
s := field.NewString("user", "address")

// создать карту поля для `create_time`
t := field.NewTime("user", "create_time")

Интерфейс CRUD

Генерация базовой модели user и DB.

// сгенерированный код
// сгенерированный код
// сгенерированный код
package query

import "gorm.io/gen"

// struct map to table `users` 
type user struct {
    gen.DO
    ID       field.Uint
    Name     field.String
    Age      field.Int
    Address  field.Field
    Birthday field.Time
}

// struct collection
type DB struct {
    db       *gorm.DB
    User     *user
}

Создание

Создание записи
// u refer to query.user
user := model.User{Name: "Modi", Age: 18, Birthday: time.Now()}

u := query.Use(db).User
err := u.WithContext(ctx).Create(&user) // передать указатель данных в Create

err // возвращает ошибку
Выбор поля создания

Пользовательские поля, которые необходимо вставить.

u := query.Use(db).User
u.WithContext(ctx).Select(u.Name, u.Age).Create(&user)
// INSERT INTO `users` (`name`,`age`) VALUES ("modi", 18)

Поля, которые нужно игнорировать при создании.

u := query.Use(db).User
u.WithContext(ctx).Omit(u.Name, u.Age).Create(&user)
// INSERT INTO `users` (`Address`, `Birthday`) VALUES ("2021-08-17 20:54:12.000", 18)
Пакетное создание

Метод Create поддерживает пакетное создание, и параметр должен быть только срезом соответствующей модели. GORM будет эффективно создавать и возвращать все первичные ключи, присвоенные значениям модели в срезе.

var users = []model.User{{Name: "modi"}, {Name: "zhangqiang"}, {Name: "songyuan"}}
query.Use(db).User.WithContext(ctx).Create(&users)

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

CreateInBatches можно указать размер пакета, например:

var users = []User{{Name: "modi_1"}, ...., {Name: "modi_10000"}}

// batch size 100
query.Use(db).User.WithContext(ctx).CreateInBatches(users, 100)

Также можно использовать глобальную конфигурацию, чтобы установить CreateBatchSize в gorm.Config / gorm.Session при инициализации gorm.

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    CreateBatchSize: 1000,
})
// ИЛИ
db = db.Session(&gorm.Session{CreateBatchSize: 1000})

u := query.NewUser(db)

var users = []User{{Name:
``` **JSON-запрос**

u := query.Use(db).User

users, err := u.WithContext(ctx).Where(gen.Cond(datatypes.JSONQuery("attributes").HasKey("role"))...).Find() // SELECT * FROM users WHERE JSON_EXTRACT(attributes,'$.role') IS NOT NULL;


**Order**

Указывает способ сортировки запроса.

```go
u := query.Use(db).User

users, err := u.WithContext(ctx).Order(u.Age.Desc(), u.Name).Find()
// SELECT * FROM users ORDER BY age DESC, name;

// Multiple orders
users, err := u.WithContext(ctx).Order(u.Age.Desc()).Order(u.Name).Find()
// SELECT * FROM users ORDER BY age DESC, name;

Limit & Offset

Используются для постраничного вывода данных. Limit ограничивает количество возвращаемых строк, а Offset указывает на смещение в наборе результатов.

u := query.Use(db).User

urers, err := u.WithContext(ctx).Limit(3).Find()
// SELECT * FROM users LIMIT 3;

// Cancel limit condition with -1
users, err := u.WithContext(ctx).Limit(10).Limit(-1).Find()
// SELECT * FROM users;

users, err := u.WithContext(ctx).Offset(3).Find()
// SELECT * FROM users OFFSET 3;

users, err := u.WithContext(ctx).Limit(10).Offset(5).Find()
// SELECT * FROM users OFFSET 5 LIMIT 10;

// Cancel offset condition with -1
users, err := u.WithContext(ctx).Offset(10).Offset(-1).Find()
// SELECT * FROM users;

Group By & Having

Группирует результаты по указанному полю и фильтрует группы с помощью условия Having.

u := query.Use(db).User

type Result struct {
    Date  time.Time
    Total int
}

var result Result

err := u.WithContext(ctx).Select(u.Name, u.Age.Sum().As("total")).Where(u.Name.Like("%modi%")).Group(u.Name).Scan(&result)
// SELECT name, sum(age) as total FROM `users` WHERE name LIKE "%modi%" GROUP BY `name`

err := u.WithContext(ctx).Select(u.Name, u.Age.Sum().As("total")).Group(u.Name).Having(u.Name.Eq("group")).Scan(&result)
// SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"

rows, err := u.WithContext(ctx).Select(u.Birthday.As("date"), u.Age.Sum().As("total")).Group(u.Birthday).Rows()
for rows.Next() {
  ...
}

o := query.Use(db).Order

rows, err := o.WithContext(ctx).Select(o.CreateAt.Date().As("date"), o.Amount.Sum().As("total")).Group(o.CreateAt.Date()).Having(u.Amount.Sum().Gt(100)).Rows()
for rows.Next() {
  ...
}

var results []Result

o.WithContext(ctx).Select(o.CreateAt.Date().As("date"), o.WithContext(ctx).Amount.Sum().As("total")).Group(o.CreateAt.Date()).Having(u.Amount.Sum().Gt(100)).Scan(&results)

Distinct

Возвращает только уникальные значения указанного поля.

u := query.Use(db).User

users, err := u.WithContext(ctx).Distinct(u.Name, u.Age).Order(u.Name, u.Age.Desc()).Find()

Distinct работает с Pluck и Count.

Joins

Объединение таблиц. Join выполняет внутреннее соединение, также есть LeftJoin и RightJoin.

u := query.Use(db).User
e := query.Use(db).Email
c := query.Use(db).CreditCard

type Result struct {
    Name  string
    Email string
}

var result Result

err := u.WithContext(ctx).Select(u.Name, e.Email).LeftJoin(e, e.UserID.EqCol(u.ID)).Scan(&result)
// SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id

rows, err := u.WithContext(ctx).Select(u.Name, e.Email).LeftJoin(e, e.UserID.EqCol(u.ID)).Rows()
for rows.Next() {
  ...
}

var results []Result

err := u.WithContext(ctx).Select(u.Name, e.Email).LeftJoin(e, e.UserID.EqCol(u.ID)).Scan(&results)

// multiple joins with parameter
users := u.WithContext(ctx).Join(e, e.UserID.EqCol(u.id), e.Email.Eq("modi@example.org")).Join(c, c.UserID.EqCol(u.ID)).Where(c.Number.Eq("411111111111")).Find()

Подзапросы

Позволяют использовать результаты одного запроса в другом.

o := query.Use(db).Order
u := query.Use(db).User

orders, err := o.WithContext(ctx).Where(u.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.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%")

From подзапросы

Подзапросы, созданные с помощью метода Table, могут быть использованы в предложении From.

u := query.Use(db).User
p := query.Use(db).Pet

users, err :=
``` **Обновление подзапроса**

Запрос с использованием подзапросов для обновления полей таблицы:

gen.Table(u.WithContext(ctx).Select(u.Name, u.Age).As("u")).Where(u.Age.Eq(18)).Find() // SELECT * FROM (SELECT name,age FROM users) as u WHERE age = 18

subQuery1 := u.WithContext(ctx).Select(u.Name) subQuery2 := p.WithContext(ctx).Select(p.Name) users, err := gen.Table(subQuery1.As("u"), subQuery2.As("p")).Find() db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{}) // SELECT * FROM (SELECT name FROM users) as u, (SELECT name FROM pets) as p


**Обновление подзапроса**

Пример обновления поля таблицы с помощью подзапроса:

u := query.Use(db).User c := query.Use(db).Company

u.WithContext(ctx).Update(u.CompanyName, c.Select(c.Name).Where(c.ID.EqCol(u.CompanyID))) // UPDATE "users" SET "company_name" = (SELECT name FROM companies WHERE companies.id = users.company_id);

u.WithContext(ctx).Where(u.Name.Eq("modi")).Update(u.CompanyName, c.Select(c.Name).Where(c.ID.EqCol(u.CompanyID)))


**Многопольное обновление подзапроса**

Пример многопольного обновления с использованием MySQL и подзапроса:

u := query.Use(db).User c := query.Use(db).Company

ua := u.As("u") ca := u.As("c")

ua.WithContext(ctx).UpdateFrom(ca.WithContext(ctx).Select(c.ID, c.Address, c.Phone).Where(c.ID.Gt(100))). Where(ua.CompanyID.EqCol(ca.ID)). UpdateSimple( ua.Address.SetCol(ca.Address), ua.Phone.SetCol(ca.Phone), ) // UPDATE users AS u,( // SELECT company.id,company.address,company.phone // FROM company WHERE company.id > 100 AND company.deleted_at IS NULL // ) AS c // SET u.address=c.address,c.phone=c.phone,updated_at='2021-11-11 11:11:11.111' // WHERE u.company_id = c.id


**Транзакции**

Примеры использования транзакций в запросах:

q := query.Use(db)

q.Transaction(func(tx *query.Query) error { if _, err := tx.User.WithContext(ctx).Where(tx.User.ID.Eq(100)).Delete(); err != nil { return err } if _, err := tx.Article.WithContext(ctx).Create(&model.User{Name:"modi"}); err != nil { return err } return nil })


**Вложенные транзакции**

GEN поддерживает вложенные транзакции:

q := query.Use(db)

q.Transaction(func(tx *query.Query) error { tx.User.WithContext(ctx).Create(&user1)

tx.Transaction(func(tx2 *query.Query) error { tx2.User.WithContext(ctx).Create(&user2) return errors.New("rollback user2") // Rollback user2 })

tx.Transaction(func(tx2 *query.Query) error { tx2.User.WithContext(ctx).Create(&user3) return nil })

return nil })

// Commit user1, user3


**Ручные транзакции**

Пример ручной транзакции:

q := query.Use(db)

// begin a transaction tx := q.Begin()

// do some database operations in the transaction (use 'tx' from this point, not 'db') tx.User.WithContext(ctx).Create(...)

// ...

// rollback the transaction in case of error tx.Rollback()

// Or commit the transaction tx.Commit()


Например:

q := query.Use(db)

func doSomething(ctx context.Context, users ...*model.User) (err error) { tx := q.Begin() defer func() { if recover() != nil || err != nil { _ = tx.Rollback() } }()

err = tx.User.WithContext(ctx).Create(users...)
if err != nil {
    return
}
return tx.Commit()

}


**Сохранение точки**

`SavePoint`, `RollbackTo` можно использовать для сохранения или отката транзакции до определённой точки:

tx := q.Begin() txCtx = tx.WithContext(ctx)

txCtx.User.Create(&user1)

tx.SavePoint("sp1") txCtx.Create(&user2) tx.RollbackTo("sp1") // Rollback user2

tx.Commit() // Commit user1


**Расширенный запрос**

**Итерация**

GEN позволяет перебирать значения с помощью Row:

u := query.Use(db).User do := u.WithContext(ctx) rows, err := do.Where(u.Name.Eq("modi")).Rows() defer rows.Close()

for rows.Next() { var user User // ScanRows is a method of gorm.DB, it can be used to scan a row into a struct do.ScanRows(rows, &user)

// do something

}


**Пакетный запрос**

u := query.Use(db).User

// batch size 100 err := u.WithContext(ctx).Where(u.ID.Gt(9)).FindInBatches(&results, 100, func(tx gen.Dao, batch int) error { for _, result := range results { // batch processing found records }

// build a new `u` to use it's api
// queryUsery := query.NewUser(tx.UnderlyingDB())

tx.Save(&results)

batch // Batch 1, 2, 3

// returns error will stop future

Извлекает отдельный столбец из базы данных и сканирует его в срез или базовый тип.

```go
u := query.Use(db).User

var ages []int64
u.WithContext(ctx).Pluck(u.Age, &ages)

var names []string
u.WithContext(ctx).Pluck(u.Name, &names)

// Distinct Pluck
u.WithContext(ctx).Distinct().Pluck(u.Name, &names) // SELECT DISTINCT `name` FROM `users`

// Запрос более одного столбца, используйте Scan или Find следующим образом:
db.WithContext(ctx).Select(u.Name, u.Age).Scan(&users)
users, err := db.Select(u.Name, u.Age).Find()

Scopes

Позволяет объявлять некоторые часто используемые или общие условия, а затем запрашивать их с помощью Scopes.

o := query.Use(db).Order

func AmountGreaterThan1000(tx gen.Dao) gen.Dao {
    return tx.Where(o.Amount.Gt(1000))
}

func PaidWithCreditCard(tx gen.Dao) gen.Dao {
    return tx.Where(o.PayModeSign.Eq("C"))
}

func PaidWithCod(tx gen.Dao) gen.Dao {
    return tx.Where(o.PayModeSign.Eq("C"))
}

func OrderStatus(status []string) func (tx gen.Dao) gen.Dao {
    return func (tx gen.Dao) gen.Dao {
      return tx.Where(o.Status.In(status...))
    }
}

orders, err := o.WithContext(ctx).Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find() // Найти все заказы по кредитной карте и сумму больше 1000

orders, err := o.WithContext(ctx).Scopes(AmountGreaterThan1000, PaidWithCod).Find()  // Найти все заказы COD и сумму больше 1000

orders, err := o.WithContext(ctx).Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find()   // Найти все оплаченные и отправленные заказы, сумма которых больше 1000

Count

Подсчитывает количество записей, соответствующих определённым условиям.

u := query.Use(db).User

count, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).Or(u.Name.Eq("zhangqiang")).Count() // SELECT count(1) FROM users WHERE name = 'modi' OR name = 'zhangqiang'

count, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).Count()  // SELECT count(1) FROM users WHERE name = 'modi'; (count)

// Count with Distinct
u.WithContext(ctx).Distinct(u.Name).Count() // SELECT COUNT(DISTINCT(`name`)) FROM `users`

FirstOrInit

Получает соответствующую первую запись или инициализирует экземпляр с заданными условиями.

u := query.Use(db).User

// Пользователь не найден, инициализируем его с заданными условиями
user, err := u.WithContext(ctx).Where(u.Name.Eq("non_existing")).FirstOrInit() // user -> User{Name: "non_existing"}

// Находим пользователя с именем modi
user, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).FirstOrInit()  // user -> User{ID: 1, Name: "modi", Age: 17}

Если вы хотите, чтобы инициализированный экземпляр содержал некоторые атрибуты, не связанные с запросом, вы можете указать их с помощью Attrs.

u := query.Use(db).User

// Пользователь не найден, инициализировать его с заданными условиями и Attrs
user, err := u.WithContext(ctx).Where(u.Name.Eq("non_existing")).Attrs(u.Age.Value(20)).FirstOrInit() // SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// user -> User{Name: "non_existing", Age: 20}

// Пользователь не найден, инициализируйте его с заданными условиями и атрибутами Attrs
user, err := u.WithContext(ctx).Where(u.Name.Eq("non_existing")).Attrs(u.Age.Value(20)).FirstOrInit() // SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// пользователь -> User{Имя: «non_existing», возраст: 20}

// Найден пользователь с именем modi, атрибуты будут проигнорированы
user, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).Attrs(u.Age.Value(20)).FirstOrInit() // SELECT * FROM USERS WHERE name = modi' ORDER BY id LIMIT 1;
// пользователь -> User{ID: 1, Имя: «modi», возраст: 17}

Assign — это атрибут, который используется для замены существующих атрибутов независимо от того, найдена ли запись.

// Пользователь не найден, инициализировать его с заданными условиями и Assign атрибутами
user, err := u.WithContext(ctx).Where(u.Name.Eq("non_existing")).Assign(u.Age.Value(20)).FirstOrInit() // пользователь -> User{Имя: «non_existing», возраст: 20}

// Нашли пользователя с именем modi, обновляем его с помощью Assign атрибутов
user, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).Assign(u.Age.Value(20)).FirstOrInit() // SELECT * FROM USERS WHERE name = modi' ORDER BY id LIMIT 1;
// пользователь -> User{ID: 111, имя: «modi», возраст: 20}

FirstOrCreate

Находит соответствующую первую запись или создаёт новую запись с заданными условиями.

u := query.Use(db).User

// Пользователь не найден, создаём новую запись с заданными условиями
user, err := u.WithContext(ctx).Where(u.Name.Eq("non_existing")).FirstOrCreate() // INSERT INTO "users" (name) VALUES ("non_existing");
// пользователь -> **Текст запроса написан на языке Go.**

Перевод текста:

Пользователь{ID: 112, Name: "non_existing"}

// Найден пользователь с `name` = `modi`
user, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).FirstOrCreate()
// user -> User{ID: 111, Name: "modi", "Age": 18}

Если вы хотите создать экземпляр, который содержит некоторые атрибуты, не являющиеся условиями запроса, вы можете указать их с помощью Attrs.

```go
u := query.Use(db).User

// Пользователь не найден, создаём его с заданными условиями и атрибутами
user, err := u.WithContext(ctx).Where(u.Name.Eq("non_existing")).Attrs(u.Age.Value(20)).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}

// Найден пользователь с `name` = `modi`, атрибуты будут проигнорированы
user, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).Attrs(u.Age.Value(20)).FirstOrCreate()
// SELECT * FROM users WHERE name = 'modi' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "modi", Age: 18}

Assign используется для того, чтобы независимо от того, найдена запись или нет, использовать указанные атрибуты для замены и сохранения в базе данных.

u := query.Use(db).User

// Пользователь не найден, инициализируем его с заданными условиями и присваиваем атрибуты
user, err := u.WithContext(ctx).Where(u.Name.Eq("non_existing")).Assign(u.Age.Value(20)).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}

// Найден пользователь с `name` = `modi`, обновляем его с помощью Assign атрибутов
user, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).Assign(u.Age.Value(20)).FirstOrCreate(&user)
// SELECT * FROM users WHERE name = 'modi' ORDER BY id LIMIT 1;
// UPDATE users SET age=20 WHERE id = 111;
// user -> User{ID: 111, Name: "modi", Age: 20}

Связь

GEN автоматически сохраняет связи (BelongsTo/HasOne/HasMany/Many2Many), как GORM.

Relation

Существует 4 вида отношений.

const (
    HasOne    RelationshipType = RelationshipType(schema.HasOne)    // HasOneRel имеет одно отношение
    HasMany   RelationshipType = RelationshipType(schema.HasMany)   // HasManyRel имеет много отношений
    BelongsTo RelationshipType = RelationshipType(schema.BelongsTo) // BelongsToRel относится к отношениям
    Many2Many RelationshipType = RelationshipType(schema.Many2Many) // Many2ManyRel — отношения «многие ко многим»
)
Связь с уже существующей моделью
package model

// существующая модель
type Customer struct {
    gorm.Model
    CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer"`
}

type CreditCard struct {
    gorm.Model
    Number        string
    CustomerRefer uint
}

GEN проверяет и преобразует эти связанные отношения:

// указываем модель
g.ApplyBasic(model.Customer{}, model.CreditCard{})

// ассоциации будут обнаружены и преобразованы в код
package query

type customer struct {
    ...
    CreditCards customerHasManyCreditCards
}

type creditCard struct{
    ...
}
И связь с таблицами базы данных

Необходимо использовать gen.FieldRelate.

card := g.GenerateModel("credit_cards")
customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", b, 
    &field.RelateConfig{
        // RelateSlice: true,
        GORMTag: "foreignKey:CustomerRefer",
    }),
)

g.ApplyBasic(card, custormer)

GEN генерирует объявленные связанные свойства:

// customers
type Customer struct {
    ID          int64          `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    CreatedAt   time.Time      `gorm:"column:created_at;type:datetime(3)" json:"created_at"`
    UpdatedAt   time.Time      `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"`
    DeletedAt   gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"`
    CreditCards []CreditCard   `gorm:"foreignKey:CustomerRefer" json:"credit_cards"`
}


// credit_cards
type CreditCard struct {
    ID            int64          `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    CreatedAt     time.Time      `gorm:"column:created_at;type:datetime(3)" json:"created_at"`
    UpdatedAt     time.Time      `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"`
    DeletedAt     gorm.DeletedAt `gorm:"column:deleted_at;text:deleted_at" json:"deleted_at"`
    CustomerRefer int64

*Примечание: в тексте запроса присутствуют фрагменты кода, которые не удалось перевести. Это связано с тем, что они содержат синтаксические конструкции, специфичные для языка программирования Go, а также имена переменных и функций, которые невозможно однозначно интерпретировать без контекста.* ```
gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"
}

Если это уже существующая связанная модель, то можно использовать gen.FieldRelateModel для объявления.

customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
    &field.RelateConfig{
        // RelateSlice: true,
        GORMTag: "foreignKey:CustomerRefer",
    }),
)

g.ApplyBasic(custormer)

Связанная конфигурация

type RelateConfig struct {
    // specify field's type
    RelatePointer      bool // ex: CreditCard  *CreditCard
    RelateSlice        bool // ex: CreditCards []CreditCard
    RelateSlicePointer bool // ex: CreditCards []*CreditCard

    JSONTag      string // related field's JSON tag
    GORMTag      string // related field's GORM tag
    NewTag       string // related field's new tag
    OverwriteTag string // related field's tag
}

Операции

Пропуск автоматического создания связей

user := model.User{
  Name:            "modi",
  BillingAddress:  Address{Address1: "Billing Address - Address 1"},
  ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
  Emails:          []Email{
    {Email: "modi@example.com"},
    {Email: "modi-2@example.com"},
  },
  Languages:       []Language{
    {Name: "ZH"},
    {Name: "EN"},
  },
}

u := query.Use(db).User

u.WithContext(ctx).Select(u.Name).Create(&user)
// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

u.WithContext(ctx).Omit(u.BillingAddress.Field()).Create(&user)
// Skip create BillingAddress when creating a user

u.WithContext(ctx).Omit(u.BillingAddress.Field("Address1")).Create(&user)
// Skip create BillingAddress.Address1 when creating a user

u.WithContext(ctx).Omit(field.AssociationFields).Create(&user)
// Skip all associations when creating a user

Метод Field объединяет серьёзное имя поля с «», например: u.BillingAddress.Field("Address1", "Street") равно BillingAddress.Address1.Street.

Запрос связанных данных

u := query.Use(db).User

languages, err = u.Languages.Model(&user).Find()

Запрос связанных данных по определённому условию.

q := query.Use(db)
u := q.User

languages, err = u.Languages.Where(q.Language.Name.In([]string{"ZH","EN"})).Model(&user).Find()

Добавление связанных данных

u := query.Use(db).User

u.Languages.Model(&user).Append(&languageZH, &languageEN)

u.Languages.Model(&user).Append(&Language{Name: "DE"})

u.CreditCards.Model(&user).Append(&CreditCard{Number: "411111111111"})

Замена связанных данных

u.Languages.Model(&user).Replace(&languageZH, &languageEN)

Удаление связанных данных

Удаление существующих связанных данных, не удаляет данные.

u := query.Use(db).User

u.Languages.Model(&user).Delete(&languageZH, &languageEN)

u.Languages.Model(&user).Delete([]*Language{&languageZH, &languageEN}...)

Очистка связанных данных

Очистка всех связанных данных, не удаляет данные.

u.Languages.Model(&user).Clear()

Подсчёт связанных данных

u.Languages.Model(&user).Count()

Удаление определённых связанных данных

Удаление определённых данных и связанных данных:

u := query.Use(db).User

// delete user's account when deleting user
u.Select(u.Account).Delete(&user)

// delete user's Orders, CreditCards relations when deleting user
db.Select(u.Orders.Field(), u.CreditCards.Field()).Delete(&user)

// delete user's has one/many/many2many relations when deleting user
db.Select(field.AssociationsFields).Delete(&user)

Предварительная загрузка

Preload

GEN поддерживает загрузку связанных данных через Preload:

type User struct {
  gorm.Model
  Username string
  Orders   []Order
}

type Order struct {
  gorm.Model
  UserID uint
  Price  float64
}

q := query.Use(db)
u := q.User
o := q.Order

// Preload Orders when find users
users, err := u.WithContext(ctx).Preload(u.Orders).Find()
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4);

users, err := u.WithContext(ctx).Preload(u.Orders).Preload(u.Profile).Preload(u.Role).Find()
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4); // has many
// SELECT * FROM profiles WHERE user_id IN (1,2,3,4); // has one
// SELECT * FROM roles WHERE id IN (4,5,6); // belongs to

Preload All

clause.Associations через Preload предварительно загружает все связанные данные:

type User struct {
  gorm.Model
  Name       string
  CompanyID  uint
  Company    Company
  Role
``` **Роль**

Orders     []Order
}

users, err := u.WithContext(ctx).Preload(field.Associations).Find()

clause.Associations не будет загружать вложенные ассоциации, скрытые ассоциации можно использовать с помощью Nested Preloading, например:

users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find()

Предварительная загрузка согласно условиям

q := query.Use(db)
u := q.User
o := q.Order

// Предварительная загрузка Orders с условиями
users, err := u.WithContext(ctx).Preload(u.Orders.On(o.State.NotIn("cancelled")).Find()
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4) AND state NOT IN ('cancelled');

users, err := u.WithContext(ctx).Where(u.State.Eq("active")).Preload(u.Orders.On(o.State.NotIn("cancelled")).Find()
// SELECT * FROM users WHERE state = 'active';
// SELECT * FROM orders WHERE user_id IN (1,2) AND state NOT IN ('cancelled');

users, err := u.WithContext(ctx).Preload(u.Orders.Order(o.ID.Desc(), o.CreateTime).Find()
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2) Order By id DESC, create_time;

users, err := u.WithContext(ctx).Preload(u.Orders.On(o.State.Eq("on")).Order(o.ID.Desc()).Find()
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2) AND state = "on" Order By id DESC;

users, err := u.WithContext(ctx).Preload(u.Orders.Clauses(hints.UseIndex("idx_order_id"))).Find()
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2) USE INDEX (`idx_order_id`);

Скрытая предварительная загрузка

db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users)

// Настройка условий предварительной загрузки для `Orders`
// И GEN не будет предварительно загружать несопоставленные элементы OrderItems тогда
db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users)

Обновление

Обновление одного поля

Метод Update обновляет одно поле. Необходимо указать условия обновления, иначе будет выдана ошибка ErrMissingWhereClause:

u := query.Use(db).User

// Update с условиями
u.WithContext(ctx).Where(u.Activate.Is(true)).Update(u.Name, "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;

// Update с условиями
u.WithContext(ctx).Where(u.Activate.Is(true)).Update(u.Age, u.Age.Add(1))
// или
u.WithContext(ctx).Where(u.Activate.Is(true)).UpdateSimple(u.Age.Add(1))
// UPDATE users SET age=age+1, updated_at='2013-11-17 21:34:10' WHERE active=true;

u.WithContext(ctx).Where(u.Activate.Is(true)).UpdateSimple(u.Age.Zero())
// UPDATE users SET age=0, updated_at='2013-11-17 21:34:10' WHERE active=true;
Обновление нескольких полей

Updates поддерживает struct и map[string]interface{}, обновляя несколько полей, но игнорируя нулевые значения свойств:

u := query.Use(db).User

// Обновление атрибутов с помощью `map`
u.WithContext(ctx).Where(u.ID.Eq(111)).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;

// Обновление атрибутов с помощью `struct`
u.WithContext(ctx).Where(u.ID.Eq(111)).Updates(model.User{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;

// Обновление с выражением
u.WithContext(ctx).Where(u.ID.Eq(111)).UpdateSimple(u.Age.Add(1), u.Number.Add(1))
// UPDATE users SET age=age+1,number=number+1, updated_at='2013-11-17 21:34:10' WHERE id=111;

u.WithContext(ctx).Where(u.Activate.Is(true)).UpdateSimple(u.Age.Value(17), u.Number.Zero(), u.Birthday.Null())
// UPDATE users SET age=17, number=0, birthday=NULL, updated_at='2013-11-17 21:34:10' WHERE active=true;

Примечание: при обновлении с использованием struct, GEN будет обновлять только ненулевые поля. Вы можете использовать map для обновления атрибутов или использовать Select для указания полей для обновления.

Выбор обновляемых полей

С помощью Select, Omit можно выбрать поля, которые необходимо обновить, или поля, которые нужно игнорировать при обновлении:

u := query.Use(db).User

// Select с Map
// Идентификатор пользователя равен `111`:
u.WithContext(ctx).Select(u.Name).Where(u.ID.Eq(111)).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;

u.WithContext(ctx).Omit(u.Name).Where(u.ID.Eq(111)).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET age=18 **Текст запроса:**

select * from @@table where
{{if age>60}}
    status="older"
{{else if age>30}}
    status="middle-ager"
{{else if age>18}}
    status="younger"
{{else}}
    {{if sex=="male"}}
        status="boys"
    {{else}}
        status="girls"
    {{end}}
{{end}}

Перевод текста запроса на русский язык:

Выберите * из @@table, где:

{{если возраст > 60}} статус = «старше» {{иначе если возраст > 30}} статус = «среднего возраста» {{иначе если возраст > 18}} статус = «моложе» {{иначе}} {{если пол == «мужской»}} статус = «мальчики» {{иначе}} статус = «девочки» {{конец}} {{конец}} @qqxhb @dino-ma

@jinzhu

Contributing

Вы можете помочь улучшить GORM/GEN.

License

Распространяется по лицензии MIT.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/gorm-gen.git
git@api.gitlife.ru:oschina-mirror/gorm-gen.git
oschina-mirror
gorm-gen
gorm-gen
master