Skip to content

Gorm Biblioteca ORM de Banco de Dados

Documentação oficial: GORM - The fantastic ORM library for Golang, aims to be developer friendly.

Repositório: go-gorm/gorm: The fantastic ORM library for Golang, aims to be developer friendly (github.com)

Na comunidade Go, para interação com banco de dados, existem duas abordagens principais. Uma prefere bibliotecas mais simples como sqlx, que não são tão poderosas mas permitem controle total sobre SQL, otimizando desempenho ao máximo. Outra prefere ORMs que aumentam a eficiência de desenvolvimento, economizando muito tempo. Quando se fala em ORM na comunidade Go, é impossível não mencionar gorm, um ORM muito estabelecido. Outros similares incluem xorm e ent que são relativamente mais jovens. Este artigo aborda gorm, focando em conteúdo básico introdutório. Para detalhes mais profundos, consulte a documentação oficial, que já está bastante completa em chinês, e o autor é um dos tradutores da documentação do gorm.

Características

  • ORM completo
  • Associações (Has One, Has Many, Belongs To, Many To Many, Polimorfismo, Herança de Tabela Única)
  • Hooks para Create, Save, Update, Delete, Find
  • Suporte a Preload e Joins
  • Transações, transações aninhadas, Save Point, Rollback To Saved Point
  • Context, modo Prepared, modo DryRun
  • Inserção em lote, FindInBatches, Find/Create com Map, CRUD com expressões SQL, Context Valuer
  • Construtor SQL, Upsert, Lock, Optimizer/Index/Comment Hint, Named Parameters, Subqueries
  • Chaves primárias compostas, índices, constraints
  • Migração automática
  • Logger customizável
  • API de plugins flexível e extensível: Database Resolver (multi-banco, leitura/escrita separadas), Prometheus...
  • Cada recurso é amplamente testado
  • Amigável ao desenvolvedor

gorm tem algumas desvantagens. Quase todos os parâmetros de método são do tipo interface vazia, sem consultar a documentação é difícil saber o que passar. Às vezes aceita struct, às vezes string, às vezes map, às vezes slice. A semântica é um pouco vaga, e muitas situações ainda exigem SQL manual.

Como alternativas, há dois ORMs para experimentar. Primeiro é aorm, lançado recentemente, que não requer escrita manual de nomes de colunas, usa operações em cadeia na maioria dos casos, baseado em reflexão. Com poucas stars, pode-se esperar mais. Segundo é ent, open source do Facebook, que também suporta operações em cadeia e na maioria dos casos não requer SQL manual. Sua filosofia é baseada em grafos (da estrutura de dados), implementado via geração de código ao invés de reflexão. Mas a documentação é apenas em inglês, o que representa uma barreira.

Instalação

Instale a biblioteca gorm

sh
$ go get -u gorm.io/gorm

Conexão

gorm atualmente suporta os seguintes bancos de dados

  • MySQL: "gorm.io/driver/mysql"
  • PostgreSQL: "gorm.io/driver/postgres"
  • SQLite: "gorm.io/driver/sqlite"
  • SQL Server: "gorm.io/driver/sqlserver"
  • TIDB: "gorm.io/driver/mysql", TIDB é compatível com protocolo mysql
  • ClickHouse: "gorm.io/driver/clickhouse"

Além disso, existem outros drivers de banco de dados fornecidos por desenvolvedores de terceiros, como o driver oracle CengSin/oracle. Este artigo usará MySQL para demonstração. O banco de dados usado determina qual driver instalar. Aqui instalamos o driver gorm para MySQL.

sh
$ go get -u gorm.io/driver/mysql

Então conecte ao banco de dados usando dsn (data source name). O driver irá parsear o dsn para as configurações correspondentes.

go
package main

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

func main() {
  dsn := "root:123456@tcp(192.168.48.138:3306)/hello?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn))
  if err != nil {
    slog.Error("db connect error", err)
  }
  slog.Info("db connect success")
}

Ou passando configuração manualmente

go
package main

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

func main() {
  db, err := gorm.Open(mysql.New(mysql.Config{}))
  if err != nil {
    slog.Error("db connect error", err)
  }
  slog.Info("db connect success")
}

Ambos os métodos são equivalentes, depende da preferência de uso.

Configuração de Conexão

Através da estrutura de configuração gorm.Config, podemos controlar alguns comportamentos do gorm.

go
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})

Abaixo estão algumas explicações simples. Use conforme suas necessidades.

go
type Config struct {
  // Desabilita transação padrão, gorm inicia transação em criação e atualização única para manter consistência de dados
  SkipDefaultTransaction bool
  // Estratégia de nomenclatura customizada
  NamingStrategy schema.Namer
  // Salva associações completas
  FullSaveAssociations bool
  // Logger customizado
  Logger logger.Interface
  // Customiza nowfunc, usado para injetar campos CreatedAt e UpdatedAt
  NowFunc func() time.Time
  // Gera apenas SQL sem executar
  DryRun bool
  // Usa prepared statements
  PrepareStmt bool
  // Faz ping no banco de dados após estabelecer conexão
  DisableAutomaticPing bool
  // Ignora foreign keys ao migrar banco de dados
  DisableForeignKeyConstraintWhenMigrating bool
  // Ignora referências de relacionamento ao migrar banco de dados
  IgnoreRelationshipsWhenMigrating bool
  // Desabilita transações aninhadas
  DisableNestedTransaction bool
  // Permite atualização global, update sem where
  AllowGlobalUpdate bool
  // Consulta todos os campos da tabela
  QueryFields bool
  // Tamanho do batch para criação em lote
  CreateBatchSize int
  // Habilita conversão de erro
  TranslateError bool

  // ClauseBuilders construtor de cláusulas
  ClauseBuilders map[string]clause.ClauseBuilder
  // ConnPool pool de conexão db
  ConnPool ConnPool
  // Dialector dialector do banco de dados
  Dialector
  // Plugins plugins registrados
  Plugins map[string]Plugin

  callbacks  *callbacks
  cacheStore *sync.Map
}

Modelos

Em gorm, modelos correspondem a tabelas de banco de dados, geralmente representados por structs. Por exemplo:

go
type Person struct {
  Id      uint
  Name    string
  Address string
  Mom     string
  Dad     string
}

Os campos do struct podem conter tipos de dados básicos e tipos que implementam as interfaces sql.Scanner e sql.Valuer. Por padrão, a tabela mapeada para o struct Person é persons, em estilo snake case plural. Os nomes das colunas também são em snake case, por exemplo Id corresponde à coluna id. gorm também fornece maneiras de configurar isso.

Especificar Nome da Coluna

Através de tags de struct, podemos especificar nomes de colunas para campos.

go
type Person struct {
  Id      uint   `gorm:"column:ID;"`
  Name    string `gorm:"column:Name;"`
  Address string
  Mom     string
  Dad     string
}

Especificar Nome da Tabela

Implementando a interface Tabler, podemos especificar o nome da tabela.

go
type Tabler interface {
  TableName() string
}

No método implementado, retorna a string person. Durante a migração do banco de dados, gorm criará uma tabela chamada person.

go
type Person struct {
  Id      uint   `gorm:"column:ID;"`
  Name    string `gorm:"column:Name;"`
  Address string
  Mom     string
  Dad     string
}

func (p Person) TableName() string {
  return "person"
}

Para estratégia de nomenclatura, também é possível passar sua própria implementação ao criar a conexão.

Rastreamento de Tempo

go
type Person struct {
  Id      uint
  Name    string
  Address string
  Mom     string
  Dad     string

  CreatedAt sql.NullTime
  UpdatedAt sql.NullTime
}

func (p Person) TableName() string {
  return "person"
}

Quando contém campos CreatedAt ou UpdatedAt, ao criar ou atualizar registros, se forem zero, gorm automaticamente usará time.Now() para definir o tempo.

go
db.Create(&Person{
    Name:    "jack",
    Address: "usa",
    Mom:     "lili",
    Dad:     "tom",
  })

// INSERT INTO `person` (`name`,`address`,`mom`,`dad`,`created_at`,`updated_at`) VALUES ('jack','usa','lili','tom','2023-10-25 14:43:57.16','2023-10-25 14:43:57.16')

gorm também suporta rastreamento de timestamp

go
type Person struct {
  Id      uint   `gorm:"primaryKey;"`
  Name    string `gorm:"primaryKey;"`
  Address string
  Mom     string
  Dad     string

  // nanoseconds
  CreatedAt uint64 `gorm:"autoCreateTime:nano;"`
  // milliseconds
  UpdatedAt uint64 `gorm:"autoUpdateTime;milli;"`
}

Então ao executar Create, equivale ao seguinte SQL

sql
INSERT INTO `person` (`name`,`address`,`mom`,`dad`,`created_at`,`updated_at`) VALUES ('jack','usa','lili','tom',1698216540519000000,1698216540)

Na prática, se houver necessidade de rastreamento de tempo, é mais recomendado armazenar timestamps no backend, o que simplifica o tratamento em casos de fusos horários diferentes.

Model

gorm fornece uma estrutura Model predefinida, que contém chave primária ID, dois campos de rastreamento de tempo e um campo de soft delete.

go
type Model struct {
    ID        uint `gorm:"primarykey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt DeletedAt `gorm:"index"`
}

Para usar, basta incorporar ao seu modelo de entidade.

go
type Order struct {
  gorm.Model
  Name string
}

Assim terá automaticamente todas as características de gorm.Model.

Chave Primária

Por padrão, o campo chamado Id é a chave primária. Use tags de struct para especificar campos de chave primária.

go
type Person struct {
  Id      uint `gorm:"primaryKey;"`
  Name    string
  Address string
  Mom     string
  Dad     string

  CreatedAt sql.NullTime
  UpdatedAt sql.NullTime
}

Múltiplos campos formam chaves primárias compostas

go
type Person struct {
  Id      uint `gorm:"primaryKey;"`
  Name    string `gorm:"primaryKey;"`
  Address string
  Mom     string
  Dad     string

  CreatedAt sql.NullTime
  UpdatedAt sql.NullTime
}

Índices

Através da tag index podemos especificar índices de coluna

go
type Person struct {
  Id      uint   `gorm:"primaryKey;"`
  Name    string `gorm:"primaryKey;"`
  Address string `gorm:"index:idx_addr,unique,sort:desc;"`
  Mom     string
  Dad     string

  // nanoseconds
  CreatedAt uint64 `gorm:"autoCreateTime:nano;"`
  // milliseconds
  UpdatedAt uint64 `gorm:"autoUpdateTime;milli;"`
}

No struct acima, foi criado um índice único no campo Address. Dois campos usando o mesmo nome de índice criam um índice composto

go
type Person struct {
    Id      uint   `gorm:"primaryKey;"`
    Name    string `gorm:"primaryKey;"`
    Address string `gorm:"index:idx_addr,unique;"`
    School  string `gorm:"index:idx_addr,unique;"`
    Mom     string
    Dad     string

    // nanoseconds
    CreatedAt uint64 `gorm:"autoCreateTime:nano;"`
    // milliseconds
    UpdatedAt uint64 `gorm:"autoUpdateTime;milli;"`
}

Chaves Estrangeiras

Em structs, relações de chave estrangeira são definidas através de incorporação de structs. Por exemplo:

go
type Person struct {
  Id   uint `gorm:"primaryKey;"`
  Name string

  MomId uint
  Mom   Mom `gorm:"foreignKey:MomId;"`

  DadId uint
  Dad   Dad `gorm:"foreignKey:DadId;"`
}

type Mom struct {
  Id   uint
  Name string

  Persons []Person `gorm:"foreignKey:MomId;"`
}

type Dad struct {
  Id   uint
  Name string

  Persons []Person `gorm:"foreignKey:DadId;"`
}

No exemplo, o struct Person tem duas chaves estrangeiras, referenciando as chaves primárias dos structs Dad e Mom. Por padrão, referencia a chave primária. Person tem relação um-para-um com Dad e Mom, uma pessoa só pode ter um pai e uma mãe. Dad e Mom têm relação um-para-muitos com Person, pois pais podem ter múltiplos filhos.

go
Mom   Mom `gorm:"foreignKey:MomId;"`

A incorporação de structs facilita a especificação de chaves estrangeiras e referências. Por padrão, o formato do campo de chave estrangeira é NomeDoTipoReferenciado+Id, como MomId. Por padrão referencia a chave primária. Através de tags de struct pode-se especificar referência a outro campo

go
type Person struct {
  Id   uint `gorm:"primaryKey;"`
  Name string

  MomId uint
  Mom   Mom `gorm:"foreignKey:MomId;references:Sid;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`

  DadId uint
  Dad   Dad `gorm:"foreignKey:DadId;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}

type Mom struct {
  Id   uint
  Sid  uint `gorm:"uniqueIndex;"`
  Name string

  Persons []Person `gorm:"foreignKey:MomId;"`
}

Onde constraint:OnUpdate:CASCADE,OnDelete:SET NULL; define a constraint de chave estrangeira.

Hooks

Um modelo de entidade pode definir hooks customizados

  • Create
  • Update
  • Delete
  • Query

As interfaces correspondentes são:

go
// Disparado antes de criar
type BeforeCreateInterface interface {
    BeforeCreate(*gorm.DB) error
}

// Disparado depois de criar
type AfterCreateInterface interface {
    AfterCreate(*gorm.DB) error
}

// Disparado antes de atualizar
type BeforeUpdateInterface interface {
    BeforeUpdate(*gorm.DB) error
}

// Disparado depois de atualizar
type AfterUpdateInterface interface {
    AfterUpdate(*gorm.DB) error
}

// Disparado antes de salvar
type BeforeSaveInterface interface {
    BeforeSave(*gorm.DB) error
}

// Disparado depois de salvar
type AfterSaveInterface interface {
    AfterSave(*gorm.DB) error
}

// Disparado antes de deletar
type BeforeDeleteInterface interface {
    BeforeDelete(*gorm.DB) error
}

// Disparado depois de deletar
type AfterDeleteInterface interface {
    AfterDelete(*gorm.DB) error
}

// Disparado depois de buscar
type AfterFindInterface interface {
    AfterFind(*gorm.DB) error
}

Structs podem implementar estas interfaces para customizar comportamentos.

Tags

Abaixo estão algumas tags suportadas pelo gorm

Nome da TagDescrição
columnEspecifica nome da coluna no banco de dados
typeTipo de dados da coluna, recomenda-se usar tipos genéricos com boa compatibilidade, como: bool, int, uint, float, string, time, bytes suportados por todos os bancos de dados. Pode ser usado com outras tags como not null, size, autoIncrement... Especificar tipo de dados do banco como varbinary(8) também é suportado
serializerEspecifica serializador para serializar/desserializar dados no banco de dados, ex: serializer:json/gob/unixtime
sizeDefine tamanho/length do tipo de dados da coluna, ex size: 256
primaryKeyDefine coluna como chave primária
uniqueDefine coluna como chave única
defaultDefine valor padrão da coluna
precisionEspecifica precisão da coluna
scaleEspecifica escala da coluna
not nullEspecifica coluna como NOT NULL
autoIncrementEspecifica coluna como auto incremento
autoIncrementIncrementPasso de auto incremento, controla intervalo entre registros consecutivos
embeddedCampo embutido
embeddedPrefixPrefixo do nome da coluna para campos embutidos
autoCreateTimeRastreia tempo atual na criação, para campos int, rastreia timestamp em segundos, pode usar nano/milli para nanossegundos/milissegundos, ex: autoCreateTime:nano
autoUpdateTimeRastreia tempo atual na criação/atualização, para campos int, rastreia timestamp em segundos, pode usar nano/milli para nanossegundos/milissegundos, ex: autoUpdateTime:milli
indexCria índice com parâmetros, múltiplos campos com mesmo nome criam índice composto
uniqueIndexIgual a index, mas cria índice único
checkCria constraint check, ex check:age > 13
<-Define permissão de escrita do campo, <-:create apenas criação, <-:update apenas atualização, <-:false sem permissão de escrita, <- permissão de criação e atualização
->Define permissão de leitura do campo, ->:false sem permissão de leitura
-Ignora campo, - sem leitura/escrita, -:migration sem permissão de migração, -:all sem permissão de leitura/escrita/migração
commentAdiciona comentário ao campo durante migração
foreignKeyEspecifica coluna do modelo atual como chave estrangeira na tabela de junção
referencesEspecifica nome da coluna na tabela referenciada, mapeada como chave estrangeira na tabela de junção
polymorphicEspecifica tipo polimórfico, como nome do modelo
polymorphicValueEspecifica valor polimórfico, nome da tabela padrão
many2manyEspecifica nome da tabela de junção
joinForeignKeyEspecifica nome da coluna de chave estrangeira na tabela de junção, mapeada para tabela atual
joinReferencesEspecifica nome da coluna de chave estrangeira na tabela de junção, mapeada para tabela referenciada
constraintConstraints de relacionamento, ex: OnUpdate, OnDelete

Migração

O método AutoMigrate nos ajuda com migração automática, criando tabelas, constraints, índices, chaves estrangeiras, etc.

go
func (db *DB) AutoMigrate(dst ...interface{}) error

Por exemplo

go
type Person struct {
  Id      uint   `gorm:"primaryKey;"`
  Name    string `gorm:"type:varchar(100);uniqueIndex;"`
  Address string
}

type Order struct {
  Id   uint
  Name string
}

db.AutoMigrate(Person{}, Order{})
// CREATE TABLE `person` (`id` bigint unsigned AUTO_INCREMENT,`name` varchar(100),`address` longtext,PRIMARY KEY (`id`),UNIQUE INDEX `idx_person_name` (`name`))
// CREATE TABLE `orders` (`id` bigint unsigned AUTO_INCREMENT,`name` longtext,PRIMARY KEY (`id`))

Ou podemos operar manualmente, acessando a interface Migrator através do método Migrator

go
func (db *DB) Migrator() Migrator

Suporta os seguintes métodos de interface

go
type Migrator interface {
  // AutoMigrate
  AutoMigrate(dst ...interface{}) error

  // Database
  CurrentDatabase() string
  FullDataTypeOf(*schema.Field) clause.Expr
  GetTypeAliases(databaseTypeName string) []string

  // Tables
  CreateTable(dst ...interface{}) error
  DropTable(dst ...interface{}) error
  HasTable(dst interface{}) bool
  RenameTable(oldName, newName interface{}) error
  GetTables() (tableList []string, err error)
  TableType(dst interface{}) (TableType, error)

  // Columns
  AddColumn(dst interface{}, field string) error
  DropColumn(dst interface{}, field string) error
  AlterColumn(dst interface{}, field string) error
  MigrateColumn(dst interface{}, field *schema.Field, columnType ColumnType) error
  HasColumn(dst interface{}, field string) bool
  RenameColumn(dst interface{}, oldName, field string) error
  ColumnTypes(dst interface{}) ([]ColumnType, error)

  // Views
  CreateView(name string, option ViewOption) error
  DropView(name string) error

  // Constraints
  CreateConstraint(dst interface{}, name string) error
  DropConstraint(dst interface{}, name string) error
  HasConstraint(dst interface{}, name string) bool

  // Indexes
  CreateIndex(dst interface{}, name string) error
  DropIndex(dst interface{}, name string) error
  HasIndex(dst interface{}, name string) bool
  RenameIndex(dst interface{}, oldName, newName string) error
  GetIndexes(dst interface{}) ([]Index, error)
}

A lista de métodos envolve banco de dados, tabelas, colunas, views, índices, constraints em múltiplas dimensões, permitindo operações mais refinadas para usuários que precisam de customização.

Especificar Comentário da Tabela

Durante migração, se quiser adicionar comentário à tabela, pode definir assim

go
db.Set("gorm:table_options", " comment 'person table'").Migrator().CreateTable(Person{})

Note que se usar AutoMigrate() para migração, e os structs tiverem relações de referência, gorm fará migração recursiva primeiro criando tabelas referenciadas, o que fará com que comentários de tabelas referenciadas e referenciadoras sejam duplicados. Então é recomendado usar o método CreateTable para criar.

TIP

Ao criar tabelas, o método CreateTable requer que a tabela referenciada seja criada antes da tabela referenciadora, caso contrário ocorrerá erro. O método AutoMigrate não requer isso, pois segue a relação de referência recursivamente.

Criação

Create

Na maioria dos casos ao criar novos registros, usamos o método Create

go
func (db *DB) Create(value interface{}) (tx *DB)

Tendo o seguinte struct

go
type Person struct {
  Id   uint `gorm:"primaryKey;"`
  Name string
}

Criando um registro

go
user := Person{
    Name: "jack",
}

// Deve passar ponteiro
db = db.Create(&user)

// Erro ocorrido durante execução
err = db.Error
// Número de registros criados
affected := db.RowsAffected

Após criação, gorm escreverá a chave primária no struct user, por isso é necessário passar ponteiro. Se passar um slice, criará em lote

go
user := []Person{
    {Name: "jack"},
    {Name: "mike"},
    {Name: "lili"},
}

db = db.Create(&user)

Da mesma forma, gorm escreverá as chaves primárias no slice. Quando volume de dados é grande, pode usar CreateInBatches para criar em lotes, pois o SQL gerado INSERT INTO table VALUES (),() ficará muito longo, e cada banco de dados tem limite de tamanho SQL. Então quando necessário, pode criar em lotes.

go
db = db.CreateInBatches(&user, 50)

Além disso, o método Save também pode criar registros. Sua função é atualizar o registro quando a chave primária corresponder, caso contrário insere.

go
func (db *DB) Save(value interface{}) (tx *DB)
go
user := []Person{
    {Name: "jack"},
    {Name: "mike"},
    {Name: "lili"},
}

db = db.Save(&user)

Upsert

O método Save só corresponde à chave primária. Podemos construir Clause para upsert mais customizado. Por exemplo

go
db.Clauses(clause.OnConflict{
    Columns:   []clause.Column{{Name: "name"}},
    DoNothing: false,
    DoUpdates: clause.AssignmentColumns([]string{"address"}),
    UpdateAll: false,
}).Create(&p)

Isso atualiza o valor do campo address quando o campo name conflitar, caso contrário cria um novo registro. Também pode fazer nada quando conflitar

go
db.Clauses(clause.OnConflict{
    Columns:   []clause.Column{{Name: "name"}},
    DoNothing: true,
}).Create(&p)

Ou atualizar todos os campos diretamente

go
db.Clauses(clause.OnConflict{
    Columns:   []clause.Column{{Name: "name"}},
    UpdateAll: true,
}).Create(&p)

Antes de usar upsert, lembre-se de adicionar índice aos campos de conflito.

Consulta

First

gorm fornece muitos métodos para consulta. O primeiro é First

go
func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB)

Busca o primeiro registro ordenado por chave primária ascendente. Por exemplo

go
var person Person
result := db.First(&person)
err := result.Error
affected := result.RowsAffected

Passa ponteiro dest para facilitar mapeamento dos dados consultados ao struct.

Ou usa métodos Table e Model para especificar tabela de consulta. O primeiro recebe string com nome da tabela, o segundo recebe modelo de entidade.

db.Table("person").Find(&p)
db.Model(Person{}).Find(&p)

TIP

Se o ponteiro passado contiver modelo de entidade como ponteiro de struct ou ponteiro de slice de struct, não precisa especificar manualmente qual tabela consultar. Esta regra se aplica a todas as operações de CRUD.

Take

O método Take é similar ao First, diferença é que não ordena por chave primária.

go
func (db *DB) Take(dest interface{}, conds ...interface{}) (tx *DB)
go
var person Person
result := db.Take(&person)
err := result.Error
affected := result.RowsAffected

Pluck

O método Pluck é usado para consultar uma única coluna de uma tabela em lote. O resultado pode ser coletado em um slice de tipo especificado, não necessariamente slice de tipo de entidade.

go
func (db *DB) Pluck(column string, dest interface{}) (tx *DB)

Por exemplo, coletar endereços de todas as pessoas em um slice de string

go
var adds []string

// SELECT `address` FROM `person` WHERE name IN ('jack','lili')
db.Model(Person{}).Where("name IN ?", []string{"jack", "lili"}).Pluck("address", &adds)

Equivalente a

go
db.Select("address").Where("name IN ?", []string{"jack", "lili"}).Find(&adds)

Count

O método Count é usado para contar número de registros de entidade

go
func (db *DB) Count(count *int64) (tx *DB)

Exemplo de uso

go
var count int64

// SELECT count(*) FROM `person`
db.Model(Person{}).Count(&count)

Find

O método mais comum para consulta em lote é Find

go
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB)

Busca todos os registros que correspondem às condições dadas

go
// SELECT * FROM `person`
var ps []Person
db.Find(&ps)

Select

gorm por padrão consulta todos os campos. Podemos usar Select para especificar campos

go
func (db *DB) Select(query interface{}, args ...interface{}) (tx *DB)

Por exemplo

go
// SELECT `address`,`name` FROM `person` ORDER BY `person`.`id` LIMIT 1
db.Select("address", "name").First(&p)

Equivalente a

go
db.Select([]string{"address", "name"}).First(&p)

Também pode usar método Omit para ignorar campos

go
func (db *DB) Omit(columns ...string) (tx *DB)

Por exemplo

go
// SELECT `person`.`id`,`person`.`name` FROM `person` WHERE id IN (1,2,3,4)
db.Omit("address").Where("id IN ?", []int{1, 2, 3, 4}).Find(&ps)

Campos selecionados ou ignorados por Select e Omit também funcionam em consultas de criação e atualização.

Where

Consulta condicional usa método Where

go
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB)

Exemplo simples

go
var p Person

db.Where("id = ?", 1).First(&p)

Em operações em cadeia, múltiplos Where constroem múltiplas cláusulas AND

go
// SELECT * FROM `person` WHERE id = 1 AND name = 'jack' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).Where("name = ?", "jack").First(&p)

Ou usa método Or para construir cláusulas OR

go
func (db *DB) Or(query interface{}, args ...interface{}) (tx *DB)
go
// SELECT * FROM `person` WHERE id = 1 OR name = 'jack' AND address = 'usa' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).
    Or("name = ?", "jack").
    Where("address = ?", "usa").
    First(&p)

E método Not, similares

go
func (db *DB) Not(query interface{}, args ...interface{}) (tx *DB)
go
// SELECT * FROM `person` WHERE id = 1 OR name = 'jack' AND NOT name = 'mike' AND address = 'usa' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).
    Or("name = ?", "jack").
    Not("name = ?", "mike").
    Where("address = ?", "usa").
    First(&p)

Para condição IN, pode passar slice diretamente no Where

go
db.Where("address IN ?", []string{"cn", "us"}).Find(&ps)

Ou condição IN multi-coluna, precisa usar tipo [][]any para parâmetros

go
// SELECT * FROM `person` WHERE (id, name, address) IN ((1,'jack','uk'),(2,'mike','usa'))
db.Where("(id, name, address) IN ?", [][]any{{1, "jack", "uk"}, {2, "mike", "usa"}}).Find(&ps)

gorm suporta agrupamento de where, combinando as declarações acima

go
db.Where(
    db.Where("name IN ?", []string{"cn", "uk"}).Where("id IN ?", []uint{1, 2}),
  ).Or(
    db.Where("name IN ?", []string{"usa", "jp"}).Where("id IN ?", []uint{3, 4}),
  ).Find(&ps)
// SELECT * FROM `person` WHERE (name IN ('cn','uk') AND id IN (1,2)) OR (name IN ('usa','jp') AND id IN (3,4))

Order

Ordenação usa método Order

go
func (db *DB) Order(value interface{}) (tx *DB)

Exemplo de uso

go
var ps []Person

// SELECT * FROM `person` ORDER BY name ASC, id DESC
db.Order("name ASC, id DESC").Find(&ps)

Também pode chamar múltiplas vezes

go
// SELECT * FROM `person` ORDER BY name ASC, id DESC,address
db.Order("name ASC, id DESC").Order("address").Find(&ps)

Limit

Métodos Limit e Offset são frequentemente usados para paginação

go
func (db *DB) Limit(limit int) (tx *DB)

func (db *DB) Offset(offset int) (tx *DB)

Exemplo simples de paginação

go
var (
    ps   []Person
    page = 2
    size = 10
)

// SELECT * FROM `person` LIMIT 10 OFFSET 10
db.Offset((page - 1) * size).Limit(size).Find(&ps)

Group

Métodos Group e Having são usados para operações de agrupamento

go
func (db *DB) Group(name string) (tx *DB)

func (db *DB) Having(query interface{}, args ...interface{}) (tx *DB)

Exemplo

go
var (
    ps []Person
)

// SELECT `address` FROM `person` GROUP BY `address` HAVING address IN ('cn','us')
db.Select("address").Group("address").Having("address IN ?", []string{"cn", "us"}).Find(&ps)

Distinct

Método Distinct é usado para remover duplicatas

go
func (db *DB) Distinct(args ...interface{}) (tx *DB)

Exemplo

go
// SELECT DISTINCT `name` FROM `person` WHERE address IN ('cn','us')
db.Where("address IN ?", []string{"cn", "us"}).Distinct("name").Find(&ps)

Subqueries

Subquery é query aninhada. Por exemplo, para buscar todas as pessoas com id maior que a média

go
// SELECT * FROM `person` WHERE id > (SELECT AVG(id) FROM `person`
db.Where("id > (?)", db.Model(Person{}).Select("AVG(id)")).Find(&ps)

from subquery

go
// SELECT * FROM (SELECT * FROM `person` WHERE address IN ('cn','uk')) as p
db.Table("(?) as p", db.Model(Person{}).Where("address IN ?", []string{"cn", "uk"})).Find(&ps)

Lock

gorm usa cláusula clause.Locking para fornecer suporte a locks

go
// SELECT * FROM `person` FOR UPDATE
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&ps)

// SELECT * FROM `person` FOR SHARE NOWAIT
db.Clauses(clause.Locking{Strength: "SHARE", Options: "NOWAIT"}).Find(&ps)

Iteração

Através do método Rows podemos obter um iterador

go
func (db *DB) Rows() (*sql.Rows, error)

Iterando sobre o iterador, usa método ScanRows para escanear cada linha para o struct.

go
rows, err := db.Model(Person{}).Rows()
if err != nil {
    return
}
defer rows.Close()

for rows.Next() {
    var p Person
    err := db.ScanRows(rows, &p)
    if err != nil {
        return
    }
}

Modificação

Save

Mencionado em criação, método Save também pode atualizar registros. Atualiza todos os campos, mesmo que alguns campos do struct sejam zero. Mas se a chave primária não corresponder, fará inserção.

go
var p Person

db.First(&p)

p.Address = "poland"
// UPDATE `person` SET `name`='json',`address`='poland' WHERE `id` = 2
db.Save(&p)

Vê-se que coloca todos os campos exceto chave primária na cláusula SET.

Update

Na maioria dos casos, é recomendado usar método Update

go
func (db *DB) Update(column string, value interface{}) (tx *DB)

Principalmente usado para atualizar campo único

go
var p Person

db.First(&p)

// UPDATE `person` SET `address`='poland' WHERE id = 2
db.Model(Person{}).Where("id = ?", p.Id).Update("address", "poland")

Updates

Método Updates é usado para atualizar múltiplas colunas, recebe struct e map como parâmetros. Quando campos do struct são zero, ignora o campo, mas em map não.

go
func (db *DB) Updates(values interface{}) (tx *DB)

Exemplo

go
var p Person

db.First(&p)

// UPDATE `person` SET `name`='jojo',`address`='poland' WHERE `id` = 2
db.Model(p).Updates(Person{Name: "jojo", Address: "poland"})

// UPDATE `person` SET `address`='poland',`name`='jojo' WHERE `id` = 2
db.Model(p).Updates(map[string]any{"name": "jojo", "address": "poland"})

Expressões SQL

Às vezes, frequentemente precisamos fazer operações como incremento ou decremento em campos, geralmente primeiro busca, depois calcula e atualiza, ou usa expressões SQL.

go
func Expr(expr string, args ...interface{}) clause.Expr

Exemplo

go
// UPDATE `person` SET `age`=age + age,`name`='jojo' WHERE `id` = 2
db.Model(p).Updates(map[string]any{"name": "jojo", "age": gorm.Expr("age + age")})

// UPDATE `person` SET `age`=age * 2 + age,`name`='jojo' WHERE `id` = 2
db.Model(p).Updates(map[string]any{"name": "jojo", "age": gorm.Expr("age * 2 + age")})

Deleção

Em gorm, deleção de registros usa método Delete, pode passar struct de entidade ou condições.

go
func (db *DB) Delete(value interface{}, conds ...interface{}) (tx *DB)

Por exemplo, passando struct diretamente

go
var p Person

db.First(&p)

// DELETE FROM `person` WHERE `person`.`id` = 2
db.Delete(&p)

Ou

go
var p Person

db.First(&p)

// DELETE FROM `person` WHERE `person`.`id` = 2
db.Model(p).Delete(nil)

Ou especificando condições

go
// DELETE FROM `person` WHERE id = 2
db.Model(Person{}).Where("id = ?", p.Id).Delete(nil)

Também pode simplificar para

go
var p Person

db.First(&p)

// DELETE FROM `person` WHERE id = 2
db.Delete(&Person{}, "id = ?", 2)

// DELETE FROM `person` WHERE `person`.`id` = 2
db.Delete(&Person{}, 2)

Para deleção em lote, passa slice

go
// DELETE FROM `person` WHERE id IN (1,2,3)
db.Delete(&Person{}, "id IN ?", []uint{1, 2, 3})
// DELETE FROM `person` WHERE `person`.`id` IN (1,2,3)
db.Delete(&Person{}, []uint{1, 2, 3})

Soft Delete

Se seu modelo de entidade usar soft delete, ao deletar, por padrão faz operação de atualização. Para deleção permanente, pode usar método Unscoped

go
db.Unscoped().Delete(&Person{}, []uint{1, 2, 3})

Definição de Associação

gorm fornece capacidade de interação de associação entre tabelas, definindo associações entre structs através de incorporação de structs e campos.

Um para Um

Relação um-para-um é a mais simples. Normalmente uma pessoa só pode ter uma mãe. Veja o struct abaixo

go
type Person struct {
  Id      uint
  Name    string
  Address string
  Age     uint

  MomId sql.NullInt64
  Mom   Mom `gorm:"foreignKey:MomId;"`
}

type Mom struct {
  Id   uint
  Name string
}

Struct Person referencia tipo Mom através de incorporação do struct Mom. Onde Person.MomId é campo de referência, chave primária Mom.Id é campo referenciado, completando associação um-para-um. Como customizar chave estrangeira, referência, constraints e regras padrão de chave estrangeira já foram explicados em Definição de Chave Estrangeira, não será repetido.

TIP

Para campos de chave estrangeira, é recomendado usar tipos fornecidos pelo pacote sql, pois chaves estrangeiras podem ser NULL por padrão. Ao usar Create para criar registros, se usar tipo comum, valor zero 0 também será criado, o que não é permitido para chaves estrangeiras inexistentes.

Um para Muitos

Adicionando struct School, relação escola e estudante é um-para-muitos. Uma escola tem múltiplos estudantes, mas um estudante só pode estudar em uma escola.

go
type Person struct {
    Id      uint
    Name    string
    Address string
    Age     uint

    MomId sql.NullInt64
    Mom   Mom `gorm:"foreignKey:MomId;"`

    SchoolId sql.NullInt64
    School   School `gorm:"foreignKey:SchoolId;"`
}

type Mom struct {
    Id   uint
    Name string
}


type School struct {
    Id   uint
    Name string

    Persons []Person `gorm:"foreignKey:SchoolId;"`
}

school.Persons é tipo []Person, indicando que pode possuir múltiplos estudantes. Person deve conter chave estrangeira referenciando School, que é Person.SchoolId.

Muitos para Muitos

Uma pessoa pode ter muitas casas, uma casa pode acomodar muitas pessoas. Esta é uma relação muitos-para-muitos.

go
type Person struct {
  Id      uint
  Name    string
  Address string
  Age     uint

  MomId sql.NullInt64
  Mom   Mom `gorm:"foreignKey:MomId;"`

  SchoolId sql.NullInt64
  School   School `gorm:"foreignKey:SchoolId;"`

  Houses []House `gorm:"many2many:person_house;"`
}

type Mom struct {
  Id   uint
  Name string
}

type School struct {
  Id   uint
  Name string

  Persons []Person
}

type House struct {
  Id   uint
  Name string

  Persons []Person `gorm:"many2many:person_house;"`
}

type PersonHouse struct {
  PersonId sql.NullInt64
  Person   Person `gorm:"foreignKey:PersonId;"`
  HouseId  sql.NullInt64
  House    House `gorm:"foreignKey:HouseId;"`
}

Person e House mantêm slices um do outro indicando relação muitos-para-muitos. Relação muitos-para-muitos geralmente requer criação de tabela de junção, especificada através de many2many. Chaves estrangeiras da tabela de junção devem estar corretas.

Após criar structs, deixe gorm migrar automaticamente para o banco de dados

go
tables := []any{
    School{},
    Mom{},
    Person{},
    House{},
    PersonHouse{},
}
for _, table := range tables {
    db.Migrator().CreateTable(&table)
}

Note a ordem de criação entre tabelas referenciadas e referenciadoras.

Operações de Associação

Após criar as três relações de associação acima,接下来 é como usar associações para CRUD. Principalmente usa método Association

go
func (db *DB) Association(column string) *Association

Recebe um parâmetro de associação, seu valor deve ser nome do campo do tipo referenciado no struct de referência incorporado.

go
db.Model(&person).Association("Mom").Find(&mom)

Por exemplo, buscar mãe de uma pessoa, parâmetro de Association é Mom, nome do campo Person.Mom.

Criar Associação

go
// Definir dados
jenny := Mom{
    Name: "jenny",
}

mit := School{
    Name:    "MIT",
    Persons: nil,
}

h1 := House{
    Id:      0,
    Name:    "h1",
    Persons: nil,
}

h2 := House{
    Name:    "h2",
    Persons: nil,
}

jack := Person{
    Name:    "jack",
    Address: "usa",
    Age:     18,
}

mike := Person{
    Name:    "mike",
    Address: "uk",
    Age:     20,
}

// INSERT INTO `people` (`name`,`address`,`age`,`mom_id`,`school_id`) VALUES ('jack','usa',18,NULL,NULL)
db.Create(&jack)
// INSERT INTO `schools` (`name`) VALUES ('MIT')
db.Create(&mit)

// Adicionar associação Person com Mom, associação um-para-um
// INSERT INTO `moms` (`name`) VALUES ('jenny') ON DUPLICATE KEY UPDATE `id`=`id`
// UPDATE `people` SET `mom_id`=1 WHERE `id` = 1
db.Model(&jack).Association("Mom").Append(&jenny)

// Adicionar associação school com Person, associação um-para-muitos
// INSERT INTO `people` (`name`,`address`,`age`,`mom_id`,`school_id`,`id`) VALUES ('jack','usa',18,1,1,1),('mike','uk',20,NULL,1,DEFAULT) ON DUPLICATE KEY UPDATE `school_id`=VALUES(`school_id`)
db.Model(&mit).Association("Persons").Append([]Person{jack, mike})

// Adicionar associação Person com Houses, associação muitos-para-muitos
// INSERT INTO `houses` (`name`) VALUES ('h1'),('h2') ON DUPLICATE KEY UPDATE `id`=`id`
// INSERT INTO `person_house` (`person_id`,`house_id`) VALUES (1,1),(1,2) ON DUPLICATE KEY UPDATE `person_id`=`person_id`
db.Model(&jack).Association("Houses").Append([]House{h1, h2})

Se todos os registros não existirem, ao criar associação, também criará registros primeiro e depois associação.

Buscar Associação

Abaixo demonstra como buscar associações.

go
// Busca associação um-para-um
var person Person
var mom Mom

// SELECT * FROM `people` ORDER BY `people`.`id` LIMIT 1
db.First(&person)
// SELECT * FROM `moms` WHERE `moms`.`id` = 1
db.Model(person).Association("Mom").Find(&mom)

// Busca associação um-para-muitos
var school School
var persons []Person

// SELECT * FROM `schools` ORDER BY `schools`.`id` LIMIT 1
db.First(&school)
// SELECT * FROM `people` WHERE `people`.`school_id` = 1
db.Model(&school).Association("Persons").Find(&persons)

// Busca associação muitos-para-muitos
var houses []House

// SELECT `houses`.`id`,`houses`.`name` FROM `houses` JOIN `person_house` ON `person_house`.`house_id` = `houses`.`id` AND `person_house`.`person_id` IN (1,2)
db.Model(&persons).Association("Houses").Find(&houses)

Busca de associação buscará registros que correspondem às condições na tabela referenciada com base nos dados existentes. Para relação muitos-para-muitos, gorm completará automaticamente o processo de junção de tabelas.

Atualizar Associação

Abaixo demonstra como atualizar associações.

go
// Atualização de associação um-para-um
var jack Person

lili := Mom{
    Name: "lili",
}

// SELECT * FROM `people` WHERE name = 'jack' ORDER BY `people`.`id` LIMIT 1
db.Where("name = ?", "jack").First(&jack)

// INSERT INTO `moms` (`name`) VALUES ('lili')
db.Create(&lili)

// INSERT INTO `moms` (`name`,`id`) VALUES ('lili',2) ON DUPLICATE KEY UPDATE `id`=`id`
// UPDATE `people` SET `mom_id`=2 WHERE `id` = 1
db.Model(&jack).Association("Mom").Replace(&lili)

// Atualização de associação um-para-muitos

var mit School
newPerson := []Person{{Name: "bob"}, {Name: "jojo"}}
// INSERT INTO `people` (`name`,`address`,`age`,`mom_id`,`school_id`) VALUES ('bob','',0,NULL,NULL),('jojo','',0,NULL,NULL)
db.Create(&newPerson)

//  SELECT * FROM `schools` WHERE name = 'mit' ORDER BY `schools`.`id` LIMIT 1
db.Where("name = ?", "mit").First(&mit)

// INSERT INTO `people` (`name`,`address`,`age`,`mom_id`,`school_id`,`id`) VALUES ('bob','',0,NULL,1,4),('jojo','',0,NULL,1,5) ON DUPLICATE KEY UPDATE `school_id`=VALUES(`school_id`)
//  UPDATE `people` SET `school_id`=NULL WHERE `people`.`id` NOT IN (4,5) AND `people`.`school_id` = 1
db.Model(&mit).Association("Persons").Replace(newPerson)

// Atualização de associação muitos-para-muitos

// INSERT INTO `houses` (`name`) VALUES ('h3'),('h4'),('h5') ON DUPLICATE KEY UPDATE `id`=`id`
// INSERT INTO `person_house` (`person_id`,`house_id`) VALUES (1,3),(1,4),(1,5) ON DUPLICATE KEY UPDATE `person_id`=`person_id`
// DELETE FROM `person_house` WHERE `person_house`.`person_id` = 1 AND `person_house`.`house_id` NOT IN (3,4,5)
db.Model(&jack).Association("Houses").Replace([]House{{Name: "h3"}, {Name: "h4"}, {Name: "h5"}})

Ao atualizar associação, se dados referenciados e referenciadores não existirem, gorm tentará criá-los.

Deletar Associação

Abaixo demonstra como deletar associações.

go
// Deleção de associação um-para-um
var (
    jack Person
    lili Mom
)

// SELECT * FROM `people` WHERE name = 'jack' ORDER BY `people`.`id` LIMIT 1
db.Where("name = ?", "jack").First(&jack)

//  SELECT * FROM `moms` WHERE name = 'lili' ORDER BY `moms`.`id` LIMIT 1
db.Where("name = ?", "lili").First(&lili)

// UPDATE `people` SET `mom_id`=NULL WHERE `people`.`id` = 1 AND `people`.`mom_id` = 2
db.Model(&jack).Association("Mom").Delete(&lili)

// Deleção de associação um-para-muitos

var (
    mit     School
    persons []Person
)

// SELECT * FROM `schools` WHERE name = 'mit' ORDER BY `schools`.`id` LIMIT 1
db.Where("name = ?", "mit").First(&mit)
// SELECT * FROM `people` WHERE name IN ('jack','mike')
db.Where("name IN ?", []string{"jack", "mike"}).Find(&persons)

// UPDATE `people` SET `school_id`=NULL WHERE `people`.`school_id` = 1 AND `people`.`id` IN (1,2)
db.Model(&mit).Association("Persons").Delete(&persons)

// Deleção de associação muitos-para-muitos
var houses []House

// SELECT * FROM `houses` WHERE name IN ('h3','h4')
db.Where("name IN ?", []string{"h3", "h4"}).Find(&houses)

// DELETE FROM `person_house` WHERE `person_house`.`person_id` = 1 AND `person_house`.`house_id` IN (3,4)
db.Model(&jack).Association("Houses").Delete(&houses)

Deleção de associação apenas remove relação de referência entre eles, não deleta registros de entidade. Também podemos usar método Clear para limpar associação diretamente

go
db.Model(&jack).Association("Houses").Clear()

Se quiser deletar registros de entidade correspondentes, pode adicionar operação Unscoped após operação Association (não afeta many2many)

go
db.Model(&jack).Association("Houses").Unscoped().Delete(&houses)

Para um-para-muitos e muitos-para-muitos, pode usar operação Select para deletar registros

go
var (
    mit     School
)
db.Where("name = ?", "mit").First(&mit)

db.Select("Persons").Delete(&mit)

Preload

Preload é usado para buscar dados de associação. Para entidades com relações de associação, primeiro carrega entidades referenciadas associadas. Query de associação mencionada anteriormente é para buscar relações de associação, preload é para buscar diretamente registros de entidade, incluindo todas as relações de associação. Sintaticamente, query de associação precisa primeiro buscar []Person especificado, depois buscar []Mom associado baseado em []Person. Preload sintaticamente busca diretamente []Person, e também carrega todas as relações de associação. Mas na prática, o SQL executado é similar. Veja exemplo

go
var users []Person

// SELECT * FROM `moms` WHERE `moms`.`id` = 1
// SELECT * FROM `people`
db.Preload("Mom").Find(&users)

Este é exemplo de query de associação um-para-um. Sua saída

go
[{Id:1 Name:jack Address:usa Age:18 MomId:{Int64:1 Valid:true} Mom:{Id:1 Name:jenny} SchoolId:{Int64:1 Valid:true} School:{Id:0 Name: Persons:[]} Houses:[]} {Id:2 Name:mike Address:uk Age:20 MomId:{Int64:0 Valid:false} Mom:{Id:0 Name:} SchoolId:{Int64:1 Valid:true} School:{Id:0 Name: Persons:[]} Houses:[]}]

Vê-se que também buscou Mom associado, mas como não fez preload da relação escolar, structs School são todos zero. Também pode usar clause.Associations para indicar preload de todas as relações, exceto relações aninhadas.

go
db.Preload(clause.Associations).Find(&users)

Abaixo exemplo de preload aninhado. Busca todos os estudantes de todas as associações escolares e mães associadas a cada estudante e casas possuídas por cada estudante, e também conjunto de donos de cada casa. Escola->Estudante->Casa->Estudante.

go
var schools []School

db.Preload("Persons").
    Preload("Persons.Mom").
    Preload("Persons.Houses").
    Preload("Persons.Houses.Persons").Find(&schools)

// Código de saída, lógica pode ser ignorada
for _, school := range schools {
    fmt.Println("school", school.Name)
    for _, person := range school.Persons {
        fmt.Println("person", person.Name)
        fmt.Println("mom", person.Mom.Name)
        for _, house := range person.Houses {
            var persons []string
            for _, p := range house.Persons {
                persons = append(persons, p.Name)
            }
            fmt.Println("house", house.Name, "owner", persons)
        }
        fmt.Println()
    }
}

Saída

school MIT
person jack
mom jenny
house h1 owner [jack]
house h2 owner [jack]

person mike
mom

Vê-se que outputou mãe de cada estudante de cada escola e suas casas, e todos donos das casas.

Transações

gorm habilita transações por padrão. Qualquer inserção e atualização falhará e fará rollback. Pode desabilitar em Configuração de Conexão, desempenho aumenta cerca de 30%. Uso de transações em gorm tem múltiplos métodos, abaixo introdução simples.

Automático

Transação em closure, através do método Transaction, passa uma função closure. Se valor de retorno não for nil, faz rollback automaticamente.

go
func (db *DB) Transaction(fc func(tx *DB) error, opts ...*sql.TxOptions) (err error)

Exemplo

go
var ps []Person

db.Transaction(func(tx *gorm.DB) error {
    err := tx.Create(&ps).Error
    if err != nil {
        return err
    }

    err = tx.Create(&ps).Error
    if err != nil {
        return err
    }

    err = tx.Model(Person{}).Where("id = ?", 1).Update("name", "jack").Error
    if err != nil {
        return err
    }

    return nil
})

Manual

É mais recomendado usar transação manual, controlamos nós mesmos quando fazer rollback, quando commit. Transação manual usa três métodos abaixo

go
// Método Begin usado para iniciar transação
func (db *DB) Begin(opts ...*sql.TxOptions) *DB

// Método Rollback usado para reverter transação
func (db *DB) Rollback() *DB

// Método Commit usado para commitar transação
func (db *DB) Commit() *DB

Exemplo

go
var ps []Person

tx := db.Begin()

err := tx.Create(&ps).Error
if err != nil {
    tx.Rollback()
    return
}

err = tx.Create(&ps).Error
if err != nil {
    tx.Rollback()
    return
}

err = tx.Model(Person{}).Where("id = ?", 1).Update("name", "jack").Error
if err != nil {
    tx.Rollback()
    return
}

tx.Commit()

Pode especificar ponto de rollback

go
var ps []Person

tx := db.Begin()

err := tx.Create(&ps).Error
if err != nil {
    tx.Rollback()
    return
}

tx.SavePoint("createBatch")

err = tx.Create(&ps).Error
if err != nil {
    tx.Rollback()
    return
}

err = tx.Model(Person{}).Where("id = ?", 1).Update("name", "jack").Error
if err != nil {
    tx.RollbackTo("createBatch")
    return
}

tx.Commit()

Resumo

Se leu todo conteúdo acima e executou o código, então pode usar gorm para CRUD de banco de dados. Além destas operações, gorm tem muitas outras funcionalidades. Mais detalhes podem ser encontrados na documentação oficial.

Golang por www.golangdev.cn edit