从零封装Gin框架实现数据库初始化GORM

 更新时间:2024年01月31日 11:06:22   作者:生活处处有BUG  
这篇文章主要为大家介绍了从零封装Gin框架实现数据库初始化GORM,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

前言

许多框架都会引入 ORM 模型来表示模型类和数据库表的映射关系,这一篇将使用 gorm[1] 作为 ORM 库,它遵循了 ActiveRecord(模型与数据库表一一对应) 模式,并且提供了强大的功能,例如模型关联、关联预加载、数据库迁移等,更多内容查看官方文档[2]

安装

go get -u gorm.io/gorm

# GORM 官方支持 sqlite、mysql、postgres、sqlserver
go get -u gorm.io/driver/mysql 

定义配置项

新建 config/database.go 文件,自定义配置项

package config

type Database struct {
    Driver string `mapstructure:"driver" json:"driver" yaml:"driver"`
    Host string `mapstructure:"host" json:"host" yaml:"host"`
    Port int `mapstructure:"port" json:"port" yaml:"port"`
    Database string `mapstructure:"database" json:"database" yaml:"database"`
    UserName string `mapstructure:"username" json:"username" yaml:"username"`
    Password string `mapstructure:"password" json:"password" yaml:"password"`
    Charset string `mapstructure:"charset" json:"charset" yaml:"charset"`
    MaxIdleConns int `mapstructure:"max_idle_conns" json:"max_idle_conns" yaml:"max_idle_conns"`
    MaxOpenConns int `mapstructure:"max_open_conns" json:"max_open_conns" yaml:"max_open_conns"`
    LogMode string `mapstructure:"log_mode" json:"log_mode" yaml:"log_mode"`
    EnableFileLogWriter bool `mapstructure:"enable_file_log_writer" json:"enable_file_log_writer" yaml:"enable_file_log_writer"`
    LogFilename string `mapstructure:"log_filename" json:"log_filename" yaml:"log_filename"`
}

config/config.go 添加 Database 成员属性

package config

type Configuration struct {
    App App `mapstructure:"app" json:"app" yaml:"app"`
    Log Log `mapstructure:"log" json:"log" yaml:"log"`
    Database Database `mapstructure:"database" json:"database" yaml:"database"`
}

config.yaml 增加对应配置项

database:
  driver: mysql # 数据库驱动
  host: 127.0.0.1 # 域名
  port: 3306 # 端口号
  database: go-test # 数据库名称
  username: root # 用户名
  password: root # 密码
  charset: utf8mb4 # 编码格式
  max_idle_conns: 10 # 空闲连接池中连接的最大数量
  max_open_conns: 100 # 打开数据库连接的最大数量
  log_mode: info # 日志级别
  enable_file_log_writer: true # 是否启用日志文件
  log_filename: sql.log # 日志文件名称

自定义 Logger(使用文件记录日志)

gorm 有一个默认的 logger[3] ,由于日志内容是输出到控制台的,我们需要自定义一个写入器,将默认logger.Writer 接口的实现切换为自定义的写入器,上一篇引入了 lumberjack ,将继续使用它。

新建 bootstrap/db.go 文件,编写 getGormLogWriter 函数:

package bootstrap
import (
    "gopkg.in/natefinch/lumberjack.v2"
    "gorm.io/gorm/logger"
    "io"
    "jassue-gin/global"
    "log"
    "os"
)
// 自定义 gorm Writer
func getGormLogWriter() logger.Writer {
    var writer io.Writer
    // 是否启用日志文件
    if global.App.Config.Database.EnableFileLogWriter {
        // 自定义 Writer
        writer = &lumberjack.Logger{
            Filename:   global.App.Config.Log.RootDir + "/" + global.App.Config.Database.LogFilename,
            MaxSize:    global.App.Config.Log.MaxSize,
            MaxBackups: global.App.Config.Log.MaxBackups,
            MaxAge:     global.App.Config.Log.MaxAge,
            Compress:   global.App.Config.Log.Compress,
        }
    } else {
        // 默认 Writer
        writer = os.Stdout
    }
    return log.New(writer, "\r\n", log.LstdFlags)
}

接下来,编写 getGormLogger 函数, 切换默认 Logger 使用的 Writer

func getGormLogger() logger.Interface {
    var logMode logger.LogLevel
    switch global.App.Config.Database.LogMode {
    case "silent":
        logMode = logger.Silent
    case "error":
        logMode = logger.Error
    case "warn":
        logMode = logger.Warn
    case "info":
        logMode = logger.Info
    default:
        logMode = logger.Info
    }
    return logger.New(getGormLogWriter(), logger.Config{
        SlowThreshold:             200 * time.Millisecond, // 慢 SQL 阈值
        LogLevel:                  logMode, // 日志级别
        IgnoreRecordNotFoundError: false, // 忽略ErrRecordNotFound(记录未找到)错误
        Colorful:                  !global.App.Config.Database.EnableFileLogWriter, // 禁用彩色打印
    })
}

至此,自定义 Logger 就已经实现了,这里只简单替换了 logger.Writer 的实现,大家可以根据各自的需求做其它定制化配置

初始化数据库

在 bootstrap/db.go 文件中,编写 InitializeDB 初始化数据库函数,以便于在 main.go 中调用

package bootstrap
import (
    "gopkg.in/natefinch/lumberjack.v2"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
    "io"
    "jassue-gin/global"
    "log"
    "os"
    "strconv"
    "time"
)
func InitializeDB() *gorm.DB {
    // 根据驱动配置进行初始化
    switch global.App.Config.Database.Driver {
    case "mysql":
       return initMySqlGorm()
    default:
       return initMySqlGorm()
    }
}
// 初始化 mysql gorm.DB
func initMySqlGorm() *gorm.DB {
    dbConfig := global.App.Config.Database
    if dbConfig.Database == "" {
        return nil
    }
    dsn := dbConfig.UserName + ":" + dbConfig.Password + "@tcp(" + dbConfig.Host + ":" + strconv.Itoa(dbConfig.Port) + ")/" +
        dbConfig.Database + "?charset=" + dbConfig.Charset +"&parseTime=True&loc=Local"
    mysqlConfig := mysql.Config{
        DSN:                       dsn,   // DSN data source name
        DefaultStringSize:         191,   // string 类型字段的默认长度
        DisableDatetimePrecision:  true,  // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
        DontSupportRenameIndex:    true,  // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
        DontSupportRenameColumn:   true,  // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
        SkipInitializeWithVersion: false, // 根据版本自动配置
    }
    if db, err := gorm.Open(mysql.New(mysqlConfig), &gorm.Config{
        DisableForeignKeyConstraintWhenMigrating: true, // 禁用自动创建外键约束
        Logger: getGormLogger(), // 使用自定义 Logger
    }); err != nil {
        global.App.Log.Error("mysql connect failed, err:", zap.Any("err", err))
        return nil
    } else {
        sqlDB, _ := db.DB()
        sqlDB.SetMaxIdleConns(dbConfig.MaxIdleConns)
        sqlDB.SetMaxOpenConns(dbConfig.MaxOpenConns)
        return db
    }
}
func getGormLogger() logger.Interface {
    var logMode logger.LogLevel
    switch global.App.Config.Database.LogMode {
    case "silent":
        logMode = logger.Silent
    case "error":
        logMode = logger.Error
    case "warn":
        logMode = logger.Warn
    case "info":
        logMode = logger.Info
    default:
        logMode = logger.Info
    }
    return logger.New(getGormLogWriter(), logger.Config{
        SlowThreshold:             200 * time.Millisecond, // 慢 SQL 阈值
        LogLevel:                  logMode, // 日志级别
        IgnoreRecordNotFoundError: false, // 忽略ErrRecordNotFound(记录未找到)错误
        Colorful:                  !global.App.Config.Database.EnableFileLogWriter, // 禁用彩色打印
    })
}
// 自定义 gorm Writer
func getGormLogWriter() logger.Writer {
    var writer io.Writer
    // 是否启用日志文件
    if global.App.Config.Database.EnableFileLogWriter {
        // 自定义 Writer
        writer = &lumberjack.Logger{
            Filename:   global.App.Config.Log.RootDir + "/" + global.App.Config.Database.LogFilename,
            MaxSize:    global.App.Config.Log.MaxSize,
            MaxBackups: global.App.Config.Log.MaxBackups,
            MaxAge:     global.App.Config.Log.MaxAge,
            Compress:   global.App.Config.Log.Compress,
        }
    } else {
        // 默认 Writer
        writer = os.Stdout
    }
    return log.New(writer, "\r\n", log.LstdFlags)
}

编写模型文件进行数据库迁移

新建 app/models/common.go 文件,定义公用模型字段

package models

import (
    "gorm.io/gorm"
    "time"
)

// 自增ID主键
type ID struct {
    ID uint `json:"id" gorm:"primaryKey"`
}

// 创建、更新时间
type Timestamps struct {
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

// 软删除
type SoftDeletes struct {
    DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
}

新建 app/models/user.go 文件,定义 User 模型

package models

type User struct {
    ID
    Name string `json:"name" gorm:"not null;comment:用户名称"`
    Mobile string `json:"mobile" gorm:"not null;index;comment:用户手机号"`
    Password string `json:"password" gorm:"not null;default:'';comment:用户密码"`
    Timestamps
    SoftDeletes
}

在 bootstrap/db.go 文件中,编写数据库表初始化代码

func initMySqlGorm() *gorm.DB {
    // ...
    if db, err := gorm.Open(mysql.New(mysqlConfig), &gorm.Config{
        DisableForeignKeyConstraintWhenMigrating: true, // 禁用自动创建外键约束
        Logger: getGormLogger(), // 使用自定义 Logger
    }); err != nil {
        return nil
    } else {
        sqlDB, _ := db.DB()
        sqlDB.SetMaxIdleConns(dbConfig.MaxIdleConns)
        sqlDB.SetMaxOpenConns(dbConfig.MaxOpenConns)
        initMySqlTables(db)
        return db
    }
}

// 数据库表初始化
func initMySqlTables(db *gorm.DB) {
    err := db.AutoMigrate(
        models.User{},
    )
    if err != nil {
        global.App.Log.Error("migrate table failed", zap.Any("err", err))
        os.Exit(0)
    }
}

定义全局变量 DB

在 global/app.go 中,编写:

package global

import (
    "github.com/spf13/viper"
    "go.uber.org/zap"
    "gorm.io/gorm"
    "jassue-gin/config"
)

type Application struct {
    ConfigViper *viper.Viper
    Config config.Configuration
    Log *zap.Logger
    DB *gorm.DB
}

var App = new(Application)

测试

在 main.go 中调用数据库初始化函数

package main

import (
    "github.com/gin-gonic/gin"
    "jassue-gin/bootstrap"
    "jassue-gin/global"
    "net/http"
)

func main() {
    // 初始化配置
    bootstrap.InitializeConfig()

    // 初始化日志
    global.App.Log = bootstrap.InitializeLog()
    global.App.Log.Info("log init success!")

    // 初始化数据库
    global.App.DB = bootstrap.InitializeDB()
    // 程序关闭前,释放数据库连接
    defer func() {
        if global.App.DB != nil {
            db, _ := global.App.DB.DB()
            db.Close()
        }
    }()

    r := gin.Default()

    // 测试路由
    r.GET("/ping", func(c *gin.Context) {
        c.String(http.StatusOK, "test gorm")
    })

    // 启动服务器
    r.Run(":" + global.App.Config.App.Port)
}

启动 main.go ,由于我还没有创建 go-test 数据库,并且调整了 logger.Writer 为 lumberjack,所以会生成 storage/logs/sql.log 文件,文件内容如下:

2021/10/13 19:17:47 /Users/sujunjie/go/src/jassue-gin/bootstrap/db.go:44
[error] failed to initialize database, got error Error 1049: Unknown database 'go-test'

创建 go-test 数据库,重新启动 main.go ,users 表创建成功。

以上就是从零封装Gin框架实现数据库初始化GORM的详细内容,更多关于Gin GORM数据库初始化的资料请关注脚本之家其它相关文章!

相关文章

  • 教你一招完美解决vscode安装go插件失败问题

    教你一招完美解决vscode安装go插件失败问题

    VSCode是我们开发go程序的常用工具,但是安装VSCode成功后,创建一个.go文件居然提示错误了,所以下面下面这篇文章主要给大家介绍了如何通过一招完美解决vscode安装go插件失败问题的相关资料,需要的朋友可以参考下
    2022-07-07
  • golang pprof监控memory block mutex统计原理分析

    golang pprof监控memory block mutex统计原理分析

    这篇文章主要为大家介绍了golang pprof监控memory block mutex统计原理分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • go build -tags构建约束试验示例解析

    go build -tags构建约束试验示例解析

    这篇文章主要为大家介绍了go build -tags构建约束试验示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • Go语言中的if条件语句使用详解

    Go语言中的if条件语句使用详解

    这篇文章主要介绍了Go语言中的if条件语句的使用,包括if...else语句以及相关嵌套,需要的朋友可以参考下
    2015-10-10
  • 一文带你玩转Golang Prometheus Eexporter开发

    一文带你玩转Golang Prometheus Eexporter开发

    本文分两大块,一是搞清楚prometheus四种类型的指标Counter,Gauge,Histogram,Summary用golang语言如何构造这4种类型对应的指标,二是搞清楚修改指标值的场景和方式,感兴趣的可以了解一下
    2023-02-02
  • 解决Golang中ResponseWriter的一个坑

    解决Golang中ResponseWriter的一个坑

    这篇文章主要介绍了解决Golang中ResponseWriter的一个坑,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • Go常用技能日志log包创建使用示例

    Go常用技能日志log包创建使用示例

    这篇文章主要为大家介绍了Go常用技能日志log包创建使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • Go语言调用其它程序并获得程序输出的方法

    Go语言调用其它程序并获得程序输出的方法

    这篇文章主要介绍了Go语言调用其它程序并获得程序输出的方法,实例分析了Go调用cmd程序的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • Golang中Error的设计与实践详解

    Golang中Error的设计与实践详解

    这篇文章主要为大家详细介绍了Golang中Error的设计以及是具体如何处理错误的相关知识,文中的示例代码简洁易懂,需要的小伙伴可以跟随小编一起学习一下
    2023-08-08
  • golang(gin)的全局统一异常处理方式,并统一返回json

    golang(gin)的全局统一异常处理方式,并统一返回json

    这篇文章主要介绍了golang(gin)的全局统一异常处理方式,并统一返回json,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01

最新评论