【golang】Gorm框架的简单使用

463 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情

前言

Gormgo的一个ORM框架,官方文档地址为-> GORM 指南

本文将介绍与gorm有关的CRUD操作,操作数据库类型为mysql数据库

数据库连接

func Open(dialector Dialector, opts ...Option) (db *DB, err error)

该函数用于进行gorm连接对应数据库

输入 go get -u gorm.io/driver/mysql 即可

对于mysql的连接,还需要引入gorm的mysql驱动,否则没有Open这个函数

 var gormDb *gorm.DB
 
 func initGormDB() {
     var err error
     gormDb, err = gorm.Open(
         mysql.Open("root:xxxxxx@tcp(127.0.0.1:3306)/go_db?charset=utf8mb4&parseTime=True&loc=Local"),
         &gorm.Config{
             PrepareStmt:            true, //缓存预编译命令
             SkipDefaultTransaction: true, //禁用默认事务操作
         },
     )
     if err != nil {
         // 这里因为是panic所以可以直接进行赋值操作,如果不是panic需要在错误之后才能进行赋值给全局变量
         panic(err)
     }
     fmt.Println("gormDb connection success~")
 }

下图将给出gormConfig的配置参数

image.png

支持自定义logger,但是需要实现对应接口的方法

 // Interface logger interface
 type Interface interface {
    LogMode(LogLevel) Interface
    Info(context.Context, string, ...interface{})
    Warn(context.Context, string, ...interface{})
    Error(context.Context, string, ...interface{})
    Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error)
 }

因为gorm底层是通过使用 database/sql 维护连接池

因此可以获取sqlDB,然后设置连接池

 sqlDB, err := gormDb.DB()
 
 // SetMaxIdleConns 设置空闲连接池中连接的最大数量
 sqlDB.SetMaxIdleConns(10)
 
 // SetMaxOpenConns 设置打开数据库连接的最大数量。
 sqlDB.SetMaxOpenConns(100)
 
 // SetConnMaxLifetime 设置了连接可复用的最大时间。
 sqlDB.SetConnMaxLifetime(time.Hour)

数据库迁移

下图是gorm中关于数据库迁移的源代码,给出了自动迁移和手动迁移两种方案,而自动迁移其实和手动迁移没有区别。

 // Migrator returns migrator
 func (db *DB) Migrator() Migrator {
    tx := db.getInstance()
 
    // apply scopes to migrator
    for len(tx.Statement.scopes) > 0 {
       scopes := tx.Statement.scopes
       tx.Statement.scopes = nil
       for _, scope := range scopes {
          tx = scope(tx)
       }
    }
 ​
    return tx.Dialector.Migrator(tx.Session(&Session{}))
 }
 ​
 // AutoMigrate run auto migration for given models
 func (db *DB) AutoMigrate(dst ...interface{}) error {
    return db.Migrator().AutoMigrate(dst...)
 }

迁移数据库

结构体中要嵌套gormModel结构体,用于管理软删除和时间

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

Model结构体中包括自增的主键ID,创建、更新、删除时间

 type GormUser struct {
    gorm.Model
     Age      int    `gorm:"age"`
     Name     string `gorm:"name"`
     Password string `gorm:"password"`
 }
 
 // Migrant 数据库迁移
 func Migrant() error {
    return gormDb.AutoMigrate(&GormUser{})
 }

注意: 在结构体tag处可以自定义gorm中的字段名来指定生成数据库表名。我这里只做演示操作,具体数据与实际开发并不相符。

测试

使用 go test -run=Migrant -v 该命令可以详细打印出测试结果

 func TestMain(m *testing.M) {
    initGormDB()
    code := m.Run()
    os.Exit(code)
 }
 

 func TestMigrant(t *testing.T) {
    err := Migrant()
    if err != nil {
       t.Errorf("Migrant fail,%v", err)
    }
 }

image.png

CRUD实战

创建

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

增加一条用户信息

 // GormCreate gorm 创建数据
 func GormCreate() error {
    u := GormUser{
       Name:     "张三",
       Age:      15,
       Password: "qwer",
    }
    return gormDb.Create(&u).Error
 }

image.png

测试

go test -run=GormCreate -v

 func TestGormCreate(t *testing.T) {
    err := GormCreate()
    if err != nil {
       t.Errorf("Migrant fail,%v", err)
    }
 }

image.png

增加多条用户信息

通过切片或者数组的方式插入多条数据

 // GormCreateMore gorm 创建多条数据
 func GormCreateMore() error {
    u := GormUser{
       Name:     "张三",
       Age:      15,
       Password: "qwer",
    }
    u2 := GormUser{
       Name:     "李四",
       Age:      16,
       Password: "qwer",
    }
    u3 := GormUser{
       Name:     "王五",
       Age:      17,
       Password: "qwer",
    }
    uu := [...]GormUser{u, u2, u3}
    return gormDb.Create(&uu).Error
 }

测试

 func TestGormCreateMore(t *testing.T) {
    err := GormCreateMore()
    if err != nil {
       t.Errorf("Migrant fail,%v", err)
    }
 }

image.png

删除

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

gorm中有两种删除方法

  • 硬删除,直接通过结构体中的字段值进行删除
  • 条件删除,先进行where查询然后删除
 // GormDelete gorm 删除数据
 func GormDelete() error {
    // 直接删除
    gormDb.Delete(&GormUser{Name: "张三"})
    // 条件删除 
    return gormDb.Where("name = ?", "张三").Delete(&GormUser{}).Error
 }

测试

这里显示错误是因为我调用了两个不同的删除函数。

 func TestGormDelete(t *testing.T) {
    err := GormDelete()
    if err != nil {
       t.Errorf("Migrant fail,%v", err)
    }
 }

image.png

gorm中删除使用的是软删除,好处是不会产生碎片,方便数据的维护。在应用程序中使用软删除的另一个好处是可以恢复无意的删除和保留历史记录

image.png

修改

修改的方法有四种,两种批量修改,两种单行修改。也可以通过保存等操作进行修改。

image.png

保存修改

 u := GormUser{}
 //先查询一条记录, 保存在模型变量u
 //等价于: SELECT * FROM `gorm_users`  WHERE (id = '2') LIMIT 1
 db.Where("id = ?", 3).Take(&u)
 u.age = 100
 db.Save(&u)

直接修改

Model用于绑定操作的表名。因为数据库中的表是根据结构体进行迁移生成,因此可以通过结构体来绑定表名。

 gormDb.Model(&GormUser{}).Update("age", 25).Where("id", 3).Error

image.png

显然该操作不行,根据程序报错显示该操作没有where语句,因此可以得出where语句必须要在Update函数之前。所以我们可以再尝试一次并验证一下结论。

 gormDb.Model(&GormUser{}).Where("id", 3).Update("age", 25).Error

image.png

根据指定结构体确定表明,然后添加查询语句Where,进行对应的字段更新。

查询

func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB) First finds the first record ordered by primary key, matching given conditions conds

First 用来查询单条数据,对所有数据进行正序排序并且放回第一条

等价于:SELECT * FROM gorm_users ORDER BY gorm_users.id ASC LIMIT 1

 // GormQuery gorm 查询数据
 func GormQuery(u *GormUser) error {
    return gormDb.First(u).Error
     // gormDb.Last(u).Error Last与First刚好相反(逆序排列),就不再测试了
 }

测试

 func TestGormQuery(t *testing.T) {
    u := GormUser{}
    err := GormQuery(&u)
    if err != nil {
       t.Errorf("gormQuery fail,%v", err)
    }
    t.Logf("gormUser first, data: %v", u)
 }

image.png

func (db *DB) Take(dest interface{}, conds ...interface{}) (tx *DB) Take finds the first record returned by the database in no specified order, matching given conditions conds

Take 查询单条

等价于:SELECT * FROM gorm_users LIMIT 1

指定查询单条数据并返回查询行数

 // GormQuery gorm 查询数据
 func GormQuery(u *GormUser) (cnt int64, err error) {
    err = gormDb.Where("age = ?", 18).Count(&cnt).First(u).Error
    return
 }

image.png

根据查询提示,查询失败的原因是没有使用Model或者Table函数,提前指定表名,因此无法查询出具体的结果。因此我们可以加上之后可以再次尝试。

 // GormQuery gorm 查询数据
 func GormQuery(u *GormUser) (cnt int64, err error) {
    err = gormDb.Model(&GormUser{}).Where("age = ?", 18).Count(&cnt).First(u).Error
    return
 }

测试

 func TestGormQuery(t *testing.T) {
    u := GormUser{}
    rowNum, err := GormQuery(&u)
    if err != nil {
       t.Errorf("gormQuery fail,%v", err)
    }
    t.Logf("gormUser first, data: %v\n", u)
    t.Logf("gormQuery method queryRows = %d", rowNum)
 }

image.png

结果显示查到了三条数据,但由于我使用的First函数,因此只返回一条数据。

查询多条数据

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

Find finds all records matching given conditions conds

Find 用于查询多条数据

 // GormQueryMore gorm 查询多条数据
 func GormQueryMore(u []*GormUser) (cnt int64, err error) {
    err = gormDb.Model(&GormUser{}).Where("age = ?", 18).Count(&cnt).Find(u).Error
    return
 }

测试

 func TestGormQueryMore(t *testing.T) {
    u := make([]*GormUser, 3)
    rowNum, err := GormQueryMore(u)
    if err != nil {
       t.Errorf("gormQuery fail,%v", err)
    }
    for i, v := range u {
       t.Logf("gormUser first,第%d条数据, data: %v\n", i, v)
    }
    t.Logf("gormQuery method queryRows = %d", rowNum)
 }

image.png

小结

gorm底层仍然是database/sql,因此原生的sql方法仍然能够被使用。对于gorm的CRUD简单操作就介绍到这里了。如果感兴趣的话可以到gorm的官网更深入的研究。