在 Go 项目中优雅的使用 gorm v2


本文摘自网络,作者,侵删。

本文基于 gorm v2 版本

连接数据库

Go 里面也不用整什么单例了,直接用私有全局变量。

func Connect(cfg *DBConfig) {
    dsn := fmt.Sprintf(
        "%s?charset=utf8&parseTime=True&loc=Local",
        cfg.DSN,
    )
    log.Debugf("db dsn: %s", dsn)
    
    var ormLogger logger.Interface
    if cfg.Debug {
      ormLogger = logger.Default.LogMode(logger.Info)
    } else {
      ormLogger = logger.Default
    }
    
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
            Logger: ormLogger,
            NamingStrategy: schema.NamingStrategy{
            TablePrefix: "tb_", // 表名前缀,`User` 对应的表名是 `tb_users`
        },
    })
    if err != nil {
        log.Fatal(err)
    }
    
    globalDB = db
    
    log.Info("db connected success")
}

调用方使用 GetDB 从 globalDB 获取 gorm.DB 进行 CURD。WithContext 实际是调用 db.Session(&Session{Context: ctx}),每次创建新 Session,各 db 操作之间互不影响:

func GetDB(ctx context.Context) *gorm.DB {
    return globalDB.WithContext(ctx)
}

自动创建数据表

一般测试环境才这么玩,生产上推荐交给 DBA 处理,应用使用低权限账号

gorm 提供 db.AutoMigrate(model) 方法自动建表 。现在我们想要实现数据库初始化后执行 AutoMigrate,并且可配置关闭 AutoMigrate

项目中一般每个表一个 go 文件,model 相关的 CURD 都在一个文件中。如果用 init 初始化,则 db 必须在 init 执行前初始化,否则 init 执行时 db 还未初始。 使用 init 函数不是一个好的实践,一个包中多个 init 函数的执行顺序也是个坑。不用 init 则需要主动去调用每个表的初始化。有没有更好的方法呢?这里可以使用回调函数实现依赖反转,使用 init 注册回调函数,在 db 初始化之后再去执行所有回调函数,达到延迟执行的目的。代码如下:

var injectors []func(db *gorm.DB)

// 注册回调
func RegisterInjector(f func(*gorm.DB)) {
    injectors = append(injectors, f)
}

// 执行回调
func callInjector(db *gorm.DB) {
    for _, v := range injectors {
        v(db)
    }
}

// 自动初始化表结构
func SetupTableModel(db *gorm.DB, model interface{}) {
    if GetDBConfig().AutoMigrate {
        err := db.AutoMigrate(model)
        if err != nil {
            log.Fatal(err)
        }
    }
}
// 调用方
func init() {
    dbcore.RegisterInjector(func(db *gorm.DB) {
        dbcore.SetupTableModel(db, &petmodel.Pet{})
    })
}

自动创建数据库

gorm 没有提供自动创建数据库的方法,这个我们通过 CREATE DATABASE IF NOT EXISTS SQL 语句来实现也非常简单:

func CreateDatabase(cfg *DBConfig) {
    slashIndex := strings.LastIndex(cfg.DSN, "/")
    dsn := cfg.DSN[:slashIndex+1]
    dbName := cfg.DSN[slashIndex+1:]

    dsn = fmt.Sprintf("%s?charset=utf8&parseTime=True&loc=Local", dsn)
    db, err := gorm.Open(mysql.Open(dsn), nil)
    if err != nil {
        log.Fatal(err)
    }
    
    createSQL := fmt.Sprintf(
        "CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET utf8mb4;",
        dbName,
    )

    err = db.Exec(createSQL).Error
    if err != nil {
        log.Fatal(err)
    }
}

通过 Context 传递事务

在 DAO 层我们一般会封装对 model 增删改查等基本操作。每个方法都需要 db 作为参数,所以我们用面向对象的方式做一下封装。如下:

type petDb struct {
    db *gorm.DB
}

func NewPetDb(ctx context.Context) struct {
    return GetDB(ctx)
}

func (s *petDb) Create(in *petmodel.Pet) error {
    return s.db.Create(in).Err
}

func (s *petDb) Update(in *petmodel.Pet) error {
    return s.db.Updates(in).Err
}

事务一般是在 Service 层,如果现在需要将多个 CURD 调用组成事务,如何复用 DAO 层的逻辑?我们很容易想到将 tx 作为参数传递到 DAO 层方法中即可。

阅读剩余部分

相关阅读 >>

Golang读取文本乱码解决方法

Go无缓冲通道的陷阱

Golang官方嵌入文件到可执行程序

常见的 Go 处理字符串的技巧

Golang和erlang区别

Go 大数据生态迎来重要产品 cds

Golang不规则json解析

手撸Golang 架构设计原则 单一职责原则

聊聊cortex的readring

交叉编译arm版tcping

更多相关阅读请进入《Go》频道 >>




打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...