30-ORM:CURD神器GORM包介绍及实战

 

 目前,GitHub上 star数最多的是GORM,它也是当前Go项目中使用最多的ORM。

 

GORM基础知识介绍

GORM是Go语言的ORM包,功能强大,调用方便。像腾讯、华为、阿里这样的大厂,都在使用GORM来构建企业级的应用。 

  • 功能全。使用ORM操作数据库的接口,GORM都有,可以满足我们开发中对数据库调用的各类需求。
  • 支持钩子方法。这些钩子方法可以应用在Create、Save、Update、Delete、Find方法中。
  • 开发者友好,调用方便。
  • 支持Auto Migration。
  • 支持关联查询。
  • 支持多种关系数据库,例如MySQL、Postgres、SQLite、SQLServer等。

GORM有两个版本,V1和V2。 

通过示例学习GORM

接下来,我们先快速看一个使用GORM的示例,通过该示例来学习GORM。示例代码存放在marmotedu/gopractise-demo/gorm/main.go文件中。因为代码比较长,你可以使用以下命令克隆到本地查看:

$ mkdir -p $GOPATH/src/github.com/marmotedu
$ cd $GOPATH/src/github.com/marmotedu
$ git clone https://github.com/marmotedu/gopractise-demo
$ cd gopractise-demo/gorm/

假设我们有一个MySQL数据库,连接地址和端口为 127.0.0.1:3306 ,用户名为 iam ,密码为 iam1234 。创建完main.go文件后,执行以下命令来运行:

$ go run main.go -H 127.0.0.1:3306 -u iam -p iam1234 -d test
2020/10/17 15:15:50 totalcount: 1
2020/10/17 15:15:50 	code: D42, price: 100
2020/10/17 15:15:51 totalcount: 1
2020/10/17 15:15:51 	code: D42, price: 200
2020/10/17 15:15:51 totalcount: 0

在企业级Go项目开发中,使用GORM库主要用来完成以下数据库操作:

  • 连接和关闭数据库。连接数据库时,可能需要设置一些参数,比如最大连接数、最大空闲连接数、最大连接时长等。
  • 插入表记录。可以插入一条记录,也可以批量插入记录。
  • 更新表记录。可以更新某一个字段,也可以更新多个字段。
  • 查看表记录。可以查看某一条记录,也可以查看符合条件的记录列表。
  • 删除表记录。可以删除某一个记录,也可以批量删除。删除还支持永久删除和软删除。

GORM功能强大,上面的示例代码展示的是比较通用的一种操作方式。

上述代码中,首先定义了一个GORM模型(Models), Models定义如下:

type Product struct {gorm.ModelCode  string `gorm:"column:code"`Price uint   `gorm:"column:price"`
}// TableName maps to mysql table name.
func (p *Product) TableName() string {return "product"
}

如果没有指定表名,则GORM使用结构体名的蛇形复数作为表名。例如:结构体名为 DockerInstance ,则表名为 dockerInstances 。

在之后的代码中,使用Pflag来解析命令行的参数,通过命令行参数指定数据库的地址、用户名、密码和数据库名。之后,使用这些参数生成建立 MySQL 连接需要的配置文件,并调用 gorm.Open 建立数据库连接:

var (host     = pflag.StringP("host", "H", "127.0.0.1:3306", "MySQL service host address")username = pflag.StringP("username", "u", "root", "Username for access to mysql service")password = pflag.StringP("password", "p", "root", "Password for access to mysql, should be used pair with password")database = pflag.StringP("database", "d", "test", "Database name to use")help     = pflag.BoolP("help", "h", false, "Print this help message")
)func main() {// Parse command line flagspflag.CommandLine.SortFlags = falsepflag.Usage = func() {pflag.PrintDefaults()}pflag.Parse()if *help {pflag.Usage()return}dns := fmt.Sprintf(`%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s`,*username,*password,*host,*database,true,"Local")db, err := gorm.Open(mysql.Open(dns), &gorm.Config{})if err != nil {panic("failed to connect database")}
}

创建完数据库连接之后,会返回数据库实例 db ,之后就可以调用db实例中的方法,完成数据库的CURD操作。 

第一个操作,自动迁移表结构。

// 1. Auto migration for given models
db.AutoMigrate(&Product{})

我不建议你在正式的代码中自动迁移表结构。 

GORM的AutoMigrate方法,只对新增的字段或索引进行变更,理论上是没有风险的。在实际的Go项目开发中,也有很多人使用AutoMigrate方法自动同步表结构 

第二个操作,插入表记录。

// 2. Insert the value into database
if err := db.Create(&Product{Code: "D42", Price: 100}).Error; err != nil {log.Fatalf("Create error: %v", err)
}
PrintProducts(db)

通过 db.Create 方法创建了一条记录。插入记录后,通过调用 PrintProducts 方法打印当前表中的所有数据记录,来测试是否成功插入。

第三个操作,获取符合条件的记录。

// 3. Find first record that match given conditions
product := &Product{}
if err := db.Where("code= ?", "D42").First(&product).Error; err != nil {log.Fatalf("Get product error: %v", err)
}

 

第四个操作,更新表记录。

// 4. Update value in database, if the value doesn't have primary key, will insert it
product.Price = 200
if err := db.Save(product).Error; err != nil {log.Fatalf("Update product error: %v", err)
}
PrintProducts(db)

通过Save方法,可以把 product 变量中所有跟数据库不一致的字段更新到数据库中。具体操作是:先获取某个资源的详细信息,再通过 product.Price = 200 这类赋值语句,对其中的一些字段重新赋值。 

第五个操作,删除表记录。

通过 Delete 方法删除表记录,代码如下:

// 5. Delete value match given conditions
if err := db.Where("code = ?", "D42").Delete(&Product{}).Error; err != nil {log.Fatalf("Delete product error: %v", err)
}
PrintProducts(db)

这里需要注意,因为 Product 中有 gorm.DeletedAt 字段,所以,上述删除操作不会真正把记录从数据库表中删除掉,而是通过设置数据库 product 表 deletedAt 字段为当前时间的方法来删除。

第六个操作,获取表记录列表。

products := make([]*Product, 0)
var count int64
d := db.Where("code like ?", "%D%").Offset(0).Limit(2).Order("id desc").Find(&products).Offset(-1).Limit(-1).Count(&count)
if d.Error != nil {log.Fatalf("List products error: %v", d.Error)
}

在PrintProducts函数中,会打印当前的所有记录。

GORM常用操作讲解

 

模型定义

GORM使用模型(Models)来映射一个数据库表。默认情况下,使用ID作为主键,使用结构体名的 snake_cases 作为表名,使用字段名的 snake_case 作为列名,并使用 CreatedAt、UpdatedAt、DeletedAt字段追踪创建、更新和删除时间

使用GORM的默认规则,可以减少代码量,但我更喜欢的方式是直接指明字段名和表名。例如,有以下模型:

type Animal struct {AnimalID int64        // 列名 `animal_id`Birthday time.Time    // 列名 `birthday`Age      int64        // 列名 `age`
}

上述模型对应的表名为 animals ,列名分别为 animal_id 、 birthday 和 age 。我们可以通过以下方式来重命名表名和列名,并将 AnimalID 设置为表的主键:

type Animal struct {AnimalID int64     `gorm:"column:animalID;primarykey"` // 将列名设为 `animalID`Birthday time.Time `gorm:"column:birthday"`            // 将列名设为 `birthday`Age      int64     `gorm:"column:age"`                 // 将列名设为 `age`
}func (a *Animal) TableName() string {return "animal"
}

上面的代码中,通过 primaryKey 标签指定主键,使用 column 标签指定列名,通过给Models添加 TableName 方法指定表名。

数据库表通常会包含4个字段。

  • ID:自增字段,也作为主键。
  • CreatedAt:记录创建时间。
  • UpdatedAt:记录更新时间。
  • DeletedAt:记录删除时间(软删除时有用)。

GORM也预定义了包含这4个字段的Models, 例如:

type Animal struct {gorm.ModelAnimalID int64     `gorm:"column:animalID"` // 将列名设为 `animalID`Birthday time.Time `gorm:"column:birthday"` // 将列名设为 `birthday`Age      int64     `gorm:"column:age"`      // 将列名设为 `age`
}

Models中的字段能支持很多GORM标签,但如果我们不使用GORM自动创建表和迁移表结构的功能,很多标签我们实际上是用不到的。 用得最多的是 column 标签。

连接数据库

在进行数据库的CURD操作之前,我们首先需要连接数据库。你可以通过以下代码连接MySQL数据库:

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

如果需要GORM正确地处理 time.Time 类型,在连接数据库时需要带上 parseTime 参数。如果要支持完整的UTF-8编码,可将charset=utf8更改为charset=utf8mb4

GORM支持连接池,底层是用 database/sql 包来维护连接池的,连接池设置如下:

sqlDB, err := db.DB()
sqlDB.SetMaxIdleConns(10)              // 设置MySQL的最大空闲连接数(推荐100)
sqlDB.SetMaxOpenConns(100)             // 设置MySQL的最大连接数(推荐100)
sqlDB.SetConnMaxLifetime(time.Hour)    // 设置MySQL的空闲连接最大存活时间(推荐10s)

 

创建记录

我们可以通过 db.Create 方法来创建一条记录:

type User struct {gorm.ModelName         stringAge          uint8Birthday     *time.Time
}
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user) // 通过数据的指针来创建

db.Create函数会返回如下3个值:

  • user.ID:返回插入数据的主键,这个是直接赋值给user变量。
  • result.Error:返回error。
  • result.RowsAffected:返回插入记录的条数。

当需要插入的数据量比较大时,可以批量插入,以提高插入性能:

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
DB.Create(&users)for _, user := range users {user.ID // 1,2,3
}

删除记录

我们可以通过Delete方法删除记录:

// DELETE from users where id = 10 AND name = "jinzhu";
db.Where("name = ?", "jinzhu").Delete(&User{})

GORM也支持根据主键进行删除,例如:

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

不过,我更喜欢使用db.Where的方式进行删除,这种方式有两个优点。

第一个优点是删除方式更通用。使用db.Where不仅可以根据主键删除,还能够随意组合条件进行删除。

第二个优点是删除方式更显式,这意味着更易读。如果使用db.Delete(&User{}, 10),你还需要确认User的主键,如果记错了主键,还可能会引入Bug。

此外,GORM也支持批量删除:

db.Where("name in (?)", []string{"jinzhu", "colin"}).Delete(&User{})

GORM支持两种删除方法:软删除和永久删除。下面我来分别介绍下。

  1. 软删除

软删除是指执行Delete时,记录不会被从数据库中真正删除。GORM会将 DeletedAt 设置为当前时间,并且不能通过正常的方式查询到该记录。如果模型包含了一个 gorm.DeletedAt 字段,GORM在执行删除操作时,会软删除该记录。

下面的删除方法就是一个软删除:

// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
db.Where("age = ?", 20).Delete(&User{})// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
db.Where("age = 20").Find(&user)

 

我们可以通过下面的方式查找被软删除的记录:

// SELECT * FROM users WHERE age = 20;
db.Unscoped().Where("age = 20").Find(&users)
  1. 永久删除

如果想永久删除一条记录,可以使用Unscoped:

// DELETE FROM orders WHERE id=10;
db.Unscoped().Delete(&order)

或者,你也可以在模型中去掉gorm.DeletedAt。

更新记录

GORM中,最常用的更新方法如下:

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

 

还可以指定更新单个列:

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

也可以指定更新多个列:

// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE name = 'colin';
db.Model(&user).Where("name", "colin").Updates(User{Name: "hello", Age: 18, Active: false})

 这个方法只会更新非零值的字段。

查询数据

GORM支持不同的查询方法, 分别是检索单个记录、查询所有符合条件的记录和智能选择字段。

  1. 检索单个记录

下面是检索单个记录的示例代码:

// 获取第一条记录(主键升序)
// SELECT * FROM users ORDER BY id LIMIT 1;
db.First(&user)// 获取最后一条记录(主键降序)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
db.Last(&user)
result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error        // returns error// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)

如果model类型没有定义主键,则按第一个字段排序。

  1. 查询所有符合条件的记录

示例代码如下:

users := make([]*User, 0)// SELECT * FROM users WHERE name <> 'jinzhu';
db.Where("name <> ?", "jinzhu").Find(&users)
  1. 智能选择字段

你可以通过Select方法,选择特定的字段。我们可以定义一个较小的结构体来接受选定的字段:

type APIUser struct {ID   uintName string
}// SELECT `id`, `name` FROM `users` LIMIT 10;
db.Model(&User{}).Limit(10).Find(&APIUser{})

除了上面讲的三种常用的基本查询方法,GORM还支持高级查询,下面我来介绍下。

高级查询

GORM支持很多高级查询功能,这里我主要介绍4种。

  1. 指定检索记录时的排序方式

示例代码如下:

// SELECT * FROM users ORDER BY age desc, name;
db.Order("age desc, name").Find(&users)
  1. Limit & Offset

Offset指定从第几条记录开始查询,Limit指定返回的最大记录数。Offset和Limit值为-1时,消除Offset和Limit条件。另外,Limit和Offset位置不同,效果也不同。

// SELECT * FROM users OFFSET 5 LIMIT 10;
db.Limit(10).Offset(5).Find(&users)
  1. Distinct

Distinct可以从数据库记录中选择不同的值。

db.Distinct("name", "age").Order("name, age desc").Find(&results)
  1. Count

Count可以获取匹配的条数。

var count int64
// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count)
db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)

GORM还支持很多高级查询功能,比如内联条件、Not 条件、Or 条件、Group & Having、Joins、Group、FirstOrInit、FirstOrCreate、迭代、FindInBatches等。 可以看下GORM的官方文档。

原生SQL

GORM支持原生查询SQL和执行SQL。原生查询SQL用法如下:

type Result struct {ID   intName stringAge  int
}var result Result
db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)

原生执行SQL用法如下;

db.Exec("DROP TABLE users")
db.Exec("UPDATE orders SET shipped_at=? WHERE id IN ?", time.Now(), []int64{1,2,3})

GORM钩子

GORM支持钩子功能,例如下面这个在插入记录前执行的钩子:

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {u.UUID = uuid.New()if u.Name == "admin" {return errors.New("invalid name")}return
}

GORM支持的钩子见下表:

iam-apiserver中的CURD操作实战

接下来,我来介绍下iam-apiserver是如何使用GORM,对数据进行CURD操作的。

首先,我们需要配置连接MySQL的各类参数。iam-apiserver通过NewMySQLOptions函数创建了一个带有默认值的MySQLOptions类型的变量,将该变量传给NewApp函数。在App框架中,最终会调用MySQLOptions提供的AddFlags方法,将MySQLOptions提供的命令行参数添加到Cobra命令行中。

接着,在PrepareRun函数中,调用GetMySQLFactoryOr函数,初始化并获取仓库层的实例mysqlFactory。实现了仓库层store.Factory接口:

type Factory interface {Users() UserStoreSecrets() SecretStorePolicies() PolicyStoreClose() error
}

GetMySQLFactoryOr函数采用了我们在 11讲 中提过的单例模式,确保iam-apiserver进程中只有一个仓库层的实例,这样可以减少内存开支和系统的性能开销。

GetMySQLFactoryOr函数中,使用github.com/marmotedu/iam/pkg/db包提供的New函数,创建了MySQL实例。New函数代码如下:

func New(opts *Options) (*gorm.DB, error) {    dns := fmt.Sprintf(`%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s`,    opts.Username,                                                                 opts.Password,                                 opts.Host,                   opts.Database,    true,                               "Local")    db, err := gorm.Open(mysql.Open(dns), &gorm.Config{    Logger: logger.New(opts.LogLevel),                                                                             })    if err != nil {                                   return nil, err         }    sqlDB, err := db.DB()                              if err != nil {                                                                return nil, err                              }                                                                  // SetMaxOpenConns sets the maximum number of open connections to the database.sqlDB.SetMaxOpenConns(opts.MaxOpenConnections)// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.sqlDB.SetConnMaxLifetime(opts.MaxConnectionLifeTime)// SetMaxIdleConns sets the maximum number of connections in the idle connection pool.sqlDB.SetMaxIdleConns(opts.MaxIdleConnections)return db, nil
}

上述代码中,我们先创建了一个 *gorm.DB 类型的实例,并对该实例进行了如下设置:

  • 通过SetMaxOpenConns方法,设置了MySQL的最大连接数(推荐100)。
  • 通过SetConnMaxLifetime方法,设置了MySQL的空闲连接最大存活时间(推荐10s)。
  • 通过SetMaxIdleConns方法,设置了MySQL的最大空闲连接数(推荐100)。

GetMySQLFactoryOr函数最后创建了datastore类型的变量mysqlFactory,该变量是仓库层的变量。mysqlFactory变量中,又包含了 *gorm.DB 类型的字段 db 。

最终我们通过仓库层的变量mysqlFactory,调用其 db 字段提供的方法来完成数据库的CURD操作。例如,创建密钥、更新密钥、删除密钥、获取密钥详情、查询密钥列表,具体代码如下(代码位于secret.go文件中):

// Create creates a new secret.
func (s *secrets) Create(ctx context.Context, secret *v1.Secret, opts metav1.CreateOptions) error {return s.db.Create(&secret).Error
}// Update updates an secret information by the secret identifier.
func (s *secrets) Update(ctx context.Context, secret *v1.Secret, opts metav1.UpdateOptions) error {return s.db.Save(secret).Error
}// Delete deletes the secret by the secret identifier.
func (s *secrets) Delete(ctx context.Context, username, name string, opts metav1.DeleteOptions) error {if opts.Unscoped {s.db = s.db.Unscoped()}err := s.db.Where("username = ? and name = ?", username, name).Delete(&v1.Secret{}).Errorif err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {return errors.WithCode(code.ErrDatabase, err.Error())}return nil
}// Get return an secret by the secret identifier.
func (s *secrets) Get(ctx context.Context, username, name string, opts metav1.GetOptions) (*v1.Secret, error) {secret := &v1.Secret{}err := s.db.Where("username = ? and name= ?", username, name).First(&secret).Errorif err != nil {if errors.Is(err, gorm.ErrRecordNotFound) {return nil, errors.WithCode(code.ErrSecretNotFound, err.Error())}return nil, errors.WithCode(code.ErrDatabase, err.Error())}return secret, nil
}// List return all secrets.
func (s *secrets) List(ctx context.Context, username string, opts metav1.ListOptions) (*v1.SecretList, error) {ret := &v1.SecretList{}ol := gormutil.Unpointer(opts.Offset, opts.Limit)if username != "" {s.db = s.db.Where("username = ?", username)}selector, _ := fields.ParseSelector(opts.FieldSelector)name, _ := selector.RequiresExactMatch("name")d := s.db.Where(" name like ?", "%"+name+"%").Offset(ol.Offset).Limit(ol.Limit).Order("id desc").Find(&ret.Items).Offset(-1).Limit(-1).Count(&ret.TotalCount)return ret, d.Error
}

上面的代码中, s.db 就是 *gorm.DB 类型的字段。

上面的代码段执行了以下操作:

  • 通过 s.db.Save 来更新数据库表的各字段;
  • 通过 s.db.Unscoped 来永久性从表中删除一行记录。对于支持软删除的资源,我们还可以通过 opts.Unscoped 选项来控制是否永久删除记录。 true 永久删除, false 软删除,默认软删除。
  • 通过 errors.Is(err, gorm.ErrRecordNotFound) 来判断GORM返回的错误是否是没有找到记录的错误类型。
  • 通过下面两行代码,来获取查询条件name的值:
selector, _ := fields.ParseSelector(opts.FieldSelector)    
name, _ := selector.RequiresExactMatch("name")

我们的整个调用链是:控制层 -> 业务层 -> 仓库层。这里你可能要问:我们如何调用仓库层的实例mysqlFactory呢?

这是因为我们的控制层实例包含了业务层的实例。在创建控制层实例时,我们传入了业务层的实例:

type UserController struct {                                        srv srvv1.Service                                                      
}                                                                          // NewUserController creates a user handler.          
func NewUserController(store store.Factory) *UserController {return &UserController{                                     srv: srvv1.NewService(store),                                             }                                                      
} 

业务层的实例包含了仓库层的实例。在创建业务层实例时,传入了仓库层的实例:

type service struct {                                                      store store.Factory                                                                     
}                                                     // NewService returns Service interface.                        
func NewService(store store.Factory) Service {                                    return &service{                                       store: store,                                             }
}

通过这种包含关系,我们在控制层可以调用业务层的实例,在业务层又可以调用仓库层的实例。这样,我们最终通过仓库层实例的 db 字段(*gorm.DB 类型)完成数据库的CURD操作。

总结

在Go项目中,我们需要使用ORM来进行数据库的CURD操作。在Go生态中,当前最受欢迎的ORM是GORM,IAM项目也使用了GORM。 这些常用功能的常见使用方式如下:

package mainimport ("fmt""log""github.com/spf13/pflag""gorm.io/driver/mysql""gorm.io/gorm"
)type Product struct {gorm.ModelCode  string `gorm:"column:code"`Price uint   `gorm:"column:price"`
}// TableName maps to mysql table name.
func (p *Product) TableName() string {return "product"
}var (host     = pflag.StringP("host", "H", "127.0.0.1:3306", "MySQL service host address")username = pflag.StringP("username", "u", "root", "Username for access to mysql service")password = pflag.StringP("password", "p", "root", "Password for access to mysql, should be used pair with password")database = pflag.StringP("database", "d", "test", "Database name to use")help     = pflag.BoolP("help", "h", false, "Print this help message")
)func main() {// Parse command line flagspflag.CommandLine.SortFlags = falsepflag.Usage = func() {pflag.PrintDefaults()}pflag.Parse()if *help {pflag.Usage()return}dns := fmt.Sprintf(`%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s`,*username,*password,*host,*database,true,"Local")db, err := gorm.Open(mysql.Open(dns), &gorm.Config{})if err != nil {panic("failed to connect database")}// 1. Auto migration for given modelsdb.AutoMigrate(&Product{})// 2. Insert the value into databaseif err := db.Create(&Product{Code: "D42", Price: 100}).Error; err != nil {log.Fatalf("Create error: %v", err)}PrintProducts(db)// 3. Find first record that match given conditionsproduct := &Product{}if err := db.Where("code= ?", "D42").First(&product).Error; err != nil {log.Fatalf("Get product error: %v", err)}// 4. Update value in database, if the value doesn't have primary key, will insert itproduct.Price = 200if err := db.Save(product).Error; err != nil {log.Fatalf("Update product error: %v", err)}PrintProducts(db)// 5. Delete value match given conditionsif err := db.Where("code = ?", "D42").Delete(&Product{}).Error; err != nil {log.Fatalf("Delete product error: %v", err)}PrintProducts(db)
}// List products
func PrintProducts(db *gorm.DB) {products := make([]*Product, 0)var count int64d := db.Where("code like ?", "%D%").Offset(0).Limit(2).Order("id desc").Find(&products).Offset(-1).Limit(-1).Count(&count)if d.Error != nil {log.Fatalf("List products error: %v", d.Error)}log.Printf("totalcount: %d", count)for _, product := range products {log.Printf("\tcode: %s, price: %d\n", product.Code, product.Price)}
}

此外,GORM还支持原生查询SQL和原生执行SQL,可以满足更加复杂的SQL场景。

GORM中,还有一个非常有用的功能是支持Hooks。Hooks可以在执行某个CURD操作前被调用。在Hook中,可以添加一些非常有用的功能,例如生成唯一ID。目前,GORM支持 BeforeXXX 、 AfterXXX 和 AfterFind Hook,其中 XXX 可以是 Save、Create、Delete、Update。

对标java的mybatis的功能,常见的sql功能都支持,对比学习是快速入门的法宝。

课后练习

  1. GORM支持AutoMigrate功能,思考下,你的生产环境是否可以使用AutoMigrate功能,为什么?
  2. 查看GORM官方文档,看下如何用GORM实现事务回滚功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/789263.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从零开始:Flutter应用上架iOS的完整流程解析

引言 &#x1f680; Flutter作为一种跨平台的移动应用程序开发框架&#xff0c;为开发者提供了便利&#xff0c;使他们能够通过单一的代码库构建出高性能、高保真度的应用程序&#xff0c;同时支持Android和iOS两个平台。然而&#xff0c;完成Flutter应用程序的开发只是第一步…

Linux安装Apache保姆级教程

文章目录 前言一、安装Apache1.安装Apache2.开启服务3.查看服务的状态4.网络请求测试5.将服务设置为自启 二、外部电脑访问Apache设置方法一&#xff1a;关闭防火墙方法二&#xff1a;添加防火墙端口&#xff08;Apache默认端口&#xff1a;80&#xff0c;如果修改了Apache端口…

边缘网关在智能制造工厂中的创新应用及其带来的显著成效-天拓四方

在数字化浪潮席卷之下&#xff0c;智能制造工厂正面临着前所未有的数据挑战与机遇。边缘网关&#xff0c;作为数据处理与传输的关键节点&#xff0c;在提升工厂运营效率、确保数据安全方面发挥着日益重要的作用。本文将通过一个具体案例&#xff0c;详细阐述边缘网关在智能制造…

[C语言实现]数据结构二叉树之《我种下的树会为我遮阳挡雨》

&#x1f970;作者: FlashRider &#x1f30f;专栏: 初阶数据结构 &#x1f356;知识概要&#xff1a;详解二叉树的概念、二叉树的遍历、以及代码实现。 目录 树的基本概念 树的存储结构与二叉树的实现 树的存储 什么是二叉树 二叉链存储二叉树 二叉树的代码实现 树的基本…

车载电子电器架构 —— 局部网络管理汇总

车载电子电器架构 —— 局部网络管理汇总 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明…

黄金票据的复现

实验环境以及工具 服务器&#xff1a;Windows server 2003 用户&#xff1a;Windows 7旗舰版 工具&#xff1a;mimikatz 搭建服务器环境 参考&#xff1a;内网横向——域渗透之黄金票据复现-CSDN博客 创建用户 使用gpupdate刷新策略&#xff1b; 搭建win7环境 设置ip ‘…

二维相位解包理论算法和软件【全文翻译-将相位分解为 “非旋转 “和 “旋转 “(2.4)】

2.4 将相位分解为 "非旋转 "和 "旋转 "部分 借用电磁场理论,可以用发散和卷曲来指定矢量场[9][10]。当且仅当矢量函数 F(r)(以及由其描述的场)在整个域 D 中不旋转或无旋转时,我们称之为矢量函数 F(r)、 因此,如果等式 2.30(也是第 2.2 节关于路径…

揭秘糖尿病患者稳定控制血糖的关键!

患者在就诊之前一直使用的二甲双胍和达格列净这两种降糖药物&#xff0c;这两种药对于控制血糖是有一定效果的。北京崇文门医院朱学敏主任的建议是继续服用&#xff0c;然后患者空腹血糖在7-8mmol/L左右&#xff0c;餐后血糖稍高&#xff0c;达到9-10mmol/L&#xff0c;但总体上…

单链表求集合的交集

#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> typedef int ElemType; typedef struct LinkNode {ElemType data;LinkNode* next; }LinkNode, * LinkList; //尾插法建立单链表 void creatLinkList(LinkList& L) {L (LinkNode*)mallo…

外汇110:交易中,是否真的存在确定性?

我们看问题的角度不同&#xff0c;得到的结果必然也是不一样的。我们不能否认任何一种可能性&#xff0c;但一切需要从逻辑出发。交易中&#xff0c;最大的确定性就是市场是不确定的&#xff0c;什么样的行情都可能发生。当然&#xff0c;绝对的确定性是不存在的&#xff0c;但…

nginx配置多vue项目

1. 找到linux docker安装好的nginx目录文件 进入nginx内 把打包好的vue项目放在html文件下 如上 三个文件夹下对应着三个不同的vue项目 2. 配置default.conf的配置文件&#xff0c; 一个nginx配置文件可以多个项目进行代理 进入到conf 找到conf.d下面的default.conf 文件…

09. 【Android教程】表格布局 TableLayout

学完了 Android 两个经典布局&#xff0c;是不是觉得已经可以应对大多数场景了&#xff1f;我记得当我学完 LinearLayout 和 RelativeLayout 之后&#xff0c;我觉得 UI 布局已经可以出师了&#xff0c;在本人从事了多年的 Android 研究之后&#xff0c;可以很负责任的告诉你&a…

STM32 uC/OS-III

What is uC/OS-III? C/OS-III 的发音为“Micro C O S Three”&#xff0c;这意味着 C/OS-III 是基于 C 语言编写的第三代 小型操作系统&#xff0c;当然这里所说的第三代是相对于 C/OS 的前两个版本 C/OS 和 C/OS-II 而言 的&#xff0c;后面也会介绍这三个版本的差别。C/OS/…

第117讲:深入MySQL性能优化:从多个角度提升数据库性能

文章目录 1.从哪些角度去考虑MySQL的优化2.数据库服务器的选型3.从操作系统层面去优化MySQL数据库3.1.关于CPU方面的优化3.2.关于内存方面的优化3.3.关于磁盘IO方面 4.应用端的优化5.数据库系统优化工具6.数据库系统参数优化6.1.最大连接数的优化&#xff08;max_connections&a…

Qt使用opencv打开摄像头

1.效果图 2.代码 #include "widget.h"#include <QApplication>#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp>#include <QImage> #include <QLabel> #incl…

hadoop 高可用(HA)、HDFS HA、Yarn HA

目录 hadoop 高可用(HA) HDFS高可用 HDFS高可用架构 QJM 主备切换&#xff1a; Yarn高可用 hadoop 高可用(HA) HDFS高可用 HDFS高可用架构 QJM 主备切换&#xff1a; Yarn高可用

【威胁情报综述阅读3】Cyber Threat Intelligence Mining for Proactive Cybersecurity Defense

【威胁情报综述阅读1】Cyber Threat Intelligence Mining for Proactive Cybersecurity Defense: A Survey and New Perspectives 写在最前面一、介绍二、网络威胁情报挖掘方法和分类A. 研究方法1&#xff09; 第 1 步 - 网络场景分析&#xff1a;2&#xff09; 第 2 步 - 数据…

python文件处理:解析docx/word文件文字、图片、复选框

前言 因为一些项目原因&#xff0c;我需要提供解析docx内容功能。本来以为这是一件比较简单的工作&#xff0c;没想到在解析复选框选项上吃了亏&#xff0c;并且较长一段时间内通过各种渠道都没有真正解决这一问题&#xff0c;反而绕了远路。 终于&#xff0c;我在github pytho…

2024最新telegram电报模块化机器人TG飞机混合开发的机器人框架

更新日记&#xff1a;24-03-10 优化服务框架回复地址 金额 等交互模式优化修复一些免费莫名被卸载模块问题修复收费模块续费后未到期被卸载问题框架增加一些方法,详细最近会出各种开发教程TRX兑换增加机器人上管理功能,自动开会员上线,能量即将上线 更新日志24-02-25 增加电…

golang和Java的简单介绍和对比

一、golang 1、Golang简介 Golang&#xff0c;也称为Go&#xff0c;是由Google公司在2009年推出的开源编程语言&#xff0c;由罗伯特格瑞史莫(Rob Pike)、肯汤普逊(Ken Thompson)、罗勃派克(Robert Griesemer)等人设计。Go语言的目标是在保持简单高效的编程模型的同时&#xf…